这可能是你看过最详细的Java集合篇【一】—— ArrayList

news2024/11/25 10:24:17

文章目录

    • List集合的特点
    • List集合常见实现类继承关系
    • ArrayList
      • 继承关系
      • 数据结构和相关变量
      • 构造方法
      • 添加元素相关方法
        • 动态扩容机制
      • 查找元素相关方法
      • 删除元素相关方法
      • 清空方法
      • 遍历方法
      • 其它方法
      • 常见面试题

List集合的特点

List集合的特点:存储元素有序、可重复、有索引,有序是指存进去和取出来的顺序相同。

List集合常见实现类继承关系

在这里插入图片描述

ArrayList

ArrayList底层数据结构是动态数组可以存储null值。线程不安全

ArrayList因为有索引,所以通过索引查改快,时间复杂度是O(1),但是增删慢

继承关系

在这里插入图片描述

可以看到,ArrayList 继承了 AbstractList 抽象类,AbstractList 抽象类官方解释:这个抽象类提供了List的接口实现,以最大限度地减少实现该接口所需的工作量,该接口由“随机访问”数据存储(如数组)支持。对于顺序访问数据(如链表),应该优先使用AbstractSequentialList而不是这个类。

ArrayList 还实现了 RandomAccess 接口,RandomAccess接口是一个标志接口(Marker)。只要List集合实现这个接口,就能支持快速随机访问,然而又有人问,快速随机访问是什么东西?有什么作用?

通过查看Collections类中的binarySearch()方法,源码如下:

public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
    if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
        return Collections.indexedBinarySearch(list, key);
    else
        return Collections.iteratorBinarySearch(list, key);
}

由此可以看出,判断list是否实现RandomAccess接口来实行indexedBinarySerach(list,key)或iteratorBinarySerach(list,key)方法。

那么执行这两个方法有什么不同?

具体源码:

// for循环遍历
private static <T>
int indexedBinarySearch(List<? extends Comparable<? super T>> list, T key) {
    int low = 0;
    int high = list.size()-1;

    while (low <= high) {
        int mid = (low + high) >>> 1;
        Comparable<? super T> midVal = list.get(mid);
        int cmp = midVal.compareTo(key);

        if (cmp < 0)
            low = mid + 1;
        else if (cmp > 0)
            high = mid - 1;
        else
            return mid; // key found
    }
    return -(low + 1);  // key not found
}

// 迭代器遍历
private static <T>
int iteratorBinarySearch(List<? extends Comparable<? super T>> list, T key)
{
    int low = 0;
    int high = list.size()-1;
    ListIterator<? extends Comparable<? super T>> i = list.listIterator();

    while (low <= high) {
        int mid = (low + high) >>> 1;
        Comparable<? super T> midVal = get(i, mid);
        int cmp = midVal.compareTo(key);

        if (cmp < 0)
            low = mid + 1;
        else if (cmp > 0)
            high = mid - 1;
        else
            return mid; // key found
    }
    return -(low + 1);  // key not found
}

ArrayList 实现了 RandomAccess 接口,而LinkedList未实现 RandomAccess 接口,可以通过for循环遍历和迭代器遍历测试遍历两种集合的性能。这里不做演示,但是测试后可以得出下面结论。

ArrayList用for循环遍历比iterator迭代器遍历快,LinkedList用iterator迭代器遍历比for循环遍历快

所以我们可以知道,当一个List拥有快速随机访问功能时,其遍历方法采用for循环最快速。而没有快速随机访问功能的List,遍历的时候采用Iterator迭代器最快速。而 ArrayList 具备快速随机访问功能,因为底层是动态数组,能直接通过下标获取元素,故继承了 RandomAccess 接口,LinkedList 不具备快速随机访问功能,因为底层是双向链表,需要遍历获取元素,故未继承RandomAccess 接口。

数据结构和相关变量

/**
 * 默认初始容量
 */
private static final int DEFAULT_CAPACITY = 10;

/**
 * 用于空实例的共享空数组实例
 * 采用通过初始容量构建一个初始ArrayList,并且传入参数为0,会使用该数组,后续第一次add操作,只会自动扩容到1,后续也是每新增一个元素扩容一次,一次长度+1
 */
private static final Object[] EMPTY_ELEMENTDATA = {};

/**
 * 用于默认大小的空实例的共享空数组实例。我们将其与EMPTY_ELEMENTDATA区分开来,以便知道添加第一个元素时要膨胀多少
 * 采用默认的空参构造方法,会使用该数组,后续第一次add操作,会自动扩容到10
 */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
 * 存储ArrayList元素的数组缓冲区。数组列表的容量是这个数组缓冲区的长度。任何带有elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空数组列表将在添加第一个元素时扩展为DEFAULT_CAPACITY。
 */
transient Object[] elementData; // 非私有以简化嵌套类访问

/**
 * 数组列表的大小(包含的元素数量)
 */
private int size;

构造方法

// 通过初始容量构建一个初始ArrayList,ArrayList的数组长度为初始容量
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

// 默认的空参构造方法,构建一个空ArrayList,后续添加第一个元素数组的长度会自动扩容为10
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

// 通过传入集合构建一个ArrayList
public ArrayList(Collection<? extends E> c) {
    Object[] a = c.toArray();
    if ((size = a.length) != 0) {
        if (c.getClass() == ArrayList.class) {
            elementData = a;
        } else {
            elementData = Arrays.copyOf(a, size, Object[].class);
        }
    } else {
        // replace with empty array.
        elementData = EMPTY_ELEMENTDATA;
    }
}

添加元素相关方法

在数组尾部添加一个元素,如果成功,返回true,否则返回false。

