前言

JDK源码解析系列文章,都是基于JDK8分析的,虽然JDK14已经出来,但是JDK8我还不会,我...

类图

  • 实现了RandomAccess接口,可以随机访问
  • 实现了Cloneable接口,可以克隆
  • 实现了Serializable接口,可以序列化、反序列化
  • 实现了List接口,是List的实现类之一
  • 实现了Collection接口,是Java Collections Framework成员之一
  • 实现了Iterable接口,可以使用for-each迭代

属性

  1. // 序列化版本UID
  2. private static final long
  3. serialVersionUID = 8683452581122892189L;
  4. /**
  5. * 默认的初始容量
  6. */
  7. private static final int
  8. DEFAULT_CAPACITY = 10;
  9. /**
  10. * 用于空实例的共享空数组实例
  11. * new ArrayList(0);
  12. */
  13. private static final Object[]
  14. EMPTY_ELEMENTDATA = {};
  15. /**
  16. * 用于提供默认大小的实例的共享空数组实例
  17. * new ArrayList();
  18. */
  19. private static final Object[]
  20. DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
  21. /**
  22. * 存储ArrayList元素的数组缓冲区
  23. * ArrayList的容量,是数组的长度
  24. *
  25. * non-private to simplify nested class access
  26. */
  27. transient Object[] elementData;
  28. /**
  29. * ArrayList中元素的数量
  30. */
  31. private int size;

小朋友,你四否有很多问号?

  1. 为什么空实例默认数组有的时候是EMPTY_ELEMENTDATA,而又有的时候是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
  2. 为什么elementData要被transient修饰
  3. 为什么elementData没有被private修饰?难道正如注释所写的non-private to simplify nested class access

带着问题,我们继续往下看。

构造方法

带初始容量的构造方法

  1. /**
  2. * 带一个初始容量参数的构造方法
  3. *
  4. * @param initialCapacity 初始容量
  5. * @throws 如果初始容量非法就抛出
  6. * IllegalArgumentException
  7. */
  8. public ArrayList(int initialCapacity) {
  9. if (initialCapacity > 0) {
  10. this.elementData =
  11. new Object[initialCapacity];
  12. } else if (initialCapacity == 0) {
  13. this.elementData = EMPTY_ELEMENTDATA;
  14. } else {
  15. throw new IllegalArgumentException(
  16. "Illegal Capacity: "+ initialCapacity);
  17. }
  18. }
  • 如果initialCapacity > 0,就创建一个新的长度是initialCapacity的数组
  • 如果initialCapacity == 0,就使用EMPTY_ELEMENTDATA
  • 其他情况,initialCapacity不合法,抛出异常

无参构造方法

  1. /**
  2. * 无参构造方法 将elementData 赋值为
  3. * DEFAULTCAPACITY_EMPTY_ELEMENTDATA
  4. */
  5. public ArrayList() {
  6. this.elementData =
  7. DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
  8. }

带一个集合参数的构造方法

  1. /**
  2. * 带一个集合参数的构造方法
  3. *
  4. * @param c 集合,代表集合中的元素会被放到list中
  5. * @throws 如果集合为空,抛出NullPointerException
  6. */
  7. public ArrayList(Collection<? extends E> c) {
  8. elementData = c.toArray();
  9. // 如果 size != 0
  10. if ((size = elementData.length) != 0) {
  11. // c.toArray 可能不正确的,不返回 Object[]
  12. // https://bugs.openjdk.java.net/browse/JDK-6260652
  13. if (elementData.getClass() != Object[].class)
  14. elementData = Arrays.copyOf(
  15. elementData, size, Object[].class);
  16. } else {
  17. // size == 0
  18. // 将EMPTY_ELEMENTDATA 赋值给 elementData
  19. this.elementData = EMPTY_ELEMENTDATA;
  20. }
  21. }
  • 使用将集合转换为数组的方法
  • 为了防止c.toArray()方法不正确的执行,导致没有返回Object[],特殊做了处理
  • 如果数组大小等于0,则使用 EMPTY_ELEMENTDATA

