怎么查网站是否备案,代刷网站推广,512m内存做网站,云南省建设考试中心网站点击上方IT牧场#xff0c;选择设为星标技术干货每日送达#xff01;前言 JDK源码解析系列文章#xff0c;都是基于JDK8分析的#xff0c;虽然JDK14已经出来#xff0c;但是JDK8我还不会#xff0c;我…类图 实现了RandomAccess接口#xff0c;… 点击上方IT牧场选择设为星标技术干货每日送达前言 JDK源码解析系列文章都是基于JDK8分析的虽然JDK14已经出来但是JDK8我还不会我…类图 实现了RandomAccess接口可以随机访问实现了Cloneable接口可以克隆实现了Serializable接口可以序列化、反序列化实现了List接口是List的实现类之一实现了Collection接口是Java Collections Framework成员之一实现了Iterable接口可以使用for-each迭代属性 // 序列化版本UIDprivate static final long serialVersionUID 8683452581122892189L;/** * 默认的初始容量 */private static final int DEFAULT_CAPACITY 10;/** * 用于空实例的共享空数组实例 * new ArrayList(0); */private static final Object[] EMPTY_ELEMENTDATA {};/** * 用于提供默认大小的实例的共享空数组实例 * new ArrayList(); */private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA {};/** * 存储ArrayList元素的数组缓冲区 * ArrayList的容量是数组的长度 * * non-private to simplify nested class access */transient Object[] elementData;/** * ArrayList中元素的数量 */private int size;小朋友你四否有很多问号为什么空实例默认数组有的时候是EMPTY_ELEMENTDATA而又有的时候是DEFAULTCAPACITY_EMPTY_ELEMENTDATA为什么elementData要被transient修饰为什么elementData没有被private修饰难道正如注释所写的non-private to simplify nested class access带着问题我们继续往下看。构造方法 带初始容量的构造方法/** * 带一个初始容量参数的构造方法 * * param initialCapacity 初始容量 * throws 如果初始容量非法就抛出 * IllegalArgumentException */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); }}如果initialCapacity 就创建一个新的长度是initialCapacity的数组如果initialCapacity 0就使用EMPTY_ELEMENTDATA其他情况initialCapacity不合法抛出异常无参构造方法/** * 无参构造方法 将elementData 赋值为 * DEFAULTCAPACITY_EMPTY_ELEMENTDATA */public ArrayList() { this.elementData DEFAULTCAPACITY_EMPTY_ELEMENTDATA;}带一个集合参数的构造方法/** * 带一个集合参数的构造方法 * * param c 集合代表集合中的元素会被放到list中 * throws 如果集合为空抛出NullPointerException */public ArrayList(Collection extends E c) { elementData c.toArray(); // 如果 size ! 0 if ((size elementData.length) ! 0) { // c.toArray 可能不正确的不返回 Object[] // https://bugs.openjdk.java.net/browse/JDK-6260652 if (elementData.getClass() ! Object[].class) elementData Arrays.copyOf( elementData, size, Object[].class); } else { // size 0 // 将EMPTY_ELEMENTDATA 赋值给 elementData this.elementData EMPTY_ELEMENTDATA; }}使用将集合转换为数组的方法为了防止c.toArray()方法不正确的执行导致没有返回Object[]特殊做了处理如果数组大小等于0则使用 EMPTY_ELEMENTDATA那么问题来了什么情况下c.toArray()会不返回Object[]呢public static void main(String[] args) { List list new ArrayList(Arrays.asList(list));// class java.util.ArrayList System.out.println(list.getClass()); Object[] listArray list.toArray();// class [Ljava.lang.Object; System.out.println(listArray.getClass()); listArray[0] new Object(); System.out.println(); List asList Arrays.asList(asList);// class java.util.Arrays$ArrayList System.out.println(asList.getClass()); Object[] asListArray asList.toArray();// class [Ljava.lang.String; System.out.println(asListArray.getClass());// java.lang.ArrayStoreException asListArray[0] new Object();}我们通过这个例子可以看出来java.util.ArrayList.toArray()方法会返回Object[]没有问题。而java.util.Arrays的私有内部类ArrayList的toArray()方法可能不返回Object[]。为什么会这样我们看ArrayList的toArray()方法源码public Object[] toArray() { // ArrayLisy中 elementData是这样定义的 // transient Object[] elementData; return Arrays.copyOf(elementData, size);}使用了Arrays.copyOf()方法public static T[] copyOf(T[] original, int newLength) {// original.getClass() 是 class [Ljava.lang.Objectreturn (T[]) copyOf(original, newLength, original.getClass());}copyOf()的具体实现public static T[] copyOf(U[] original, int newLength, Class extends T[] newType) {SuppressWarnings(unchecked)/** * 如果newType是Object[] copy 数组 类型就是 Object * 否则就是 newType 类型 */ T[] copy ((Object)newType (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));return copy;}我们知道ArrayList中elementData就是Object[]类型所以ArrayList的toArray()方法必然会返回Object[]。我们再看一下java.util.Arrays的内部ArrayList源码(截取的部分源码)private static class ArrayListE extends AbstractListEimplements RandomAccess, java.io.Serializable { // 存储元素的数组 private final E[] a; ArrayList(E[] array) { // 直接把接收的数组 赋值 给 a a Objects.requireNonNull(array); } /** * obj 为空抛出异常 * 不为空 返回 obj */ public static T requireNonNull(T obj) {if (obj null)throw new NullPointerException();return obj; }Overridepublic Object[] toArray() {// 返回 a 的克隆对象return a.clone(); }}这是Arrays.asList()方法源码public static List asList(T... a) {return new ArrayList(a);}不难看出来java.util.Arrays的内部ArrayList的toArray()方法是构造方法接收什么类型的数组就返回什么类型的数组。所以在我们上面的例子中实际上返回的是String类型的数组再将其中的元素赋值成Object类型的自然报错。我们还是继续看ArrayList吧…插入方法 在列表最后添加指定元素/** * 在列表最后添加指定元素 * * param e 要添加的指定元素 * return true */public boolean add(E e) { // 增加 modCount ensureCapacityInternal(size 1); elementData[size] e; return true;}在父类AbstractList上定义了modCount 属性用于记录数组修改的次数。在指定位置添加指定元素/** * 在指定位置添加指定元素 * 如果指定位置已经有元素就将该元素和随后的元素移动到右面一位 * * param index 待插入元素的下标 * param element 待插入的元素 * throws 可能抛出 IndexOutOfBoundsException */public void add(int index, E element) { rangeCheckForAdd(index); // 增加 modCount ensureCapacityInternal(size 1); System.arraycopy(elementData, index, elementData, index 1, size - index); elementData[index] element; size;}插入方法调用的其他私有方法/** * 计算容量 */private static int calculateCapacity( Object[] elementData, int minCapacity) { if (elementData DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { 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);}扩容方法 /** * 数组可以分配的最大size * 一些虚拟机在数组中预留一些header words * 如果尝试分配更大的size可能导致OutOfMemoryError */private static final int MAX_ARRAY_SIZE Integer.MAX_VALUE - 8;/** * 增加容量至少保证比minCapacity大 * param minCapacity 期望的最小容量 */private void grow(int minCapacity) { // 有可能溢出的代码 int oldCapacity elementData.length; int newCapacity oldCapacity (oldCapacity 1); if (newCapacity - minCapacity 0) newCapacity minCapacity; 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);}/** * 最大容量返回 Integer.MAX_VALUE */private static int hugeCapacity(int minCapacity) { if (minCapacity 0) // overflow throw new OutOfMemoryError(); return (minCapacity MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;}通常情况新容量是原来容量的1.5倍如果原容量的1.5倍比minCapacity小那么就扩容到minCapacity特殊情况扩容到Integer.MAX_VALUE看完构造方法、添加方法、扩容方法之后上文第1个问题终于有了答案。原来new ArrayList()会将elementData 赋值为 DEFAULTCAPACITY_EMPTY_ELEMENTDATAnew ArrayList(0)会将elementData 赋值为 EMPTY_ELEMENTDATAEMPTY_ELEMENTDATA添加元素会扩容到容量为1而DEFAULTCAPACITY_EMPTY_ELEMENTDATA扩容之后容量为10。通过反射我们可以验证这一想法。如下public static void main(String[] args) { printDefaultCapacityList(); printEmptyCapacityList();}public static void printDefaultCapacityList() { ArrayList defaultCapacity new ArrayList(); System.out.println( default 初始化长度 getCapacity(defaultCapacity)); defaultCapacity.add(1); System.out.println( default add 之后 长度 getCapacity(defaultCapacity));}public static void printEmptyCapacityList() { ArrayList emptyCapacity new ArrayList(0); System.out.println( empty 初始化长度 getCapacity(emptyCapacity)); emptyCapacity.add(1); System.out.println( empty add 之后 长度 getCapacity(emptyCapacity));}public static int getCapacity(ArrayList arrayList) { Class arrayListClass ArrayList.class;try {// 获取 elementData 字段 Field field arrayListClass.getDeclaredField(elementData);// 开启访问权限 field.setAccessible(true);// 把示例传入get获取实例字段elementData的值 Object[] objects (Object[]) field.get(arrayList);//返回当前ArrayList实例的容量值return objects.length; } catch (Exception e) { e.printStackTrace();return -1; }}移除方法 移除指定下标元素方法/** * 移除列表中指定下标位置的元素 * 将所有的后续元素向左移动 * * param 要移除的指定下标 * return 返回被移除的元素 * throws 下标越界会抛出IndexOutOfBoundsException */public E remove(int index) { rangeCheck(index); modCount; E oldValue elementData(index); int numMoved size - index - 1; if (numMoved 0) System.arraycopy(elementData, index1, elementData, index, numMoved); // 将引用置空让GC回收 elementData[--size] null; return oldValue;}移除指定元素方法/** * 移除第一个在列表中出现的指定元素 * 如果存在移除返回true * 否则返回false * * param o 指定元素 */public boolean remove(Object o) { if (o null) { for (int index 0; index if (elementData[index] null) { fastRemove(index); return true; } } else { for (int index 0; 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, index1, elementData, index, numMoved); // 将引用置空让GC回收 elementData[--size] null;}查找方法 查找指定元素的所在位置/** * 返回指定元素第一次出现的下标 * 如果不存在该元素返回 -1 * 如果 o null 会特殊处理 */public int indexOf(Object o) { if (o null) { for (int i 0; i if (elementData[i]null) return i; } else { for (int i 0; i if (o.equals(elementData[i])) return i; } return -1;}查找指定位置的元素/** * 返回指定位置的元素 * * param index 指定元素的位置 * throws index越界会抛出IndexOutOfBoundsException */public E get(int index) { rangeCheck(index); return elementData(index);}该方法直接返回elementData数组指定下标的元素效率还是很高的。所以ArrayListfor循环遍历效率也是很高的。序列化方法 /** * 将ArrayLisy实例的状态保存到一个流里面 */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); // 按照顺序写入所有的元素 for (int i0; 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(); if (size 0) { // be like clone(), allocate array based upon size not capacity ensureCapacityInternal(size); Object[] a elementData; // Read in all elements in the proper order. for (int i0; i a[i] s.readObject(); } }}看完序列化反序列化方法我们终于又能回答开篇的第二个问题了。elementData之所以用transient修饰是因为JDK不想将整个elementData都序列化或者反序列化而只是将size和实际存储的元素序列化或反序列化从而节省空间和时间。创建子数组 public List subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList(this, 0, fromIndex, toIndex);}我们看一下简短版的SubListprivate class SubList extends AbstractListE implements RandomAccess { private final AbstractList parent;private final int parentOffset;private final int offset;int size; SubList(AbstractList 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; }// 省略代码...}SubList的set()方法是直接修改ArrayList中elementData数组的使用中应该注意SubList是没有实现Serializable接口的是不能序列化的迭代器 创建迭代器方法public Iterator iterator() { return new Itr();}Itr属性// 下一个要返回的元素的下标int cursor;// 最后一个要返回元素的下标 没有元素返回 -1int lastRet -1;// 期望的 modCountint expectedModCount modCount;Itr的hasNext() 方法public boolean hasNext() { return cursor ! size;}Itr的next()方法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];}final void checkForComodification() { if (modCount ! expectedModCount) throw new ConcurrentModificationException();}在迭代的时候会校验modCount是否等于expectedModCount不等于就会抛出著名的ConcurrentModificationException异常。什么时候会抛出ConcurrentModificationExceptionpublic static void main(String[] args) { ArrayList arrayList new ArrayList(); for (int i 0; i 10; i) { arrayList.add(i); } remove(arrayList); System.out.println(arrayList);}public static void remove(ArrayList list) { Iterator iterator list.iterator();while (iterator.hasNext()) { Integer number iterator.next();if (number % 2 0) {// 抛出ConcurrentModificationException异常 list.remove(number); } }}那怎么写才能不抛出ConcurrentModificationException很简单将list.remove(number);换成iterator.remove();即可。why请看Itr的remove()源码…Itr的remove()方法public void remove() { if (lastRet 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor lastRet; lastRet -1; // 移除之后将modCount 重新赋值给 expectedModCount expectedModCount modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); }}原因就是因为Itr的remove()方法移除之后将modCount重新赋值给 expectedModCount。这就是源码不管单线程还是多线程只要违反了规则就会抛异常。源码看的差不多了开篇的问题却还剩一个到底为什么elementData没有用private修饰呢我们知道的private修饰的变量内部类也是可以访问到的。难道注释中non-private to simplify nested class access的这句话有毛病当我们看表面看不到什么东西的时候不妨看一下底层。测试类代码一顿javac、javap之后(使用JDK8)再一顿javac、javap之后(使用JDK11)虽然字节码指令我还看不太懂但是我能品出来注释是没毛病的private修饰的确会影响内部类的访问。ArrayList类注释翻译 类注释还是要看的能给我们一个整体的了解这个类。我将ArrayList的类注释大概翻译整理了一下ArrayList是实现List接口的可自动扩容的数组。实现了所有的List操作允许所有的元素包括null值。ArrayList大致和Vector相同除了ArrayList是非同步的。size isEmpty get set iterator 和 listIterator 方法时间复杂度是O(1)常量时间。其他方法是O(n)线性时间。每一个ArrayList实例都有一个capacity(容量)。capacity是用于存储列表中元素的数组的大小。capacity至少和列表的大小一样大。如果多个线程同时访问ArrayList的实例并且至少一个线程会修改必须在外部保证ArrayList的同步。修改包括添加删除扩容等操作仅仅设置值不包括。这种场景可以用其他的一些封装好的同步的list。如果不存在这样的ObjectArrayList应该用Collections.synchronizedList包装起来最好在创建的时候就包装起来来保证同步访问。iterator()和listIterator(int)方法是fail-fast的如果在迭代器创建之后列表进行结构化修改迭代器会抛出ConcurrentModificationException。面对并发修改迭代器快速失败、清理而不是在未知的时间不确定的情况下冒险。请注意快速失败行为不能被保证。通常来讲不能同步进行的并发修改几乎不可能做任何保证。因此写依赖这个异常的程序的代码是错误的快速失败行为应该仅仅用于防止bug。总结 ArrayList底层的数据结构是数组ArrayList可以自动扩容不传初始容量或者初始容量是0都会初始化一个空数组但是如果添加元素会自动进行扩容所以创建ArrayList的时候给初始容量是必要的Arrays.asList()方法返回的是的Arrays内部的ArrayList用的时候需要注意subList()返回内部类不能序列化和ArrayList共用同一个数组迭代删除要用迭代器的remove方法或者可以用倒序的for循环ArrayList重写了序列化、反序列化方法避免序列化、反序列化全部数组浪费时间和空间elementData不使用private修饰可以简化内部类的访问源码系列第一篇一不小心就写的有点长。但是懵懂到深刻的过程还是挺耐人寻味的。文章中没有展开的点或者你有什么其他好奇的地方欢迎留言讨论。我们下篇文章再见…干货分享最近将个人学习笔记整理成册使用PDF分享。关注我回复如下代码即可获得百度盘地址无套路领取•001《Java并发与高并发解决方案》学习笔记•002《深入JVM内核——原理、诊断与优化》学习笔记•003《Java面试宝典》•004《Docker开源书》•005《Kubernetes开源书》•006《DDD速成(领域驱动设计速成)》•007全部•008加技术讨论群近期热文•彻底解决 GitHub 拉取代码网速慢的问题•基于 SpringBoot2 和 Netty 实现一个简易的RPC通信框架•一本彻底搞懂MySQL索引优化EXPLAIN百科全书•盘点 10 个代码重构的小技巧•性能测试如何定位瓶颈偶发超时看高手如何快速排查问题•震精Spring Boot内存泄露排查竟这么难想知道更多长按/扫码关注我吧↓↓↓技术讨论群喜欢就点个在看呗^_^