ArrayList 解析
来源:互联网 发布:知识库管理系统 java 编辑:程序博客网 时间:2024/06/07 17:40
ArrayList,在我们写 Android 的时候经常配合 ListView ,RecyclerView,ViewPager 使用。那么今天就来解析一下它。
创建
构造函数
transient Object[] elementData; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = new Object[0]; public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; }
我们可以发现,默认的构造函数中就只有一句话。但也表明了自己的真实身份,就是一个控制一个 Object 数组的类。
但是,为什么还要给 elementData 赋值一个静态的Object数组呢?为什么不直接 new 一个Object 数组呢?我们知道,static final 修饰的常量存在于内存中的方法区,且有且仅有一个实例,所以当我们再次创建一个 ArrayList 的对象时,elementData 指向的还是同一个 DEFAULTCAPACITY_EMPTY_ELEMENTDATA。
由图可以看出,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 在这里充当的是一个缓冲区的作用,避免创建多个无用的数组。666
扩容
在上一节中我们可以发现 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的长度是 0 的,那还怎么存储数据呢?而且,难道程序中所有的数据都是存储在同一个静态数组中吗?带着疑问,我们来看一下 add()方法:
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
我们可以看到第二句很明显就是插入数据并使长度自增的操作,那么第一句的方法 ensureCapacityInternal() 应该就是扩容的操作了。
private void ensureCapacityInternal(int minCapacity) { if (elementData == EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); }
我们首先对 elementData 进行了判断,若相等则比较 10 与 var1 的大小,接着执行下一个方法。看到这里,我们可以猜测,ArrayList 在最初应该是直接扩增到了 10 。但是,为什么要加一个判断呢,是因为 elementData 的指向会改变吗?我们继续看下一个方法:
private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
该方法,先使 modCount 自增,表示修改次数增加。然后判断当前传入的值是否比当前数组长度大,若是则执行 grow()方法
private void grow(int minCapacity) { // overflow-conscious code 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); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
grow()方法中,前面的都是对数组大小做一些限制,不多赘述。最后一行:
this.elementData = Arrays.copyOf(this.elementData, newCapacity);
这一行,应该就是做扩容操作了,Arrays.copyOf()的参数列表中,分别是,elementData 和 newCapacity。我们回过头:
int newCapacity = oldCapacity + (oldCapacity >> 1);
结合前面的猜想,每次扩容应该都是扩大了1.5倍的。继续深入:
public static <T> T[] copyOf(T[] original, int newLength) { return (T[]) copyOf(original, newLength, original.getClass()); } public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> 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; }
在这里,利用传入的数据创建了一个新的数组,然后把原本数组的数据复制到了新的数组对象中并返回。现在,我们不仅证实了前面的猜想,疑问也都解决了。我们试着继续深入
public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
看到 native ,所以应该是用到了 c 或 c++ 的方法。但是源码看不到了 = =;
System.arraycopy()
通过查阅资料,我发现其实 System.arraycopy() 内部实现是用 c 语言中的 memmove()/memcpy() 函数来实现的。它们是一个内存操作函数,通过直接移动指针来实现数组的复制。更多内容,我也还不是很清楚,希望高手指点。
仰视源码,实现memmove
c语言模拟实现memmove
扩容总结
- ArrayList 对象创建后,内部数组的初始长度未0
- 首次添加数据时,会扩容到10
- 每次扩容,都会扩大原来的1.5倍
- 每次扩容,实际都是新创建一个数组然后再把数据复制到新数组中然后返回
- size 表示的是逻辑容量而不是内部数组的实际容量
常用操作解析
get
public E get(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); return (E) elementData[index]; }
我们清楚,数组只要知道下标就能直接获取数据。ArrayList 也是如此,先检查是否越界,然后直接通过下标获取数据。
set
public E set(int index, E element) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); E oldValue = (E) elementData[index]; elementData[index] = element; return oldValue; }
同样修改也一样,时间复杂度与获取一样 同为 O(1)
remove
public E remove(int index) { if (index >= size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); modCount++; E oldValue = (E) 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; }
算出需要移动的距离后,直接利用 System.arraycopy 把后面的参数移动到前面,最后在制空最后一位。
add && addAll
public void add(int index, E element) { if (index > size || index < 0) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); ensureCapacityInternal(size + 1); // Increments modCount!! System.arraycopy(elementData, index, elementData, index + 1, size - index); elementData[index] = element; size++; } public boolean addAll(Collection<? extends E> var1) { Object[] var2 = var1.toArray(); int var3 = var2.length; this.ensureCapacityInternal(this.size + var3); System.arraycopy(var2, 0, this.elementData, this.size, var3); this.size += var3; return var3 != 0; }
两个方法都很相似,扩容后,利用 System.arraycopy 进行复制。
clear
public void clear() { modCount++; // clear to let GC do its work for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
简单粗暴,直接全部置空。
SubList
在查看源码的时候还发现了 ArrayList 中,还存在一个内部类 SubList,他与 ArrayList 一样都是继承 Abstractlist 。同时也包含get , set , add ,remove 等方法。
public List<E> subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList(this, 0, fromIndex, 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 void add(int index, E e) { if (index < 0 || index > this.size) throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); if (ArrayList.this.modCount != this.modCount) throw new ConcurrentModificationException(); parent.add(parentOffset + index, e); this.modCount = parent.modCount; this.size++; } . . . . . . }
我们发现,parent 即是原始的 list 的引用,在其他对数组操作的方法中,最后也都是调用了 parent 中的方法。即,对 subList 的操作其实就是对 ArrayList 的操作,会改变 ArrayList 的内容。
SubList 的妙用
list.subList(100,200).clear();
直接删除一段数据(100-199),而不用 for 循环。
SubList 的缺陷
创建 subList 后,如果修改了 ArrayList ,再次调用 SubList 的时候,会发生异常
List<String> stringList = new ArrayList<>(); stringList.add("1"); stringList.add("2"); stringList.add("3"); stringList.add("4"); stringList.add("5"); List<String> subList = stringList.subList(0,stringList.size()); stringList.add("6"); System.out.print("stringList = "+stringList.size()); System.out.print("subList = "+subList.size());
异常
Exception in thread "main" java.util.ConcurrentModificationExceptionstringList = 6 at java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1231) at java.util.ArrayList$SubList.size(ArrayList.java:1040) at com.jinjunhuang.arraylistdemo.Demo.main(Demo.java:32) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
至于原因,我们来看一下 size()方法
public int size() { if (ArrayList.this.modCount != this.modCount) throw new ConcurrentModificationException(); return this.size; }
当修改了 ArrayList 后,ArrayList.modeCount 会改变,而 sublist 的 modCount 只在构造函数中获取 ArrayList.modeCount 的值,而不会动态改变,所以,在执行 sublist 的方法时,两者不相等,就会抛出错误。这种机制也叫 fail-fast。
fail-fast 机制
是Java集合的一种检测错误机制。当多个线程对集合进行机构上的改变的操作时,有可能会产生 fail-fast 机制。这种机制,不会保证一定会出现错误,但会进最大努力去抛出 ConcurrentModificationException 异常。迭代器中也存在这种机制。获取更多信息推荐阅读
序列化
在谈到构造函数的时候,我们会发现,ArrayList 中的 Object 数组中用了 transient 来修饰,表示不用序列化,但是我们的数据都是放在数组中的,数据不序列化,我们持久化了有什么用呢?这是因为,elementDa 不一定是满的,没有用上的空间我们并不想再耗时间去序列化。但有存储数据的空间我们还是要序列化的,因此,Java 中重写了 writeObject 方法,以实现局部序列化。
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 其实
- 就是维护一个一维数组的对象
- 每次创建 ArrayList 对象,内部的数组引用都会指向一个长度为0的数组常量
- 扩容就是新建一个更大的数组对象,然后利用 System.arraycopy() 复制旧数组中的数据
- 首次扩容会扩张到 10
- 每次扩容都会扩张到原来的 1.5倍
- ArrayList 和数组一样,获取数据很快速,但是插入删除会很慢
- 插入删除操作也是利用 System.arraycopy()来实现数据的移动的
- System.arraycopy()是采用 c 语言的 memcpy/memmove 实现的
- ArrayList 中 SubList 是 ArrayList 中的一个视图,修改 SubList ,ArrayList 中的数据也会改变
- 若创建了 SubList 后修改了 ArrayList ,再次调用前面创建的 SubList 的实例,会触发 fail-fast机制的发生
- ArrayList 中重写了 writeObject 方法以优化数组的序列化过程
- ArrayList解析
- Arraylist解析
- ArrayList 解析
- ArrayList解析
- ArrayList 源码解析
- ArrayList源码解析
- android源码解析 -- ArrayList
- ArrayList源码解析
- ArrayList源码解析
- 深入解析ArrayList
- ArrayList,hashSet解析
- ArrayList源码解析
- ArrayList源码解析
- ArrayList LinkedList源码解析
- ArrayList 源码解析
- ArrayList集合源码解析
- ArrayList源码解析
- ArrayList类源码解析
- 网银支付(接入的是连连支付)
- 【区域填充】中的扫描线填充算法,活性边表AET,新边表NET
- linux安装qt 5.8
- vue生命周期
- JS知识整理(持续更新)
- ArrayList 解析
- SME Model
- 【JavaScript学习】BOM:location对象
- QNX实时操作系统学习笔记之嵌入式系统(二)
- Jupyter Notebook 快速入门(1)
- Android去掉标题栏的几种方法
- MySQL与Oracle的区别
- iOS iPhone X 适配
- Publish ROS message from terminal