那么问题来了,什么情况下c.toArray()会不返回Object[]呢?

  1. public static void main(String[] args) {
  2. List<String> list = new ArrayList<>(Arrays.asList("list"));
  3. // class java.util.ArrayList
  4. System.out.println(list.getClass());
  5. Object[] listArray = list.toArray();
  6. // class [Ljava.lang.Object;
  7. System.out.println(listArray.getClass());
  8. listArray[0] = new Object();
  9. System.out.println();
  10. List<String> asList = Arrays.asList("asList");
  11. // class java.util.Arrays$ArrayList
  12. System.out.println(asList.getClass());
  13. Object[] asListArray = asList.toArray();
  14. // class [Ljava.lang.String;
  15. System.out.println(asListArray.getClass());
  16. // java.lang.ArrayStoreException
  17. asListArray[0] = new Object();
  18. }

我们通过这个例子可以看出来,java.util.ArrayList.toArray()方法会返回Object[]没有问题。而java.util.Arrays的私有内部类ArrayList的toArray()方法可能不返回Object[]

为什么会这样?

我们看ArrayList的toArray()方法源码:

  1. public Object[] toArray() {
  2. // ArrayLisy中 elementData是这样定义的
  3. // transient Object[] elementData;
  4. return Arrays.copyOf(elementData, size);
  5. }

使用了Arrays.copyOf()方法:

  1. public static <T> T[] copyOf(T[] original, int newLength) {
  2. // original.getClass() 是 class [Ljava.lang.Object
  3. return (T[]) copyOf(original, newLength, original.getClass());
  4. }

copyOf()的具体实现:

  1. public static <T,U> T[] copyOf(U[] original,
  2. int newLength, Class<? extends T[]> newType) {
  3. @SuppressWarnings("unchecked")
  4. /**
  5. * 如果newType是Object[] copy 数组 类型就是 Object
  6. * 否则就是 newType 类型
  7. */
  8. T[] copy = ((Object)newType == (Object)Object[].class)
  9. ? (T[]) new Object[newLength]
  10. : (T[]) Array.newInstance(newType.getComponentType(), newLength);
  11. System.arraycopy(original, 0, copy, 0,
  12. Math.min(original.length, newLength));
  13. return copy;
  14. }

我们知道ArrayList中elementData就是Object[]类型,所以ArrayList的toArray()方法必然会返回Object[]

我们再看一下java.util.Arrays的内部ArrayList源码(截取的部分源码):

  1. private static class ArrayList<E> extends AbstractList<E>
  2. implements RandomAccess, java.io.Serializable {
  3. // 存储元素的数组
  4. private final E[] a;
  5. ArrayList(E[] array) {
  6. // 直接把接收的数组 赋值 给 a
  7. a = Objects.requireNonNull(array);
  8. }
  9. /**
  10. * obj 为空抛出异常
  11. * 不为空 返回 obj
  12. */
  13. public static <T> T requireNonNull(T obj) {
  14. if (obj == null)
  15. throw new NullPointerException();
  16. return obj;
  17. }
  18. @Override
  19. public Object[] toArray() {
  20. // 返回 a 的克隆对象
  21. return a.clone();
  22. }
  23. }

这是Arrays.asList()方法源码

  1. public static <T> List<T> asList(T... a) {
  2. return new ArrayList<>(a);
  3. }

不难看出来java.util.Arrays的内部ArrayList的toArray()方法,是构造方法接收什么类型的数组,就返回什么类型的数组。

所以,在我们上面的例子中,实际上返回的是String类型的数组,再将其中的元素赋值成Object类型的,自然报错。

我们还是继续看ArrayList吧...

插入方法

在列表最后添加指定元素

  1. /**
  2. * 在列表最后添加指定元素
  3. *
  4. * @param e 要添加的指定元素
  5. * @return true
  6. */
  7. public boolean add(E e) {
  8. // 增加 modCount !!
  9. ensureCapacityInternal(size + 1);
  10. elementData[size++] = e;
  11. return true;
  12. }
  • 在父类AbstractList上,定义了modCount 属性,用于记录数组修改的次数。