public boolean add(E e) {
  // 动态扩容
  ensureCapacityInternal(size + 1);  // Increments modCount!!
  // 新元素放入数组
  elementData[size++] = e;
  return true;
}

动态扩容机制

ArrayList的动态扩容机制是当我们刚创建一个ArrayList的时候,它的容量大小为0,只有在第一次执行add操作时,才会扩容到10;后续添加元素如果达到10,会进行扩容,首先创建一个新数组,长度为原来数组的1.5倍,然后使用Arrays.copyOf()方法把老数组里面的元素复制到新数组里面。扩容完成再把要添加的新元素放入新数组。

相关源码:

// 计算容量,如果是第一次执行add操作,扩容为10
private static int calculateCapacity(Object[] elementData, int minCapacity) {
  // 如果是调用空参构造创建的数组
  if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    // DEFAULT_CAPACITY为10,第一次执行add操作minCapacity=size+1=0+1=1
    return Math.max(DEFAULT_CAPACITY, minCapacity);
  }
  return minCapacity;
}

private void ensureCapacityInternal(int minCapacity) {
  ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private void ensureExplicitCapacity(int minCapacity) {
  // 代表该集合结构修改的次数
  modCount++;

  // overflow-conscious code
  if (minCapacity - elementData.length > 0)
    // 触发扩容
    grow(minCapacity);
}

// 要分配的数组的最大大小。一些虚拟机在数组中保留一些头字。尝试分配更大的数组可能会导致OutOfMemoryError:请求的数组大小超过虚拟机限制
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
    // overflow-conscious code
    // 老容量
    int oldCapacity = elementData.length;
    // 扩容,原来长度加上原来长度右移1位,差不多为原来的1.5倍
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    // 除了第一次初始化长度为10,最小容量minCapacity都是数组里面元素数量+1
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    // 如果新容量newCapacity大于最大数组容量(int类型最大值2147483647-8=2147483639)
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    // 把老数组里面的元素复制到新数组里面
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    // minCapacity是元素数量+1,大于最大数组容量,则设置为int最大值,否则设置为最大数组容量
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

在指定索引位置插入一个元素。无返回值

public void add(int index, E element) {
    // 校验位置是否越界,最大为size
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 将索引位置以及其后面的元素后移一位
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    // 索引位置设置为新插入的元素
    elementData[index] = element;
    size++;
}

private void rangeCheckForAdd(int index) {
    if (index > size || index < 0)
      throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

批量添加元素,向本数组添加一个指定的集合的元素,如果成功,返回true,否则返回false。

// 将指定集合中的所有元素追加到本数组的末尾
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}

// 将指定集合中的所有元素追加到本数组的index索引位置后面
public boolean addAll(int index, Collection<? extends E> c) {
    // 校验位置是否越界,最大为size
    rangeCheckForAdd(index);

    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // Increments modCount

    int numMoved = size - index;
    if (numMoved > 0)
      System.arraycopy(elementData, index, elementData, index + numNew,
                       numMoved);

    System.arraycopy(a, 0, elementData, index, numNew);
    size += numNew;
    return numNew != 0;
}

查找元素相关方法

获取数组指定下标位置的元素

public E get(int index) {
    // 校验索引是否越界,最大为size-1
    rangeCheck(index);

    // 直接根据下标获取数组元素
    return elementData(index);
}

private void rangeCheck(int index) {
    if (index >= size)
      throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}

E elementData(int index) {
  	return (E) elementData[index];
}

删除元素相关方法

从当前数组中移除指定的元素。如果成功,返回true,否则返回false。

public boolean remove(Object o) {
    if (o == null) {
        // 遍历数组移除null节点
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        // 遍历数组移除非null节点
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

// 根据索引移除
private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
      // 将数组索引位置后一位移动到索引位置
      System.arraycopy(elementData, index+1, elementData, index,
                       numMoved);
    // 最后一位置空
    elementData[--size] = null; // clear to let GC do its work
}

从当前数组中移除指定位置的元素。返回被移除的元素

public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

批量移除元素,从当前数组中指定集合的元素,如果成功,返回true,否则返回false。

public boolean removeAll(Collection<?> c) {
    Objects.requireNonNull(c);
    return batchRemove(c, false);
}

// complement=false代表移除指定集合包含数组里面的元素,complement=true代表留下指定集合包含数组里面的元素
private boolean batchRemove(Collection<?> c, boolean complement) {
    final Object[] elementData = this.elementData;
    int r = 0, w = 0;
    boolean modified = false;
    try {
      // 将指定集合中不包含的数组里面的元素放到数组的前w位
      for (; r < size; r++)
        if (c.contains(elementData[r]) == complement)
          elementData[w++] = elementData[r];
    } finally {
      // Preserve behavioral compatibility with AbstractCollection,
      // even if c.contains() throws.
      if (r != size) {
        System.arraycopy(elementData, r,
                         elementData, w,
                         size - r);
        w += size - r;
      }
      // 将数组中索引为w后面的元素置空
      if (w != size) {
        // clear to let GC do its work
        for (int i = w; i < size; i++)
          elementData[i] = null;
        modCount += size - w;
        size = w;
        modified = true;
      }
    }
    return modified;
}

// 判断某个元素是狗在集合中
public boolean contains(Object o) {
  	return indexOf(o) >= 0;
}

// 根据元素获取集合索引,不存在返回-1
public int indexOf(Object o) {
    if (o == null) {
      for (int i = 0; i < size; i++)
        if (elementData[i]==null)
          return i;
    } else {
      for (int i = 0; i < size; i++)
        if (o.equals(elementData[i]))
          return i;
    }
    return -1;
}

清空方法

循环置空数组里面的每一个元素,size设置为0

public void clear() {
    modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;

    size = 0;
}

遍历方法

遍历ArrayList一般采用两种方式,普通for循环和增强for循环(也称foreach循环),普通for循环就是走索引遍历查询,比较简单,这里就不介绍了,foreach循环最终转换成迭代器形式。接下来分析下ArrayList的迭代器实现。

public ListIterator<E> listIterator(int index) {
    if (index < 0 || index > size)
        throw new IndexOutOfBoundsException("Index: "+index);
    return new ListItr(index);
}

public ListIterator<E> listIterator() {
    return new ListItr(0);
}

public Iterator<E> iterator() {
    return new Itr();
}

// ArrayList默认的迭代器。实现了默认的迭代器接口Iterator,包含迭代器的基础功能方法。
// 可以调用iterator()方法,直接获取
private class Itr implements Iterator<E> {
    int cursor;       // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    Itr() {}

    public boolean hasNext() {
        return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        checkForComodification();
        int i = cursor;
        if (i >= size)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i + 1;
        return (E) elementData[lastRet = i];
    }

    public void remove() {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.remove(lastRet);
            cursor = lastRet;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        final int size = ArrayList.this.size;
        int i = cursor;
        if (i >= size) {
            return;
        }
        final Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length) {
            throw new ConcurrentModificationException();
        }
        while (i != size && modCount == expectedModCount) {
            consumer.accept((E) elementData[i++]);
        }
        // update once at end of iteration to reduce heap write traffic
        cursor = i;
        lastRet = i - 1;
        checkForComodification();
    }

    final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }
}

