ArrayList源码笔记 ---- JDK1.8
结构
-
父类和接口
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable //分析: 继承了AbstractList,可以继承一些通用的方法。 实现了RandomAccess 随机访问接口 是一个空的接口 和数组一样可以通过下标访问 实现了Cloneable 表示可以使用Object.clone方法 实现了Serializable 序列化 什么是序列化?简单的说,就是能够从类变成字节流传输,然后还能从字节流变成原来的类 // 而ElementDate[]数组,缺使用了transient修饰,这样不会被序列化。为什么呢? 对于ArrayList来说,是一个可扩容的数组。比如默认10个容量,现在放入了第11个,则会容量会扩容到15. 这时候序列化就把那四个闲置的位置也序列化了,空间上的浪费。 所以ArrayList直接实现了序列化的方法,writeObject()和readObject(),在这里面,序列化的是以当前数组真实大小来序列化的 增加元素是先去判断当前增加临界点 也就是 11 扩容 先扩容 再把第11个加进去
//问题1: 可以看到继承了父类AbstractList也实现了List接口同样ArrayList也实现了List接口,这是为什么呢? 接口中的某些方法可以被抽取出来作为通用的方法,所有先让一个抽象类实现接口中的某些通用方法,然后ArrayList实现自己的特有方法,这样使代码更加简洁 //问题2: 为什么在继承了实现了List接口的AbstractList父类后,还去实现List接口? 继承了父类后就相当于也要实现List,在实现List是不是多此一举呢? https://stackoverflow.com/questions/2165204/why-does-linkedhashsete-extend-hashsete-and-implement-sete 在这里有人提问了这个问题,说是作者 Josh Bloch 一开始以为可能是有用的,后来确实没用。但是也不影响什么就没撤销 回答原文:I've asked Josh Bloch, and he informs me that it was a mistake. He used to think, long ago, that there was some value in it, but he since "saw the light". Clearly JDK maintainers haven't considered this to be worth backing out later.
-
类的属性
private static final long serialVersionUID = 8683452581122892189L; /** * Default initial capacity. 默认初始容量。大小为10 */ private static final int DEFAULT_CAPACITY = 10; /** * Shared empty array instance used for empty instances. 用于空实例的共享空数组实例 * 如果是这样的 ArrayList a =new ArrayList(0); 则返回该空数组 可以debug跟踪一下 */ private static final Object[] EMPTY_ELEMENTDATA = {}; /** * Shared empty array instance used for default sized empty instances. We * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when * first element is added. * 如果是这样的 ArrayList a =new ArrayList(); 无参数则返回该空数组 * 和上面的区别 以了解添加第一个元素时要膨胀多少 ???看下一个属性 */ private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; /** * The array buffer into which the elements of the ArrayList are stored. * The capacity of the ArrayList is the length of this array buffer. Any * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA * will be expanded to DEFAULT_CAPACITY when the first element is added. * 存储ArrayList的元素,ArrayList长度就是该数组大小 * 如果数组是elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA的空ArrayList第一次添加元素直接扩容为DEFAULT_CAPACITY */ transient Object[] elementData; // non-private to simplify nested class access 非私有以简化嵌套类访问 /** * The size of the ArrayList (the number of elements it contains). * ArrayList 的大小(它包含的元素数)。 * @serial */ private int size; //要分配的数组的最大大小。 一些 VM 在数组中保留一些头字。 尝试分配更大的数组可能会导致 OutOfMemoryError:请求的数组大小超出 VM 限 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE(0x7fffffff 2^31-1) - 8;
-
类的构造函数 初始化elementData数组
public ArrayList(int initialCapacity) if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) {//当为指定容量为0时 使用EMPTY_ELEMENTDATA空数组 this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } //构造一个初始容量为 10 的空列表。当添加第一个元素后就会扩容到初始值10 第一次调用add方法时 public ArrayList() {//无参直接使用DEFAULTCAPACITY_EMPTY_ELEMENTDATA空数组 this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } //这个就意味着实现了Collection的类都可以转化为ArrayList Collection<? extends E>:限定了其集合里面的元素只能是E及E的子类 //<? super E> 限定了其集合里面的元素只能是E或者E的父类 public ArrayList(Collection<? extends E> c) { elementData = c.toArray();//转化为Collection中是返回Object数组,但是每个类的实现方法都不一样 if ((size = elementData.length) != 0) {//初始化size 且判断不等于0要不然就直接返回空数组和指定容量为0时一样了 // c.toArray might (incorrectly) not return Object[] (see 6260652) //每个集合的toarray()的实现方法不一样,所以需要判断一下,如果不是Object[].class类型,那么就需要使用ArrayList中的方法去改造一下。 if (elementData.getClass() != Object[].class) //copyOf(要复制的数组,要返回的副本的长度,要返回的副本的类) elementData = Arrays.copyOf(elementData, size, Object[].class); } else { // replace with empty array. this.elementData = EMPTY_ELEMENTDATA; } }
常用方法 --增删改查
-
trimToSize() 将容器容量修改到当前size大小。
//将此ArrayList实例的容量修剪为列表的当前大小。 // 应用程序可以使用此操作来最小化ArrayList实例的存储空间。 public void trimToSize() { modCount++; if (size < elementData.length) {//这个是容器的容量,可以debug试一下,打断点到这里 光标浮在length在执行完之后就会有值的变化 elementData = (size == 0) ? EMPTY_ELEMENTDATA : Arrays.copyOf(elementData, size);//在这里修改 } }
-
ensureCapacity(int minCapacity)
//对于这个方法,如果是由空参构造出的实例对象,且没经过add minCapacity小与默认容量则不会扩容 public void ensureCapacity(int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)//阻止了空参构造生成的空ArrayList的比默认容量小的扩容,因为没有必要,第一次add时就会进行扩容。 // 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; } //封装了一下 public boolean isEmpty() { return size == 0; }
-
包含 contains 如果有则返回true 也是封装了一下
public boolean contains(Object o) { return indexOf(o) >= 0; }
//返回第一次出现元素的下标,没找到则返回-1,同时也可以找null值。 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; } //同样还有返回最后一次出现元素下标的方法 倒着找,和数组一样的方式 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; } ```
-
clone()方法,很多类都实现了这个方法,返回一个内容一样但是不是一个对象。是浅拷贝
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); } } //测试 ArrayList cd = new ArrayList(); cd.add("asdf"); cd.add("asd"); ArrayList cb = (ArrayList) cd.clone(); System.out.println(cd == cb);false System.out.println(cb.get(0) == cd.get(0));true 深拷贝概念 深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。 当对象和它所引用的对象一起拷贝时即发生深拷贝。 深拷贝相比于浅拷贝速度较慢并且花销较大。 所以ArrayList是浅拷贝
6.toArray
//用于将List转为数组,返回一个大小为size的全新数组。
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
public <T> T[] toArray(T[] a) {
if (a.length < size)//如果a的大小不够,则创建一个大小为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);//刚好够,则拷贝内容给到a
if (a.length > size)//大于则给后面内容标记为null,GC
a[size] = null;
return a;
}
-
get方法和数组一样的方式获取内容
public E get(int index) { rangeCheck(index); return elementData(index); }
E elementData(int index) { return (E) elementData[index];}
-
修改指定位置的元素
public E set(int index, E element) { rangeCheck(index); E oldValue = elementData(index); elementData[index] = element; return oldValue; } ```
-
添加元素 将指定的元素附加到此列表的末尾。
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! add操作涉及到容器的扩容 elementData[size++] = e;//正常操作,多线程时不安全 return true; }
private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//如果是通过空参构造生成 minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);//最小都会扩容至默认大小 } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++;//使用迭代器遍历元素时,添加元素,扩容会让迭代器失效 // overflow-conscious code if (minCapacity - elementData.length > 0)//防止是参数为0的构造参数生成的对象,扩容时minCapacity小于现有元素大小 grow(minCapacity);//扩容操作 }
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length;//旧容量 int newCapacity = oldCapacity + (oldCapacity >> 1);//就是扩容1.5倍左右 oldCapacity >> 1 二进制左移一位,相当于十进制除于2 //如果以旧容量扩容后的值小于期望值则按照期望值扩容 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //如果扩容后的值 > jvm 所能分配的数组的最大值,那么就用 Integer 的最大值 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: minCapacity 通常接近 size,所以这是一个胜利: 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; //MAX_ARRAY_SIZE Integer.MAX_VALUE 前者比后者小8 }
public void add(int index, E element) //在此列表中的指定位置插入指定元素
总结:通过源代码可以看出,扩容会扩容1.5倍然后生成一个新的数组将内容拷贝过去,add时可以用null值
扩容本质就是通过elementData = Arrays.copyOf(elementData, newCapacity); 我们通过 System.arraycopy 方法进行拷贝
/** * @param src 被拷贝的数组 * @param srcPos 从数组那里开始 * @param dest 目标数组 * @param destPos 从目标数组那个索引位置开始拷贝 * @param length 拷贝的长度 * 此方法是没有返回值的,通过 dest 的引用进行传值 */ public static native void arraycopy(Object src, int srcPos, Object dest, int destPos, int length);
添加全部到List
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; }
-
删除元素 remove(int index)删除指定位置,
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);//把数组index之后的元素向前移1。 elementData[--size] = null; // clear to let GC do its work return oldValue; }
通过值来删除元素
//允许null值就多了好多代码,有多个o只会删除第一个 public boolean remove(Object o) { if (o == null) { for (int index = 0; index < size; index++) if (elementData[index] == null) { fastRemove(index); return true; } } else { for (int index = 0; index < size; index++) if (o.equals(elementData[index])) {//这里使用了equals如果是自己写的类则需要重写equals方法 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 void clear() { modCount++; // clear to let GC do its work 如果一个引用是null则会有垃圾回收器来清理 for (int i = 0; i < size; i++) elementData[i] = null; size = 0; }
从此列表中删除包含在指定集合中的所有元素
public boolean removeAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, false); }
仅保留此列表中包含在指定集合中的元素。 换句话说,从该列表中删除所有未包含在指定集合中的元素。
public boolean retainAll(Collection<?> c) { Objects.requireNonNull(c); return batchRemove(c, true); }
//removeAll方法 则complement为false c中元素是要被删除的 private boolean batchRemove(Collection<?> c, boolean complement) { final Object[] elementData = this.elementData; int r = 0, w = 0; boolean modified = false; try { for (; r < size; r++) //假如当前list中元素为 a b c d , c中元素为 b c if (c.contains(elementData[r]) == complement)//第一个取出的是 a 在c中未包含,则为false 总表达式就为true elementData[w++] = elementData[r];//将a放入第一个元素 //第二次为 b 在c中可以找到 则返回true 总表达式就为false 跳过 //这样下来 w 之前都为不删的元素。 } finally { // Preserve behavioral compatibility with AbstractCollection, // even if c.contains() throws. if (r != size) {//如果r!=size说明上面抛出了异常。 //把r之后的内容拷贝到w之后,r之后的内容还没用被判断 System.arraycopy(elementData, r, elementData, w, size - r); w += size - r; } //w!=size说明有被删除的元素 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; }
迭代器
此类的iterator和listIterator方法返回的iterator是快速失败的:如果在创建迭代器后的任何时间以任何方式修改列表结构,除了通过迭代器自己的remove或add方法,迭代器将抛出ConcurrentModificationException 。 因此,面对并发修改,迭代器快速而干净地失败,而不是在未来不确定的时间冒着任意、非确定性行为的风险。
就是fail-fast 快速失败。多线程下一般错误都是不容易出现但是有可能出现,如果能快速失败就不用担心之后的使用中突然出错。通过modCount来快速失败
不过一般多线程下也不用这个ArrayList类,有JUC包下的类使用,更加可靠
之前没看源码之前就只知道有iterator()去遍历元素,看了之后发现还有个ListIterator
//迭代器遍历时,不能增加,删除,扩容操作可以修改。
public Iterator<E> iterator() {
return new Itr();
}
//此类实现了Iterator接口,该接口一共就四个方法,我只用过前三个
//hasNext()判断还好没有下一个
//next取出下一个值
//remove删除一个
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;//fail-fast机制就是靠这个
public boolean hasNext() {
return cursor != size;//下一个索引小于size
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();//检查modCount有没有被修改
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();
}
}
//这个方法对集合中剩余的元素进行操作,直到元素完毕或者抛出异常
//感觉不好用,Consumer函数式接口支持Lambda
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;//调用该方法并未重新初始化此类,也就是说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();
}
}
ListIterator 这个类只是在List或者它的实现类下可以使用或者说有,提供了更多方法,add,set还可以倒序遍历 hasPrevious previous,ListIterator可以定位当前的索引位置,nextIndex()和previousIndex()可以实现。
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();
}
}
}
内部类SubList SubList到底怎么转化为ArrayList?
foreach方法
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();
}
}
//测试
ArrayList a =new ArrayList();
a.add("222");
a.add("333");
a.forEach(b-> System.out.println(b));
System.out.println(a.toString());
问题
-
ArrayList的扩容机制?
无参构造,第一次add时会初始化且扩容 首先无论你现在期望容量是多少,第一次都会以默认容量10扩容。 每一次都会去判断该不该扩容,判断依据是 期望容量 - 当前容量 > 0 当是第一次时,期望容量是默认容量 10 - 当前容量 0 是个空的 。必然大于0 则执行grow方法,扩容。 扩容进入首先 计算新数组大小 新容量 = 旧容量 + 旧容量 >> 1; 然后比较期望容量和新容量哪个大一点,第一次扩容肯定是期望大,所以使用期望容量10。 之后再有一些判断,判断不能大于Interger的最大值等。 最后就是扩容了 , 使用Arrays.copyof 将旧数组复制到新数组。里面还是使用的是System.arraycopy 超过容量扩容 每一次add都会判断是不是要去扩容。判断的依据就是 期望容量(当前数组大小+1) - 当前数组大小(10 也就是当前容量) > 0 如果是默认大小初始化 10 那么当第11个元素add时,会扩容。 这个时候grow中用的就不会是期望容量了,而是新容量 = 旧容量 + 旧容量 >> 1; 当前为15 10+10>>1(5) 最后扩容
-
ArrayList怎么排序?
1.使用Collections.sort(List<String>)方法,此方法有一个重载方法Collections.sort(List<A>,new Comparator接口的匿名内部类,需要实现compare()方法)。 第一个一个参数sort,需要List中对象类型实现Comparable接口并实现其方法compareTo(). 第二个二个参数的方法,适用于自定义类型或者说所有没有实现内部比较器Comparator的CompareTo()的类。需要其实现外部比较器 Comparator的compare()方法。 2. 使用ArrayList自己内部的方法,arrayList.sort(Comparator<? super E> c) 这个方法需要一个外部比较器,不过如果是null,则使用的是内部比较强Comparable 必须arrayList存储的对象实现该接口的compareTo方法。 3. 且1中实现调用的是2的sort 也就是说俩排序都是list.sort(),而list.sort()中调用的则是Arrays类的sort(),Arrays类有一堆sort排序。
-
ArrayList怎么去重?
1. 将ArrayList整个放到HashSet中,利用set的特性,没有重复的值。不过无法保证取出时是原来的顺序 2. 利用LinkedHashSet 底层是LinkedHashMap,保证存入和取出顺序一致,并且无重复值 3. 自己for循环
-
ArrayList和LinkedList区别?
ArrayList 底层是数组 LinkedList 底层是双向链表 在之后就是数组和链表的性能问题了 但是也不能直接说数组查找修改元素快,但是增加删除元素慢。LinkedList增删效率比较高且不需要初始化容量 具体分析 增加元素,可以插入头,尾,中间 ArrayList 有两个增加元素的方法,add(E) 直接插到尾部 add(int,E)指定位置插入 LinkedList 有挺多 addFirst(E)添加到头部 addLast(E)添加到尾部 add(E)添加到尾部 和addLast()没啥区别,一个无返回,一个默认返回true add(int,E)指定位置添加 如果指定位置是尾部,则直接添加到尾部(调用linkLast()),如果不是则调用linkBefore() 这些方法都只是对内部方法的封装,linkFirst(),linkLast(),linkBefore()。 对于插入到尾部,ArrayList直接就添加了,但是若是扩容就会耗时长一点。LinkedList插入尾部需要改变指向,把节点链接上去,所以ArrayList会快一点。 对于插入中间,ArrayList 是个数组需要将这个位置之后的元素都向后移一位,实现是使用System.arraycopy(),将旧数组复制到新数组。就是将index开始的向后移一位,空出index位。LinkedList则是需要遍历找到该index位置上的节点,改变指向。遍历找节点是有优化的简易的二分,首先,将链表分成前一半,后一半,这样只需要判断index是>mid还是<mid,只需要遍历一半。这里ArrayList快一点 对于插到头部,ArrayList的性能会很差了,要将整体移后一位,空出第一个位置。而LinkedList,则直接改变指向就行,毕竟是双向链表。 删除就都一样了。 遍历 ArrayList遍历,for,foreach,迭代器都行,差不了太多 LinkedList for遍历就会差很多,for遍历 通过get(i)查找,get(i)会调用node(i)去查找i位置的元素,虽然会是前后二分遍历。但是每一次for都会这样遍历一次去查找元素。而迭代器只node一次,之后通过hasNext()和next()