在指定位置添加指定元素

  1. /**
  2. * 在指定位置添加指定元素
  3. * 如果指定位置已经有元素,就将该元素和随后的元素移动到右面一位
  4. *
  5. * @param index 待插入元素的下标
  6. * @param element 待插入的元素
  7. * @throws 可能抛出 IndexOutOfBoundsException
  8. */
  9. public void add(int index, E element) {
  10. rangeCheckForAdd(index);
  11. // 增加 modCount !!
  12. ensureCapacityInternal(size + 1);
  13. System.arraycopy(elementData, index, elementData, index + 1,
  14. size - index);
  15. elementData[index] = element;
  16. size++;
  17. }

插入方法调用的其他私有方法

  1. /**
  2. * 计算容量
  3. */
  4. private static int calculateCapacity(
  5. Object[] elementData, int minCapacity) {
  6. if (elementData ==
  7. DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
  8. return Math.max(DEFAULT_CAPACITY, minCapacity);
  9. }
  10. return minCapacity;
  11. }
  12. private void ensureCapacityInternal(int minCapacity) {
  13. ensureExplicitCapacity(
  14. calculateCapacity(elementData, minCapacity)
  15. );
  16. }
  17. private void ensureExplicitCapacity(int minCapacity) {
  18. modCount++;
  19. // overflow-conscious code
  20. if (minCapacity - elementData.length > 0)
  21. grow(minCapacity);
  22. }

扩容方法

  1. /**
  2. * 数组可以分配的最大size
  3. * 一些虚拟机在数组中预留一些header words
  4. * 如果尝试分配更大的size,可能导致OutOfMemoryError
  5. */
  6. private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
  7. /**
  8. * 增加容量,至少保证比minCapacity大
  9. * @param minCapacity 期望的最小容量
  10. */
  11. private void grow(int minCapacity) {
  12. // 有可能溢出的代码
  13. int oldCapacity = elementData.length;
  14. int newCapacity = oldCapacity + (oldCapacity >> 1);
  15. if (newCapacity - minCapacity < 0)
  16. newCapacity = minCapacity;
  17. if (newCapacity - MAX_ARRAY_SIZE > 0)
  18. newCapacity = hugeCapacity(minCapacity);
  19. // minCapacity is usually close to size, so this is a win:
  20. elementData = Arrays.copyOf(elementData, newCapacity);
  21. }
  22. /**
  23. * 最大容量返回 Integer.MAX_VALUE
  24. */
  25. private static int hugeCapacity(int minCapacity) {
  26. if (minCapacity < 0) // overflow
  27. throw new OutOfMemoryError();
  28. return (minCapacity > MAX_ARRAY_SIZE) ?
  29. Integer.MAX_VALUE :
  30. MAX_ARRAY_SIZE;
  31. }
  • 通常情况新容量是原来容量的1.5倍
  • 如果原容量的1.5倍比minCapacity小,那么就扩容到minCapacity
  • 特殊情况扩容到Integer.MAX_VALUE

看完构造方法、添加方法、扩容方法之后,上文第1个问题终于有了答案。原来,new ArrayList()会将elementData 赋值为 DEFAULTCAPACITY_EMPTY_ELEMENTDATA,new ArrayList(0)会将elementData 赋值为 EMPTY_ELEMENTDATA,EMPTY_ELEMENTDATA添加元素会扩容到容量为1,而DEFAULTCAPACITY_EMPTY_ELEMENTDATA扩容之后容量为10

通过反射我们可以验证这一想法。如下:

  1. public static void main(String[] args) {
  2. printDefaultCapacityList();
  3. printEmptyCapacityList();
  4. }
  5. public static void printDefaultCapacityList() {
  6. ArrayList defaultCapacity = new ArrayList();
  7. System.out.println(
  8. "default 初始化长度:" + getCapacity(defaultCapacity));
  9. defaultCapacity.add(1);
  10. System.out.println(
  11. "default add 之后 长度:" + getCapacity(defaultCapacity));
  12. }
  13. public static void printEmptyCapacityList() {
  14. ArrayList emptyCapacity = new ArrayList(0);
  15. System.out.println(
  16. "empty 初始化长度:" + getCapacity(emptyCapacity));
  17. emptyCapacity.add(1);
  18. System.out.println(
  19. "empty add 之后 长度:" + getCapacity(emptyCapacity));
  20. }
  21. public static int getCapacity(ArrayList<?> arrayList) {
  22. Class<ArrayList> arrayListClass = ArrayList.class;
  23. try {
  24. // 获取 elementData 字段
  25. Field field = arrayListClass.getDeclaredField("elementData");
  26. // 开启访问权限
  27. field.setAccessible(true);
  28. // 把示例传入get,获取实例字段elementData的值
  29. Object[] objects = (Object[]) field.get(arrayList);
  30. //返回当前ArrayList实例的容量值
  31. return objects.length;
  32. } catch (Exception e) {
  33. e.printStackTrace();
  34. return -1;
  35. }
  36. }