// ArrayList升级版的迭代器,继承了ArrayList的迭代器,还实现了升级版的迭代器接口ListIterator。除了默认迭代器的一些功能外,还有一些附加的功能,如:
// add()方法,可以向List中添加对象。
// hasPrevious()和previous()方法,可以实现逆向(顺序向前)遍历。Iterator就不可以。
// ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。Iterator没有此功能。
// 都可实现删除对象,但是ListIterator可以实现对象的修改,set()方法可以实现。Iierator仅能遍历,不能修改。
// 可以通过listIterator()方法或者listIterator(int index)方法获取,listIterator()方法获取的ListItr实例中cursor为默认的0,listIterator(int index)方法获取的ListItr实例中cursor为传入的index值,代表从指定的索引开始遍历获取元素
private class ListItr extends Itr implements ListIterator<E> {
    ListItr(int index) {
        super();
        cursor = index;
    }

    public boolean hasPrevious() {
        return cursor != 0;
    }

    public int nextIndex() {
        return cursor;
    }

    public int previousIndex() {
        return cursor - 1;
    }

    @SuppressWarnings("unchecked")
    public E previous() {
        checkForComodification();
        int i = cursor - 1;
        if (i < 0)
            throw new NoSuchElementException();
        Object[] elementData = ArrayList.this.elementData;
        if (i >= elementData.length)
            throw new ConcurrentModificationException();
        cursor = i;
        return (E) elementData[lastRet = i];
    }

    public void set(E e) {
        if (lastRet < 0)
            throw new IllegalStateException();
        checkForComodification();

        try {
            ArrayList.this.set(lastRet, e);
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }

    public void add(E e) {
        checkForComodification();

        try {
            int i = cursor;
            ArrayList.this.add(i, e);
            cursor = i + 1;
            lastRet = -1;
            expectedModCount = modCount;
        } catch (IndexOutOfBoundsException ex) {
            throw new ConcurrentModificationException();
        }
    }
}

其它方法

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{

    // 将ArrayList实例的容量缩减为list的当前size(实际存储的元素个数)大小。
    public void trimToSize() {
        modCount++;
        if (size < elementData.length) {
            elementData = (size == 0)
              ? EMPTY_ELEMENTDATA
              : Arrays.copyOf(elementData, size);
        }
    }

    // 手动扩容
    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            // any size if not default element table
            ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

    // 获取集合中元素的个数
    public int size() {
        return size;
    }

    // 判断集合中是否包含元素,是返回true,否返回false
    public boolean isEmpty() {
        return size == 0;
    }

    // 返回指定元素在此集合中最后出现的索引,如果此集合不包含该元素,则返回-1
    public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

    // 返回ArrayList实例的浅拷贝。(元素本身不会被复制。)
    public Object clone() {
        try {
            ArrayList<?> v = (ArrayList<?>) super.clone();
            v.elementData = Arrays.copyOf(elementData, size);
            v.modCount = 0;
            return v;
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
    }

    // 集合转Object数组
    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }

    // 转指定数据类型的数组,传入的数组长度如果小于集合,则创建一个新数组,长度为集合长度,传入的数组长度如果大于集合,数组大于部分设置为null
    @SuppressWarnings("unchecked")
    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // Make a new array of a's runtime type, but my contents:
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }

    // 将此集合中指定位置的元素替换为指定元素
    public E set(int index, E element) {
        rangeCheck(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

    // 从集合中删除索引在fromIndex(含)和toIndex(不含)之间的所有元素。将所有后续元素向左移动(减少它们的索引)。这个调用通过{(toIndex - fromIndex)}元素来缩短集合。(如果{@code toIndex==fromIndex},此操作无效)
    protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex,
                         numMoved);

        // clear to let GC do its work
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }

    /**
     * 构造IndexOutOfBoundsException详细消息。
     */
    private String outOfBoundsMsg(int index) {
        return "Index: "+index+", Size: "+size;
    }

