ArrayList源码阅读笔记
来源:互联网 发布:淘宝网的盈利模式答案 编辑:程序博客网 时间:2024/05/09 12:48
初始化
/**
* Default initial capacity.一个默认的初始化容量 10
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* The size of the ArrayList (the number of elements it contains).
* 存的是元素的个数
* @serial
*/
private int size;
/** 初始化
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative初始值为负数报此异常
*/
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);
}
}
注意 collection.toArray(),
注意 list1的类是ArrayList,不是ArrayList:
注意 数组getClass和普通类getClass的区别:
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
// replace with empty array.
this.elementData = EMPTY_ELEMENTDATA;
}
}
增加--add(E e)
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
普通插入,调用ensureCapacityInternal保证有size++的位置以储存e
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
如果elementDate是用(0)初始化建立的,那么把minCapacity和DEFAULT_CAPACITY中的最大值当做ensureExplicitCapacity的参数保证element数组有空间
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
如果element数组长度小于期望的容量最小值,那么就grow(minCapacity)增长到期望的最小值。
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);
}
Ps:oldCapacity>>1相当于除以2
采用的策略是用 当前长度 + 当前长度/2 = newCapacity,如果newCapacity还是达不到期望的最小值,那么就newCapacity=minCapacity,导致newCapacity一定是大于等于期望的最小容量的,为原先的1.5倍。
如果newCapacity 大于规定的数组的最大值
PS:MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8
那么就比较minCapacity与数组最大值的大小,如果大于,就让newCapacity = Integer.MAX_VALUE,反之则=MAX_ARRAY_SIZE。
最后调用Arrays.copyOf(elementData, newCapacity);加长数组。
PS:Arrays.copyOf(...)的源码:
public static <T> T[] copyOf(T[] original, int newLength) {
return (T[]) copyOf(original, newLength, original.getClass());
}
看得出这是一个用于增大数组长度的方法。
该扩容方法可能会导致有的空间没有存储元素。
增加--add(int index,E element)
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
按照index插入
先检查是否出界,如果出界就抛出我们很熟悉的IndexOutOfBoundsException异常。
然后ensureCapacityInternal确保有位置来存放e,同上。
然后把包括index之后的元素复制到往后一位,导致了element[index]空了出来,然后element[index] = element;赋值。
size++把长度加一
PS:System.arraycopy(...)的源码:
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
是一个本地方法,使用的是内存复制,省去了大量的数组寻址访问等时间,便于大数组的copy。
批量增加addAll(Collection<? extends E> c)
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;
}
把c toArray后
跟上面差不多,一个套路。
批量增加addAll(int index, Collection<? extends E> c)
public boolean addAll(int index, Collection<? extends E> c) {
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;
}
就有一点不同的,如果size == index(相当于在源list紧跟其后添加c),就不用移动了。
PS:不可能出现size < index,此种情况在rangeCheckForAdd(index)处早已屏蔽。
PS:arrayList是可以add(null)的。打印的时候打印[null]。
修改 E set(int index,E element)
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
代码阅读简单,执行又快速。这也是查找修改用数组的好处。
删除 boolean remove(Object 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])) {
fastRemove(index);
return true;
}
}
return false;
}
如果找不到o,只会返回false,并不会报异常。
并且只会remove掉第一个跟o.equals == true的对象。
Q&A:
Q:为什么空与非空的删除要分开呢?
A:因为要调用o.equals来匹配相应的object,null会报空指针异常。
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让GC对付最后一个值
}
以0开始的索引,index+1=size代表这个索引是最后一个值的
所以,如果size = index+1,代表要删除最后一个,那么直接elementData[--size] = null就好了;如果不是最后一个,那么就让[index+1位置的元素]到[最后的元素]复制到[index]到[最后-1]的位置,然后elementData[--size] = null把最后一个值设置为空,删除完毕。
注意:
--size,--size是先执行size=size-1,然后再使用size的值 这时的size值就是表达式--size的值。
size-- 是先使用size的值作为表达式size--的值,然后,执行size=size-1操作。
这种删除是通过遍历实现的。
删除E 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);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
首先检查是否越界。
后面就跟fastRemove差不多了。
批量删除boolean removeAll(Collection<?> c)
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
return batchRemove(c, false);
}
首先检查是否为空。
然后批量删除
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
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++)
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;
}
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;
}
批量删除。
假设list = [1,2,3,4,5,6],c = [2,4,5]
首先建立一个final的数组引用指向当前引用,遍历elementData数组,如果发现c中没有elementData[r]元素(当前elementData[r]元素为非删除元素),那么就把它按照elementData[w++]的顺序从elementData[0]开始覆盖,覆盖完毕后list=[1,3,6,4,5,6],然后如果r!=size(意味着try有异常抛出,r没有涨到size的值),使用System.arraycopy将出错时r位置后面的(包括r)值 复制到出错位置w位置的后面(包括w),相当于手动复制过来。
If(w!=size)是用来把w后面的值置空的。
PS:size -= i - 3 = size - (i - 3)
modCount
此处为转载
作者:特立独行的猪手
链接:https://juejin.im/post/58edf7cbb123db43cc36f47f
来源:掘金
在add、remove过程中,经常发现会有modCount++或者modCount--操作。这里来看下modCount是个啥玩意。
modCount变量是在AbstractList中定义的。
protected transient int modCount = 0;
modCount是一个int型变量,用来记录ArrayList结构变化的次数。
modCount起作用的地方是在使用iterator的时候。ArrayList的iterator方法。
public Iterator<E> iterator() {
return new Itr();
}
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;
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();
}
}
iterator方法会返回私有内部类Itr的一个实例。这里可以看到Itr类中很多方法,都会调用checkForComodification方法。来检查modCount是够等于expectedModCount。如果发现modCount != expectedModCount将会抛出ConcurrentModificationException异常。
这里写一个小例子来验证体会下modCount的作用。简单介绍一下这个小例子:准备两个线程t1、t2,两个线程对同一个ArrayList进行操作,t1线程将循环向ArrayList中添加元素,t2线程将把ArrayList元素读出来。
Test类:
public class Test {
List<String> list = new ArrayList<String>();
public Test() {
}
public void add() {
for (int i = 0; i < 10000; i++) {
list.add(String.valueOf(i));
}
}
public void read() {
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
t1线程:
public class Test1Thread implements Runnable {
private Test test;
public Test1Thread(Test test) {
this.test = test;
}
public void run() {
test.add();
}
t2线程:
public class Test2Thread implements Runnable {
private Test test;
public Test2Thread(Test test) {
this.test = test;
}
public void run() {
test.read();
}
}
main类
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
Thread t1 = new Thread(new Test1Thread(test));
Thread t2 = new Thread(new Test2Thread(test));
t1.start();
t2.start();
}
执行这个mian类就会发现程序将抛出一个ConcurrentModificationException异常。
由异常可以发现抛出异常点正处于在调用next方法的checkForComodification方法出现了异常。这里也就出现上文描述的modCount != expectedModCount的情况,原因是t2线程在读数据的时候,t1线程还在不断的添加元素。
这里modCount的作用也就显而易见了,用modCount来规避多线程中并发的问题。由此也可以看出ArrayList是非线程安全的类。
- ArrayList源码阅读笔记
- ArrayList源码阅读笔记
- ArrayList源码阅读笔记
- ArrayList和LinkedList源码阅读笔记
- ArrayList源码阅读
- ArrayList源码阅读
- java8 ArrayList源码阅读
- 源码阅读之ArrayList
- ArrayList源码阅读
- ArrayList源码阅读
- Java源码阅读-ArrayList
- JDK源码阅读-ArrayList
- java8 ArrayList源码阅读
- Java源码阅读-ArrayList
- java8 ArrayList源码阅读
- JAVA 集合类(java.util)源码阅读笔记------ArrayList
- ArrayList的阅读笔记
- ArrayList的阅读笔记
- 什么是webhook
- typeof和instanceof的作用和区别
- 本地通知的使用
- Oracle-intersect-minus等日常函数
- Jmeter微型压力测试
- ArrayList源码阅读笔记
- RTTI
- UE4 动态修改材质 控制颜色和贴图
- java 接口中用private、protected、public、default和status修饰方法和变量
- jstl中c:if的使用
- tensorflow中tf.contrib.learn.preprocessing.VocabularyProcessor理解
- mui初级入门教程(五)— 聊聊即时通讯(IM),基于环信 web im SDK
- ArcGIS矢量图的配准
- c#通过app.manifest使程序以管理员身份运行