移除方法

移除指定下标元素方法

  1. /**
  2. * 移除列表中指定下标位置的元素
  3. * 将所有的后续元素,向左移动
  4. *
  5. * @param 要移除的指定下标
  6. * @return 返回被移除的元素
  7. * @throws 下标越界会抛出IndexOutOfBoundsException
  8. */
  9. public E remove(int index) {
  10. rangeCheck(index);
  11. modCount++;
  12. E oldValue = elementData(index);
  13. int numMoved = size - index - 1;
  14. if (numMoved > 0)
  15. System.arraycopy(elementData,
  16. index+1, elementData, index, numMoved);
  17. // 将引用置空,让GC回收
  18. elementData[--size] = null;
  19. return oldValue;
  20. }

移除指定元素方法

  1. /**
  2. * 移除第一个在列表中出现的指定元素
  3. * 如果存在,移除返回true
  4. * 否则,返回false
  5. *
  6. * @param o 指定元素
  7. */
  8. public boolean remove(Object o) {
  9. if (o == null) {
  10. for (int index = 0; index < size; index++)
  11. if (elementData[index] == null) {
  12. fastRemove(index);
  13. return true;
  14. }
  15. } else {
  16. for (int index = 0; index < size; index++)
  17. if (o.equals(elementData[index])) {
  18. fastRemove(index);
  19. return true;
  20. }
  21. }
  22. return false;
  23. }

移除方法名字、参数的个数都一样,使用的时候要注意。

私有移除方法

  1. /*
  2. * 私有的 移除 方法 跳过边界检查且不返回移除的元素
  3. */
  4. private void fastRemove(int index) {
  5. modCount++;
  6. int numMoved = size - index - 1;
  7. if (numMoved > 0)
  8. System.arraycopy(elementData, index+1, elementData, index,
  9. numMoved);
  10. // 将引用置空,让GC回收
  11. elementData[--size] = null;
  12. }

查找方法

查找指定元素的所在位置

  1. /**
  2. * 返回指定元素第一次出现的下标
  3. * 如果不存在该元素,返回 -1
  4. * 如果 o ==null 会特殊处理
  5. */
  6. public int indexOf(Object o) {
  7. if (o == null) {
  8. for (int i = 0; i < size; i++)
  9. if (elementData[i]==null)
  10. return i;
  11. } else {
  12. for (int i = 0; i < size; i++)
  13. if (o.equals(elementData[i]))
  14. return i;
  15. }
  16. return -1;
  17. }

查找指定位置的元素

  1. /**
  2. * 返回指定位置的元素
  3. *
  4. * @param index 指定元素的位置
  5. * @throws index越界会抛出IndexOutOfBoundsException
  6. */
  7. public E get(int index) {
  8. rangeCheck(index);
  9. return elementData(index);
  10. }

该方法直接返回elementData数组指定下标的元素,效率还是很高的。所以ArrayList,for循环遍历效率也是很高的。

序列化方法

  1. /**
  2. * 将ArrayLisy实例的状态保存到一个流里面
  3. */
  4. private void writeObject(java.io.ObjectOutputStream s)
  5. throws java.io.IOException{
  6. // Write out element count, and any hidden stuff
  7. int expectedModCount = modCount;
  8. s.defaultWriteObject();
  9. // Write out size as capacity for behavioural compatibility with clone()
  10. s.writeInt(size);
  11. // 按照顺序写入所有的元素
  12. for (int i=0; i<size; i++) {
  13. s.writeObject(elementData[i]);
  14. }
  15. if (modCount != expectedModCount) {
  16. throw new ConcurrentModificationException();
  17. }
  18. }