    // 仅保留此集合中包含在指定集合中的元素。换句话说,从该集合中删除指定集合中不包含的所有元素
    public boolean retainAll(Collection<?> c) {
        Objects.requireNonNull(c);
        return batchRemove(c, true);
    }
    
    // 将ArrayList实例的状态保存到一个输出流中(即序列化它)。
    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

    // 从输入流中重构ArrayList实例(即反序列化它)。
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            int capacity = calculateCapacity(elementData, size);
            SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }

    // 返回该集合中指定的fromIndex(包含)和toIndex(不包含)之间部分的。
    public List<E> subList(int fromIndex, int toIndex) {
        subListRangeCheck(fromIndex, toIndex, size);
        return new SubList(this, 0, fromIndex, toIndex);
    }

    // 校验subList(int fromIndex, int toIndex)方法中的fromIndex、toIndex索引位置是否越界
    static void subListRangeCheck(int fromIndex, int toIndex, int size) {
        if (fromIndex < 0)
            throw new IndexOutOfBoundsException("fromIndex = " + fromIndex);
        if (toIndex > size)
            throw new IndexOutOfBoundsException("toIndex = " + toIndex);
        if (fromIndex > toIndex)
            throw new IllegalArgumentException("fromIndex(" + fromIndex +
                                               ") > toIndex(" + toIndex + ")");
    }

    // 原集合切割后定义的新集合类
    private class SubList extends AbstractList<E> implements RandomAccess {
        private final AbstractList<E> parent;
        private final int parentOffset;
        private final int offset;
        int size;

        SubList(AbstractList<E> parent,
                int offset, int fromIndex, int toIndex) {
            this.parent = parent;
            this.parentOffset = fromIndex;
            this.offset = offset + fromIndex;
            this.size = toIndex - fromIndex;
            this.modCount = ArrayList.this.modCount;
        }

        public E set(int index, E e) {
            rangeCheck(index);
            checkForComodification();
            E oldValue = ArrayList.this.elementData(offset + index);
            ArrayList.this.elementData[offset + index] = e;
            return oldValue;
        }

        public E get(int index) {
            rangeCheck(index);
            checkForComodification();
            return ArrayList.this.elementData(offset + index);
        }

        public int size() {
            checkForComodification();
            return this.size;
        }

        public void add(int index, E e) {
            rangeCheckForAdd(index);
            checkForComodification();
            parent.add(parentOffset + index, e);
            this.modCount = parent.modCount;
            this.size++;
        }

        public E remove(int index) {
            rangeCheck(index);
            checkForComodification();
            E result = parent.remove(parentOffset + index);
            this.modCount = parent.modCount;
            this.size--;
            return result;
        }

        protected void removeRange(int fromIndex, int toIndex) {
            checkForComodification();
            parent.removeRange(parentOffset + fromIndex,
                               parentOffset + toIndex);
            this.modCount = parent.modCount;
            this.size -= toIndex - fromIndex;
        }

        public boolean addAll(Collection<? extends E> c) {
            return addAll(this.size, c);
        }

        public boolean addAll(int index, Collection<? extends E> c) {
            rangeCheckForAdd(index);
            int cSize = c.size();
            if (cSize==0)
                return false;

            checkForComodification();
            parent.addAll(parentOffset + index, c);
            this.modCount = parent.modCount;
            this.size += cSize;
            return true;
        }

        public Iterator<E> iterator() {
            return listIterator();
        }

        public ListIterator<E> listIterator(final int index) {
            checkForComodification();
            rangeCheckForAdd(index);
            final int offset = this.offset;

            return new ListIterator<E>() {
                int cursor = index;
                int lastRet = -1;
                int expectedModCount = ArrayList.this.modCount;

                public boolean hasNext() {
                    return cursor != SubList.this.size;
                }

                @SuppressWarnings("unchecked")
                public E next() {
                    checkForComodification();
                    int i = cursor;
                    if (i >= SubList.this.size)
                        throw new NoSuchElementException();
                    Object[] elementData = ArrayList.this.elementData;
                    if (offset + i >= elementData.length)
                        throw new ConcurrentModificationException();
                    cursor = i + 1;
                    return (E) elementData[offset + (lastRet = i)];
                }

                public boolean hasPrevious() {
                    return cursor != 0;
                }

                @SuppressWarnings("unchecked")
                public E previous() {
                    checkForComodification();
                    int i = cursor - 1;
                    if (i < 0)
                        throw new NoSuchElementException();
                    Object[] elementData = ArrayList.this.elementData;
                    if (offset + i >= elementData.length)
                        throw new ConcurrentModificationException();
                    cursor = i;
                    return (E) elementData[offset + (lastRet = i)];
                }

                @SuppressWarnings("unchecked")
                public void forEachRemaining(Consumer<? super E> consumer) {
                    Objects.requireNonNull(consumer);
                    final int size = SubList.this.size;
                    int i = cursor;
                    if (i >= size) {
                        return;
                    }
                    final Object[] elementData = ArrayList.this.elementData;
                    if (offset + i >= elementData.length) {
                        throw new ConcurrentModificationException();
                    }
                    while (i != size && modCount == expectedModCount) {
                        consumer.accept((E) elementData[offset + (i++)]);
                    }
                    // update once at end of iteration to reduce heap write traffic
                    lastRet = cursor = i;
                    checkForComodification();
                }

                public int nextIndex() {
                    return cursor;
                }

                public int previousIndex() {
                    return cursor - 1;
                }

                public void remove() {
                    if (lastRet < 0)
                        throw new IllegalStateException();
                    checkForComodification();

                    try {
                        SubList.this.remove(lastRet);
                        cursor = lastRet;
                        lastRet = -1;
                        expectedModCount = ArrayList.this.modCount;
                    } catch (IndexOutOfBoundsException ex) {
                        throw new ConcurrentModificationException();
                    }
                }

                public void set(E e) {
                    if (lastRet < 0)
                        throw new IllegalStateException();
                    checkForComodification();

                    try {
                        ArrayList.this.set(offset + lastRet, e);
                    } catch (IndexOutOfBoundsException ex) {
                        throw new ConcurrentModificationException();
                    }
                }

                public void add(E e) {
                    checkForComodification();

                    try {
                        int i = cursor;
                        SubList.this.add(i, e);
                        cursor = i + 1;
                        lastRet = -1;
                        expectedModCount = ArrayList.this.modCount;
                    } catch (IndexOutOfBoundsException ex) {
                        throw new ConcurrentModificationException();
                    }
                }

                final void checkForComodification() {
                    if (expectedModCount != ArrayList.this.modCount)
                        throw new ConcurrentModificationException();
                }
            };
        }

        public List<E> subList(int fromIndex, int toIndex) {
            subListRangeCheck(fromIndex, toIndex, size);
            return new SubList(this, offset, fromIndex, toIndex);
        }

        private void rangeCheck(int index) {
            if (index < 0 || index >= this.size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }

        private void rangeCheckForAdd(int index) {
            if (index < 0 || index > this.size)
                throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        }

        private String outOfBoundsMsg(int index) {
            return "Index: "+index+", Size: "+this.size;
        }

        private void checkForComodification() {
            if (ArrayList.this.modCount != this.modCount)
                throw new ConcurrentModificationException();
        }

        public Spliterator<E> spliterator() {
            checkForComodification();
            return new ArrayListSpliterator<E>(ArrayList.this, offset,
                                               offset + this.size, this.modCount);
        }
    }

    // 遍历集合,集合中每一个元素执行action行为操作
    @Override
    public void forEach(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        final int expectedModCount = modCount;
        @SuppressWarnings("unchecked")
        final E[] elementData = (E[]) this.elementData;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            action.accept(elementData[i]);
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

    // Spliterator为jdk1.8新增接口,也是一个迭代器实现,可拆分迭代器,为了并行执行而设计的
    @Override
    public Spliterator<E> spliterator() {
        return new ArrayListSpliterator<>(this, 0, -1, 0);
    }

    /** Index-based split-by-two, lazily initialized Spliterator */
    static final class ArrayListSpliterator<E> implements Spliterator<E> {

        /*
         * If ArrayLists were immutable, or structurally immutable (no
         * adds, removes, etc), we could implement their spliterators
         * with Arrays.spliterator. Instead we detect as much
         * interference during traversal as practical without
         * sacrificing much performance. We rely primarily on
         * modCounts. These are not guaranteed to detect concurrency
         * violations, and are sometimes overly conservative about
         * within-thread interference, but detect enough problems to
         * be worthwhile in practice. To carry this out, we (1) lazily
         * initialize fence and expectedModCount until the latest
         * point that we need to commit to the state we are checking
         * against; thus improving precision.  (This doesn't apply to
         * SubLists, that create spliterators with current non-lazy
         * values).  (2) We perform only a single
         * ConcurrentModificationException check at the end of forEach
         * (the most performance-sensitive method). When using forEach
         * (as opposed to iterators), we can normally only detect
         * interference after actions, not before. Further
         * CME-triggering checks apply to all other possible
         * violations of assumptions for example null or too-small
         * elementData array given its size(), that could only have
         * occurred due to interference.  This allows the inner loop
         * of forEach to run without any further checks, and
         * simplifies lambda-resolution. While this does entail a
         * number of checks, note that in the common case of
         * list.stream().forEach(a), no checks or other computation
         * occur anywhere other than inside forEach itself.  The other
         * less-often-used methods cannot take advantage of most of
         * these streamlinings.
         */

        private final ArrayList<E> list;
        private int index; // current index, modified on advance/split
        private int fence; // -1 until used; then one past last index
        private int expectedModCount; // initialized when fence set

        /** Create new spliterator covering the given  range */
        ArrayListSpliterator(ArrayList<E> list, int origin, int fence,
                             int expectedModCount) {
            this.list = list; // OK if null unless traversed
            this.index = origin;
            this.fence = fence;
            this.expectedModCount = expectedModCount;
        }

        private int getFence() { // initialize fence to size on first use
            int hi; // (a specialized variant appears in method forEach)
            ArrayList<E> lst;
            if ((hi = fence) < 0) {
                if ((lst = list) == null)
                    hi = fence = 0;
                else {
                    expectedModCount = lst.modCount;
                    hi = fence = lst.size;
                }
            }
            return hi;
        }

        public ArrayListSpliterator<E> trySplit() {
            int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
            return (lo >= mid) ? null : // divide range in half unless too small
                new ArrayListSpliterator<E>(list, lo, index = mid,
                                            expectedModCount);
        }

        public boolean tryAdvance(Consumer<? super E> action) {
            if (action == null)
                throw new NullPointerException();
            int hi = getFence(), i = index;
            if (i < hi) {
                index = i + 1;
                @SuppressWarnings("unchecked") E e = (E)list.elementData[i];
                action.accept(e);
                if (list.modCount != expectedModCount)
                    throw new ConcurrentModificationException();
                return true;
            }
            return false;
        }

        public void forEachRemaining(Consumer<? super E> action) {
            int i, hi, mc; // hoist accesses and checks from loop
            ArrayList<E> lst; Object[] a;
            if (action == null)
                throw new NullPointerException();
            if ((lst = list) != null && (a = lst.elementData) != null) {
                if ((hi = fence) < 0) {
                    mc = lst.modCount;
                    hi = lst.size;
                }
                else
                    mc = expectedModCount;
                if ((i = index) >= 0 && (index = hi) <= a.length) {
                    for (; i < hi; ++i) {
                        @SuppressWarnings("unchecked") E e = (E) a[i];
                        action.accept(e);
                    }
                    if (lst.modCount == mc)
                        return;
                }
            }
            throw new ConcurrentModificationException();
        }

        public long estimateSize() {
            return (long) (getFence() - index);
        }

        public int characteristics() {
            return Spliterator.ORDERED | Spliterator.SIZED | Spliterator.SUBSIZED;
        }
    }

    // 删除所有满足特定条件的集合元素。
    @Override
    public boolean removeIf(Predicate<? super E> filter) {
        Objects.requireNonNull(filter);
        // figure out which elements are to be removed
        // any exception thrown from the filter predicate at this stage
        // will leave the collection unmodified
        int removeCount = 0;
        final BitSet removeSet = new BitSet(size);
        final int expectedModCount = modCount;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            @SuppressWarnings("unchecked")
            final E element = (E) elementData[i];
            if (filter.test(element)) {
                removeSet.set(i);
                removeCount++;
            }
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }

        // shift surviving elements left over the spaces left by removed elements
        final boolean anyToRemove = removeCount > 0;
        if (anyToRemove) {
            final int newSize = size - removeCount;
            for (int i=0, j=0; (i < size) && (j < newSize); i++, j++) {
                i = removeSet.nextClearBit(i);
                elementData[j] = elementData[i];
            }
            for (int k=newSize; k < size; k++) {
                elementData[k] = null;  // Let gc do its work
            }
            this.size = newSize;
            if (modCount != expectedModCount) {
                throw new ConcurrentModificationException();
            }
            modCount++;
        }

        return anyToRemove;
    }

    // 将此集合中的每个元素替换为对该元素应用运算符的结果。由操作符抛出的错误或运行时异常被传递给调用者。
    @Override
    @SuppressWarnings("unchecked")
    public void replaceAll(UnaryOperator<E> operator) {
        Objects.requireNonNull(operator);
        final int expectedModCount = modCount;
        final int size = this.size;
        for (int i=0; modCount == expectedModCount && i < size; i++) {
            elementData[i] = operator.apply((E) elementData[i]);
        }
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }

    // 将此集合根据传入的规则进行排序
    @Override
    @SuppressWarnings("unchecked")
    public void sort(Comparator<? super E> c) {
        final int expectedModCount = modCount;
        Arrays.sort((E[]) elementData, 0, size, c);
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
        modCount++;
    }
}

