JAVA语言:源码分析之ArrayList
龚超 2018-07-25 来源 : 阅读 747 评论 0

摘要:本文主要向大家介绍了JAVA语言的源码分析之ArrayList,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。

本文主要向大家介绍了JAVA语言的源码分析之ArrayList,通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助。

ArrayList 是我们常用的集合类,是基于数组实现的。不同于数组的是 ArrayList 可以动态扩容。

类结构

ArrayList 是 Java 集合框架 List 接口的一个实现类。提供了一些操作数组元素的方法。

实现 List 接口同时,也实现了 RandomAccess , Cloneable , java.io.Serializable 。

ArrayList 继承与 AbstractList 。


类成员

elementData


transient Object[] elementData;


复制代码

elementData 是用于保存数据的数组,是 ArrayList 类的基础。

elementData 是被关键字 transient 修饰的。我们知道被 transient 修饰的变量,是不会参与对象序列化和发序列化操作的。而我们知道 ArrayList 实现了 java.io.Serializable ,这就表明 ArrayList 是可序列化的类,这里貌似出现了矛盾。

ArrayList 在序列化和反序列化的过程中,有两个值得关注的方法: writeObject 和 readObject :


privatevoidwriteObject(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();

}

}


privatevoidreadObject(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(); // ignored


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 i=0; i<size; i++) {

a[i] = s.readObject();

}

}

}


复制代码

writeObject 会将 ArrayList 中的 size 和 element 数据写入 ObjectOutputStream 。 readObject 会从 ObjectInputStream 读取 size 和 element 数据。

之所以采用这种序列化方式,是出于性能的考量。因为 ArrayList 中 elementData 数组在 add 元素的过程,容量不够时会动态扩容,这就到可能会有空间没有存储元素。采用上述序列化方式,可以保证只序列化有实际值的数组元素,从而节约时间和空间。

size


private int size;


复制代码

size 是 ArrayList 的大小。

DEFAULT_CAPACITY


/**

* Default initial capacity.

*/

private static final int DEFAULT_CAPACITY = 10;


复制代码

ArrayList 默认容量是10。

构造函数

ArrayList 提供了2个构造函数 ArrayList(int initialCapacity) 和 ArrayList() 。

使用有参构造函数初始化 ArrayList 需要指定初始容量大小,否则采用默认值10。

add()方法


publicbooleanadd(E e){

ensureCapacityInternal(size + 1); // Increments modCount!!

elementData[size++] = e;

return true;

}


复制代码

在 add 元素之前,会调用 ensureCapacityInternal 方法,来判断当前数组是否需要扩容。


privatevoidensureCapacityInternal(intminCapacity){

if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

// 如果elementData为空数组,指定elementData最少需要多少容量。

// 如果初次add,将取默认值10;

minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

}

ensureExplicitCapacity(minCapacity);

}


privatevoidensureExplicitCapacity(intminCapacity){

modCount++;


// overflow-conscious code

// elementData容量不足的情况下进行扩容

if (minCapacity - elementData.length > 0)

grow(minCapacity);

}


privatevoidgrow(intminCapacity){

// 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);

}


复制代码

从 grow 方法中可以看出, ArrayList 的 elementData 数组如遇到容量不足时,将会把新容量 newCapacity 设置为 oldCapacity + (oldCapacity >> 1) 。二进制位操作 >> 1 等同于 /2 的效果,扩容导致的 newCapacity 也就设置为原先的1.5倍。

如果新的容量大于 MAX_ARRAY_SIZE 。将会调用 hugeCapacity 将 int 的最大值赋给 newCapacity 。不过这种情况一般不会用到,很少会用到这么大的 ArrayList 。

在确保有容量的情况下,会将元素添加至 elementData 数组中。

add(int index, E element) 方法


publicvoidadd(intindex, E element){

rangeCheckForAdd(index);


ensureCapacityInternal(size + 1); // Increments modCount!!

System.arraycopy(elementData, index, elementData, index + 1,

size - index);

elementData[index] = element;

size++;

}


复制代码

带有 index 的 add 方法相对于直接 add 元素方法会略有不同。

首先会调用 rangeCheckForAdd 来检查,要添加的 index 是否存在数组越界问题; 同样会调用 ensureCapacityInternal 来保证容量; 调用 System.arraycopy 方法复制数组,空出 elementData[index] 的位置; 赋值并增加 size ;

remove(int index) 方法