反序列化方法

  1. /**
  2. * 根据一个流(参数)重新生成一个ArrayList
  3. */
  4. private void readObject(java.io.ObjectInputStream s)
  5. throws java.io.IOException, ClassNotFoundException {
  6. elementData = EMPTY_ELEMENTDATA;
  7. // Read in size, and any hidden stuff
  8. s.defaultReadObject();
  9. // Read in capacity
  10. s.readInt();
  11. if (size > 0) {
  12. // be like clone(), allocate array based upon size not capacity
  13. ensureCapacityInternal(size);
  14. Object[] a = elementData;
  15. // Read in all elements in the proper order.
  16. for (int i=0; i<size; i++) {
  17. a[i] = s.readObject();
  18. }
  19. }
  20. }

看完序列化,反序列化方法,我们终于又能回答开篇的第二个问题了。elementData之所以用transient修饰,是因为JDK不想将整个elementData都序列化或者反序列化,而只是将size和实际存储的元素序列化或反序列化,从而节省空间和时间。

创建子数组

  1. public List<E> subList(int fromIndex, int toIndex) {
  2. subListRangeCheck(fromIndex, toIndex, size);
  3. return new SubList(this, 0, fromIndex, toIndex);
  4. }

我们看一下简短版的SubList

  1. private class SubList extends AbstractList<E> implements RandomAccess {
  2. private final AbstractList<E> parent;
  3. private final int parentOffset;
  4. private final int offset;
  5. int size;
  6. SubList(AbstractList<E> parent,
  7. int offset, int fromIndex, int toIndex) {
  8. this.parent = parent;
  9. this.parentOffset = fromIndex;
  10. this.offset = offset + fromIndex;
  11. this.size = toIndex - fromIndex;
  12. this.modCount = ArrayList.this.modCount;
  13. }
  14. public E set(int index, E e) {
  15. rangeCheck(index);
  16. checkForComodification();
  17. E oldValue = ArrayList.this.elementData(offset + index);
  18. ArrayList.this.elementData[offset + index] = e;
  19. return oldValue;
  20. }
  21. // 省略代码...
  22. }
  • SubList的set()方法,是直接修改ArrayListelementData数组的,使用中应该注意
  • SubList是没有实现Serializable接口的,是不能序列化的

迭代器

创建迭代器方法

  1. public Iterator<E> iterator() {
  2. return new Itr();
  3. }

Itr属性

  1. // 下一个要返回的元素的下标
  2. int cursor;
  3. // 最后一个要返回元素的下标 没有元素返回 -1
  4. int lastRet = -1;
  5. // 期望的 modCount
  6. int expectedModCount = modCount;

Itr的hasNext() 方法

  1. public boolean hasNext() {
  2. return cursor != size;
  3. }

Itr的next()方法

  1. public E next() {
  2. checkForComodification();
  3. int i = cursor;
  4. if (i >= size)
  5. throw new NoSuchElementException();
  6. Object[] elementData = ArrayList.this.elementData;
  7. if (i >= elementData.length)
  8. throw new ConcurrentModificationException();
  9. cursor = i + 1;
  10. return (E) elementData[lastRet = i];
  11. }
  12. final void checkForComodification() {
  13. if (modCount != expectedModCount)
  14. throw new ConcurrentModificationException();
  15. }

在迭代的时候,会校验modCount是否等于expectedModCount,不等于就会抛出著名的ConcurrentModificationException异常。什么时候会抛出ConcurrentModificationException

  1. public static void main(String[] args) {
  2. ArrayList arrayList = new ArrayList();
  3. for (int i = 0; i < 10; i++) {
  4. arrayList.add(i);
  5. }
  6. remove(arrayList);
  7. System.out.println(arrayList);
  8. }
  9. public static void remove(ArrayList<Integer> list) {
  10. Iterator<Integer> iterator = list.iterator();
  11. while (iterator.hasNext()) {
  12. Integer number = iterator.next();
  13. if (number % 2 == 0) {
  14. // 抛出ConcurrentModificationException异常
  15. list.remove(number);
  16. }
  17. }
  18. }