常见面试题

Q:ArrayList在jdk1.7和jdk1.8的区别?

A:

jdk1.7:在JDK1.7中,使用ArrayList list = new ArrayList()创建List集合时,底层直接创建了长度是10的Object[]数组elementData;在接下来调用add()方法向集合中添加元素时,如果本次的添加导致底层elementData数组容量不足,则调用 ensureCapacity(int minCapacity) 方法进行扩容。默认情况下,扩容为原来的1.5倍(>>1),同时将原来数组中的所有数据复制到新的数组中。故而,由此得到结论,在开发中,建议使用带参构造器创建List集合:ArrayList list = new ArrayList(int capacity)。预估集合的大小,直接一次到位,避免中间的扩容,提高效率。

jdk1.8:JDK 1.8和1.7中 ArrayList 最明显的区别就是底层数组在JDK1.8中,如果不指定长度,使用无参构造方法ArrayList list = new ArrayList()创建List集合时,底层的Object[] elementData初始化为{}(空的数组),并没有直接创建长度为10的数组;而在第一次调用add()方法时,底层才创建了长度为10的数组,并将本次要添加的元素添加进去(这样做可节省内存消耗,因为在添加元素时数组名将指针指向了新的数组且老数组是一个空数组这样有利于System.gc(),并不会一直占据内存)。后续的添加和扩容操作与JDK1.7无异。