publicEremove(intindex){

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;

}


复制代码

ArryList 提供了两个删除 List 元素的方法,如上所示,就是根据 index 来删除元素。

检查 index 是否越界; 取出原先值的,如果要删除的值不是数组最后一位,调用 System.arraycopy 方法将待删除的元素移动至 elementData 最后一位。 将 elementData 最后一位赋值为null。

remove(Object o) 方法


publicbooleanremove(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;

}


复制代码

remove(Object o) 是根据元素删除的,相对来说就要麻烦一点:

当元素 o 为空的时候,遍历数组删除空的元素。 当元素 o 不为空的时候,遍历数组找出于 o 元素的 index ,并删除元素。 如果以上两步都没有成功删除元素,返回 false 。

modCount

在 add 、 remove 过程中,经常发现会有 modCount++ 或者 modCount-- 操作。这里来看下 modCount 是个啥玩意。

modCount 变量是在 AbstractList 中定义的。


protected transient int modCount = 0;


复制代码

modCount 是一个 int 型变量,用来记录 ArrayList 结构变化的次数。

modCount 起作用的地方是在使用 iterator 的时候。 ArrayList 的 iterator 方法。


publicIteratoriterator(){

return new Itr();

}


private classItrimplementsIterator{

int cursor; // index of next element to return

int lastRet = -1; // index of last element returned; -1 if no such

int expectedModCount = modCount;


publicbooleanhasNext(){

return cursor != size;

}


@SuppressWarnings("unchecked")

publicEnext(){

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];

}


publicvoidremove(){

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")

publicvoidforEachRemaining(Consumer 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();

}


finalvoidcheckForComodification(){

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 classTest{


Listlist = new ArrayList();



publicTest(){


}



publicvoidadd(){


for (int i = 0; i < 10000; i++) {

list.add(String.valueOf(i));

}


}


publicvoidread(){


Iterator iterator = list.iterator();

while (iterator.hasNext()) {

System.out.println(iterator.next());

}


}


复制代码

t1 线程:


public classTest1ThreadimplementsRunnable{


private Test test;


publicTest1Thread(Test test){

this.test = test;

}


publicvoidrun(){


test.add();


}


复制代码

t2 线程:


public classTest2ThreadimplementsRunnable{


private Test test;


publicTest2Thread(Test test){

this.test = test;

}



publicvoidrun(){

test.read();

}

}


复制代码

main 类


publicstaticvoidmain(String[] args)throwsInterruptedException{


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 是非线程安全的类。

以上就是职坐标整理发布关于JAVA的介绍,先祝大家对它有了一定的了解吧,了解更多内容,请关注职坐标编程语言JAVA频道!

本文由 @职坐标 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论
本文作者 联系TA

擅长针对企业软件开发的产品设计及开发的细节与流程设计课程内容。座右铭:大道至简!

  • 370
    文章
  • 23298
    人气
  • 87%
    受欢迎度

已有23人表明态度,87%喜欢该老师!

进入TA的空间
求职秘籍 直通车
  • 索取资料 索取资料 索取资料
  • 答疑解惑 答疑解惑 答疑解惑
  • 技术交流 技术交流 技术交流
  • 职业测评 职业测评 职业测评
  • 面试技巧 面试技巧 面试技巧
  • 高薪秘笈 高薪秘笈 高薪秘笈
TA的其他文章 更多>>
WEB前端必须会的基本知识题目
经验技巧 93% 的用户喜欢
Java语言中四种遍历List的方法总结(推荐)
经验技巧 91% 的用户喜欢
Java语言之SHA-256加密的两种实现方法详解
经验技巧 75% 的用户喜欢
java语言实现把两个有序数组合并到一个数组的实例
经验技巧 75% 的用户喜欢
通过Java语言代码来创建view的方法
经验技巧 80% 的用户喜欢
其他海同师资 更多>>
吕益平
吕益平 联系TA
熟悉企业软件开发的产品设计及开发
孔庆琦
孔庆琦 联系TA
对MVC模式和三层架构有深入的研究
周鸣君
周鸣君 联系TA
擅长Hadoop/Spark大数据技术
范佺菁
范佺菁 联系TA
擅长Java语言,只有合理的安排和管理时间你才能做得更多,行得更远!
金延鑫
金延鑫 联系TA
擅长与学生或家长及时有效沟通
经验技巧30天热搜词 更多>>

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程