那怎么写才能不抛出ConcurrentModificationException?很简单,将list.remove(number);换成iterator.remove();即可。why?请看Itr的remove()源码...

Itr的remove()方法

  1. public void remove() {
  2. if (lastRet < 0)
  3. throw new IllegalStateException();
  4. checkForComodification();
  5. try {
  6. ArrayList.this.remove(lastRet);
  7. cursor = lastRet;
  8. lastRet = -1;
  9. // 移除之后将modCount 重新赋值给 expectedModCount
  10. expectedModCount = modCount;
  11. } catch (IndexOutOfBoundsException ex) {
  12. throw new ConcurrentModificationException();
  13. }
  14. }

原因就是因为Itr的remove()方法,移除之后将modCount重新赋值给 expectedModCount。这就是源码,不管单线程还是多线程,只要违反了规则,就会抛异常。

源码看的差不多了,开篇的问题却还剩一个!到底为什么elementData没有用private修饰呢?

我们知道的,private修饰的变量,内部类也是可以访问到的。难道注释中non-private to simplify nested class access的这句话有毛病?

当我们看表面看不到什么东西的时候,不妨看一下底层。

测试类代码:

一顿javacjavap之后(使用JDK8):

再一顿javacjavap之后(使用JDK11):

虽然字节码指令我还看不太懂,但是我能品出来,注释是没毛病的,private修饰的确会影响内部类的访问。

ArrayList类注释翻译

类注释还是要看的,能给我们一个整体的了解这个类。我将ArrayList的类注释大概翻译整理了一下:

  • ArrayList是实现List接口的可自动扩容的数组。实现了所有的List操作,允许所有的元素,包括null值。
  • ArrayList大致和Vector相同,除了ArrayList是非同步的。
  • size isEmpty get set iteratorlistIterator 方法时间复杂度是O(1),常量时间。其他方法是O(n),线性时间。
  • 每一个ArrayList实例都有一个capacity(容量)。capacity是用于存储列表中元素的数组的大小。capacity至少和列表的大小一样大。
  • 如果多个线程同时访问ArrayList的实例,并且至少一个线程会修改,必须在外部保证ArrayList的同步。修改包括添加删除扩容等操作,仅仅设置值不包括。这种场景可以用其他的一些封装好的同步的list。如果不存在这样的Object,ArrayList应该用Collections.synchronizedList包装起来最好在创建的时候就包装起来,来保证同步访问。
  • iterator()listIterator(int)方法是fail-fast的,如果在迭代器创建之后,列表进行结构化修改,迭代器会抛出ConcurrentModificationException
  • 面对并发修改,迭代器快速失败、清理,而不是在未知的时间不确定的情况下冒险。请注意,快速失败行为不能被保证。通常来讲,不能同步进行的并发修改几乎不可能做任何保证。因此,写依赖这个异常的程序的代码是错误的,快速失败行为应该仅仅用于防止bug

总结

  • ArrayList底层的数据结构是数组
  • ArrayList可以自动扩容,不传初始容量或者初始容量是0,都会初始化一个空数组,但是如果添加元素,会自动进行扩容,所以,创建ArrayList的时候,给初始容量是必要的
  • Arrays.asList()方法返回的是的Arrays内部的ArrayList,用的时候需要注意
  • subList()返回内部类,不能序列化,和ArrayList共用同一个数组
  • 迭代删除要用,迭代器的remove方法,或者可以用倒序的for循环
  • ArrayList重写了序列化、反序列化方法,避免序列化、反序列化全部数组,浪费时间和空间
  • elementData不使用private修饰,可以简化内部类的访问

源码系列第一篇,一不小心就写的有点长。但是懵懂到深刻的过程还是挺耐人寻味的。文章中没有展开的点,或者你有什么其他好奇的地方,欢迎留言讨论。我们下篇文章再见...

欢迎关注个人微信公众号【如逆水行舟】,用心输出基础、算法、源码系列文章。