总结:JDK1.7中的ArrayList对象的创建,类似于单例中的饿汉式;而JDK1.8,则类似于单例中的懒汉式。这么做的好处就是:延迟了数组的创建,节省内存空间。

1.8和1.7的区别,由于都是比较简单的数组实现,可能除了1.8的代码一些细微的优化更好之外,再就是1.8的代码行数比较多之外(1.8有1461行、1.7有1173行),也可能是注释写的比较详细。其主要函数的具体实现相差无几。


Q:ArrayList怎么变成线程安全的?

A:

  1. 使用 Collections.synchronizedList() 方法。Java 提供了 Collections 类中的 synchronizedList() 方法,可以将一个普通的 ArrayList 转换成线程安全的 List。示例:List<E> myArrayList = Collections.synchronizedList(new ArrayList<E>());

  2. 使用 java.util.concurrent.CopyOnWriteArrayList 类:CopyOnWriteArrayList 是 Java 并发包(java.util.concurrent)中提供的一个线程安全的列表实现。它在读取操作上没有锁,并且支持在迭代期间进行修改操作,而不会抛出 ConcurrentModificationException 异常。示例:CopyOnWriteArrayList<E> myArrayList = new CopyOnWriteArrayList<E>();

  3. 写一个myArrayList继承自Arraylist,然后重写或按需求编写自己的方法,这些方法要写成synchronized,在这些synchronized的方法中调用ArrayList的方法。

    import java.util.ArrayList;
     
    public class MyArrayList<E> extends ArrayList<E> {
     
        @Override
        public synchronized boolean add(E e) {
            // 在 synchronized 方法中调用 ArrayList 的 add 方法
            return super.add(e);
        }
     
        @Override
        public synchronized void add(int index, E element) {
            // 在 synchronized 方法中调用 ArrayList 的 add 方法
            super.add(index, element);
        }
     
        @Override
        public synchronized E remove(int index) {
            // 在 synchronized 方法中调用 ArrayList 的 remove 方法
            return super.remove(index);
        }
     
        @Override
        public synchronized boolean remove(Object o) {
            // 在 synchronized 方法中调用 ArrayList 的 remove 方法
            return super.remove(o);
        }
     
        // 可以按需求继续重写其他需要同步的方法
     
        // 注意:需要根据具体的需求选择要同步的方法,不一定需要将所有方法都同步
     
    }
    
  4. 使用显式的锁:可以使用 java.util.concurrent.locks 包中提供的显式锁(如 ReentrantLock)或者 synchronized锁 来手动实现对 ArrayList 的同步。这需要在访问 ArrayList 的地方显式地获取和释放锁,从而确保在同一时刻只有一个线程可以访问 ArrayList。

    import java.util.ArrayList;
    import java.util.concurrent.locks.ReentrantLock;
     
    public class MyArrayList<E> {
     
        private ArrayList<E> arrayList = new ArrayList<>();
        private ReentrantLock lock = new ReentrantLock();
     
        public void add(E e) {
            lock.lock();
            try {
                // 在锁内部调用 ArrayList 的 add 方法
                arrayList.add(e);
            } finally {
                lock.unlock();
            }
        }
     
        public void add(int index, E element) {
            lock.lock();
            try {
                // 在锁内部调用 ArrayList 的 add 方法
                arrayList.add(index, element);
            } finally {
                lock.unlock();
            }
        }
     
        public E remove(int index) {
            lock.lock();
            try {
                // 在锁内部调用 ArrayList 的 remove 方法
                return arrayList.remove(index);
            } finally {
                lock.unlock();
            }
        }
     
        public boolean remove(Object o) {
            lock.lock();
            try {
                // 在锁内部调用 ArrayList 的 remove 方法
                return arrayList.remove(o);
            } finally {
                lock.unlock();
            }
        }
     
        // 可以按需求继续实现其他需要同步的方法
     
    }
    

Q:ArrayList为什么查改快,增删慢?

A:

查改快:查询和修改只有通过索引查询时,性能才很快,因为ArrayList底层是数组,可以直接找到对应下标数据,时间复杂度O(1)

增慢:因为ArrayList的底层数据结构是数组,数组不支持动态扩容。所以在向ArrayList中加入元素add的时候,有可能会导致List的扩容。如果不需要扩容,且直接在数组最后一位添加元素(即调用add(E e)方法),时间复杂度为O(1);如果不需要扩容,且指定添加元素的下标位置(即调用add(int index, E element)方法),需要将数组从添加的index位置为起点,到最后一个的所有元素,向后移动一位

删慢:删除指定元素或者删除指定下标的元素,ArrayList底层实际都是通过底层System.arraycopy()方法,将数组的index+1位置开始,复制到index位置,然后最后一位设置成null。实际上就是从删除的index位置为起点,到最后一个的所有元素,向前移动一位,然后把最后一位设置成null。所以这个过程会很慢。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/460061.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Shiro 实战教程(全)

目录 1.权限的管理 1.1 什么是权限管理 1.2 什么是身份认证 1.3 什么是授权 2.什么是shiro 3.shiro的核心架构 3.1 Subject 3.2 SecurityManager 3.3 Authenticator 3.4 Authorizer 3.5 Realm 3.6 SessionManager 3.7 SessionDAO 3.8 CacheManager 3.9 Cryptogr…

他们是怎么使用上gpt-4的-gpt-4在哪用

为什么有人在使用GPT4 openAI尚未正式发布GPT-4模型&#xff0c;也没有公布任何与GPT-4相关的信息。因此&#xff0c;没有人可以在使用GPT-4模型。 值得注意的是&#xff0c;虽然OpenAI尚未正式发布GPT-4&#xff0c;但由于其之前发布的GPT-3具有出色的性能和功能&#xff0c…

JVM 垃圾回收算法

之前说堆内存中有垃圾回收&#xff0c;比如Young区的Minor GC&#xff0c;Old区的Major GC&#xff0c;Young区和Old区 的Full GC。 但是对于一个对象而言&#xff0c;怎么确定它是垃圾&#xff1f;是否需要被回收&#xff1f;怎样对它进行回收&#xff1f;等等这些问题我们还需…

【3. 初学ROS,年轻人的第一个Node节点】

【3. 初学ROS&#xff0c;年轻人的第一个Node节点】 1. 工作空间设置2. 创建Package3. 回访依赖包4. 创建Node节点5. 源码编译6. 运行Node节点7. Node节点完善8. 总结 本教程是B站阿杰视频的笔记 视频地址&#xff1a;https://www.bilibili.com/video/BV1nG411V7HW 超声波传感器…

SqlServer2022安装与配置_并用Navicat连接SqlServer---sqlserver工作笔记0001

首先去下载 SQL Server 下载 | Microsoft https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 首先去下载安装包,这里我们下最新的 下载这个免费版的 可以看到下面有个全功能免费版本下载他 然后点击安装 下载以后安装 选择自定义 然后安装

策略模式——时势造影响

● 策略模式介绍 在软件开发中常常遇到这样的情况&#xff1a;实现某一个功能可以有多种算法或者策略&#xff0c;我们根据实际情况选择不同的算法或者策略来完成该功能。例如&#xff0c;排序算法&#xff0c;可以使用插入排序、归并排序、冒泡排序。 针对这种情况&#xff0c…