ArrayList源码解析,老哥,来一起复习一哈?的更多相关文章

  1. ArrayList源码解析

    ArrayList简介 ArrayList定义 1 public class ArrayList<E> extends AbstractList<E> implements L ...

  2. 顺序线性表 ---- ArrayList 源码解析及实现原理分析

    原创播客,如需转载请注明出处.原文地址:http://www.cnblogs.com/crawl/p/7738888.html ------------------------------------ ...

  3. 面试必备:ArrayList源码解析(JDK8)

    面试必备:ArrayList源码解析(JDK8) https://blog.csdn.net/zxt0601/article/details/77281231 概述很久没有写博客了,准确的说17年以来 ...

  4. ArrayList源码解析(二)

    欢迎转载,转载烦请注明出处,谢谢. https://www.cnblogs.com/sx-wuyj/p/11177257.html 自己学习ArrayList源码的一些心得记录. 继续上一篇,Arra ...

  5. Java中的容器(集合)之ArrayList源码解析

    1.ArrayList源码解析 源码解析: 如下源码来自JDK8(如需查看ArrayList扩容源码解析请跳转至<Java中的容器(集合)>第十条):. package java.util ...

  6. Collection集合重难点梳理,增强for注意事项和三种遍历的应用场景,栈和队列特点,数组和链表特点,ArrayList源码解析, LinkedList-源码解析

    重难点梳理 使用到的新单词: 1.collection[kəˈlekʃn] 聚集 2.empty[ˈempti] 空的 3.clear[klɪə(r)] 清除 4.iterator 迭代器 学习目标: ...

  7. ArrayList源码解析--值得深读

    ArrayList源码解析 基于jdk1.8 ArrayList的定义 类注释 允许put null值,会自动扩容: size isEmpty.get.set.add等方法时间复杂度是O(1): 是非 ...

  8. 【源码解析】- ArrayList源码解析,绝对详细

    ArrayList源码解析 简介 ArrayList是Java集合框架中非常常用的一种数据结构.继承自AbstractList,实现了List接口.底层基于数组来实现动态容量大小的控制,允许null值 ...

  9. Java集合-ArrayList源码解析-JDK1.8

    ◆ ArrayList简介 ◆ ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAcc ...

随机推荐

  1. [Mybatis]Mybatis常用操作

    Mybatis是目前国内比较流行的ORM框架,特点是可以写灵活的SQL语句,非常适合中小企业的面向数据库开发. 本文总结自己开发过程中常用的Mybatis操作. 一.插入操作 主键自增插入单条 < ...

  2. php批量 下载图片

    <?php set_time_limit(0); $file = fopen("index.csv",'r');$temp = [];$i=0;$firstsku='';wh ...

  3. 不会用Java Future,我怀疑你泡茶没我快, 又是超长图文!!

    你有一个思想,我有一个思想,我们交换后,一个人就有两个思想 If you can NOT explain it simply, you do NOT understand it well enough ...

  4. Redis RDB 分析工具 rdbtools 说明

    背景 Redis是基于内存的KV数据库,内存作为存储介质,关注其内存的使用情况是一个重要指标,解析其内部的存储信息是给出优化方法和维护的最基本要求.解析内存有二种方法:第一个是通过scan遍历所有ke ...

  5. winserver2008R2安装配置JDK+tomcat之坑

    记一次winserver安装jdk+tomcat 准备软件: jdk-8u241-windows-x64 apache-tomcat-8.5.46-windows-x64 一.安装java 1.安装j ...

  6. IDEA 2020.1 查看内存使用情况

  7. 仔细想想SpringAOP也不难嘛,面试没有必要慌

    文章已托管到GitHub,大家可以去GitHub查看阅读,欢迎老板们前来Star! 搜索关注微信公众号 码出Offer 领取各种学习资料! LOGO SpringAOP 一.什么是AOP AOP(As ...

  8. 如何证明sleep不释放锁,而wait释放锁?

    wait 加锁示例 public class WaitDemo { private static Object locker = new Object(); public static void ma ...

  9. [日常摘要] -- ThreadLocal篇

    简介 ThreadLocal,即线程变量,是一个以ThreadLocal对象为键.任意对象为值的存储结构.这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个 ...

  10. 好用的npm模块记录

    标签: node node盛行的今天,前端开发已经离不开npm模块的使用,大名鼎鼎的如gulp,webpack等,此处不多说,除了它们有那么几个常用的npm模块是我喜欢并依赖它的,下面就是我平时工作中 ...