机器学习 day09(如何设置学习率α,特征工程,多项式回归)

常见的错误的学习曲线图&#xff08;上方两个&#xff09; 当关于迭代次数的学习曲线图&#xff0c;出现波浪型或向上递增型&#xff0c;表示梯度下降算法出错该情况可由&#xff0c;学习率α过大&#xff0c;或代码有bug导致 常用的调试方法&#xff1a; 选择一个非常非常…

【学习笔记】unity脚本学习(六)【GUI发展历程、IMGUI控件、Layout自动布局】

目录 unity 界面发展IMGUINGUI其他GUI插件uGUIUI 工具包比较 GUI基础GUI静态变量Unity扩展编辑器屏幕空间的总尺寸Screen.width 和 Screen.height GUI静态函数&#xff08;GUI控件&#xff09;Label图片 Box控件Button与RepeatButtonTextFieldTextAreaPasswordField其他控件 GU…

MySql主从复制原理及部署

MySql主从复制 原理&#xff1a; 1、Master节点开启binlog&#xff0c;并将变动记录到binlog中&#xff1b; 2、Slave节点定期探测Master节点的binlog&#xff0c;如有变动&#xff0c;开启I/O线程向Master节点请求二进制事件&#xff1b; 3、Master节点为每一个I/O线程启动…

win10卸载MySQL8.0

停止MySQL服务 shiftctrlesc打开任务管理器 将MySQL服务停止&#xff0c;这里我只有一个MySQL服务&#xff0c;如有多个MySQL服务&#xff0c;也要全部停止掉。 卸载mysql server等设备 控制面板 -》程序 -》 程序和功能&#xff0c;将mysql server等设备卸载掉&#xff0c;好…

SpringCloudAlibaba服务熔断、限流——Sentinel

Sentinel 本专栏学习内容来自尚硅谷周阳老师的视频 有兴趣的小伙伴可以点击视频地址观看 简介 Sentinel是Alibaba公司推出的一个熔断与限流工具&#xff0c;相当于我们之前学习的Hystrix&#xff0c;可以解决服务使用中的各种问题&#xff0c;例如&#xff1a;服务雪崩、服务降…

minigpt4搭建过程记录,简单体验图文识别乐趣

引言 从3月开始&#xff0c;aigc进入了疯狂的开端&#xff0c;正如4月12日无界 AI直播 在《探索 AIGC 与人类合作的无限可能》中关于梳理的时间线一样&#xff0c;aigc的各种产品如雨后春笋般进入了不可逆的态势&#xff0c;里面有句话很形象&#xff0c;人间一日&#xff0c;…

信息收集(四)服务器信息收集

信息收集&#xff08;一&#xff09;域名信息收集 信息收集&#xff08;二&#xff09;IP信息收集 信息收集&#xff08;三&#xff09;端口和目录信息收集 WAF指纹识别 什么是WAF WAF的全称是&#xff08;Web Application Firewall &#xff09;Web 应用防火墙用来过滤HTTP…

最新国内免费chatgpt 的试用方法

方式一&#xff1a; 免费账号&#xff1a; 地址&#xff1a;gpt-easy.com 账号test666, 666666 方式二&#xff1a; wheart.cn 每3小时15次调用 方式三&#xff1a; Microsoft Edge 插件&#xff0c;每天30次免费 方式四&#xff1a; wetab插件&#xff0c;多源切换&am…

StarRocks 3.0 集群安装手册

本文介绍如何以二进制安装包方式手动部署最新版 StarRocks 3.0集群。 什么是 StarRocks StarRocks 是新一代极速全场景 MPP (Massively Parallel Processing) 数据库。StarRocks 的愿景是能够让用户的数据分析变得更加简单和敏捷。用户无需经过复杂的预处理&#xff0c;就可以…

2023年五月份图形化三级打卡试题

活动时间 从2023年5月1日至5月21日&#xff0c;每天一道编程题。 本次打卡的规则如下&#xff1a; 小朋友每天利用10~15分钟做一道编程题&#xff0c;遇到问题就来群内讨论&#xff0c;我来给大家答疑。 小朋友做完题目后&#xff0c;截图到朋友圈打卡并把打卡的截图发到活动群…

Android硬件通信之 GPIO通信

一&#xff0c;为什么要和硬件通信 1.1&#xff0c;做软件开发的可能大多只是在手机上做服务器/客户端这种应用&#xff0c;说白了这些只是对数据的处理&#xff0c;对数据做存储和读取&#xff0c;以及分析的工作。 1.2 但随着智能领域的发展&#xff0c;人们已不满足手动去…

医院核心数据库一体化建设实践

建设背景 “以数据为核心资源的数字化时代&#xff0c;正在成为引领和推动新一轮科技革命的核心力量&#xff0c;将会深刻影响卫生健康行业。” 这是四月份发布的《公立医院运营管理信息化功能指引》中对数据重要性的描述。数据库作为数据的载体&#xff0c;支撑着整个业务系…

李清照最经典的10首诗词

在三千年的诗歌艺术中&#xff0c;男人一直是绝对的主角&#xff0c;虽然时常有女诗人&#xff0c;却如流星闪过。 一直到宋代&#xff0c;李清照的横空出世&#xff0c;给文坛带来一股清风。 她被誉为“千古第一才女”&#xff0c;她的诗词可柔美、可刚毅。 有人将她与李煜…

android studio RadioButton单选按钮

1.定义 <!--单选按钮--> <TextViewandroid:layout_marginTop"10dp"android:layout_width"match_parent"android:layout_height"wrap_content"android:text"请选择你的性别&#xff1a;"> </TextView> <RadioGrou…