1.前言
在平时的开发中,Java集合一直是比较常用的。以前,对集合的掌握并不深入,仅简单的使用增删改查的相关方法。这个星期,抽时间反复的读了几遍ArrayList的源码,有了一些收获,写出来给自己,也希望对大家有帮助。
2.走进ArrayList
声明:
1
2
|
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
|
属性:
1
2
|
private transient Object[] elementData;
private int size;
|
分析:
RandomAccess接口,其实并无方法需要实现,只是一个标示。说明ArrayList可以快速访问,说白了就是可以通过下标索引的方式进行随机访问。
疑问:那么对于ArrayList而言,访问的方式,有 迭代遍历/随机访问/增强FOR循环,哪一种方式更加快速呢?
Serializable接口,同上,也是一个标示接口。注意到transient修饰了object[],说明在序列化的时候,object[]并不会序列化,那么反序列化的时候,object[]将为null。是这样吗?
疑问:很显然,ArrayList的很多方法必然是要操作object[]的,如果我们对ArrayList的对象实例进行了序列化,然后反序列化,最终object[]由于被transient修饰了,读出来是一个null。这显然是不行的,那么ArrayList对序列化做了哪些处理?
Cloneable接口亦是一个标示接口,表示可以进行对象的克隆。ArrayList复写了Object类的clone()方法。
疑问:克隆,深拷贝 OR 浅拷贝 ?
下面,我们一个疑问一个疑问的解决。
|
看一个测试例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
List<String> list = new ArrayList<String>();
for ( int i = 0 ; i < 1000000 ; i++){
list.add( "" + i);
}
long start = System.currentTimeMillis();
Iterator<String> it = list.iterator();
while (it.hasNext()){
it.next();
}
System.out.println( "iterator访问耗时(ms) : " + (System.currentTimeMillis() - start));
start = System.currentTimeMillis();
int length = list.size();
for ( int i = 0 ; i < length ; i++){
list.get(i);
}
System.out.println( "index访问耗时(ms) : " + (System.currentTimeMillis() - start));
start = System.currentTimeMillis();
for (String tmp : list){
}
System.out.println( "for-each访问耗时(ms) : " + (System.currentTimeMillis() - start));
|
运行结果如下:
iterator访问耗时(ms) : 47
index访问耗时(ms) : 15
for-each访问耗时(ms) : 47
结论:
对于支持RandomAccess的列表而言,用索引的方式进行访问是非常快速的。虽然,迭代器和增强FOR循环写法上简单,但是效率并不高。
|
阅读下java.io.Serializable的代码,发现有如下说明:
Classes that require special handling during the serialization and
deserialization process must implement special methods with these exact
signatures:
private void writeObject(java.io.ObjectOutputStream out)
throws IOException
private void readObject(java.io.ObjectInputStream in)
throws IOException, ClassNotFoundException;
private void readObjectNoData()
throws ObjectStreamException;
上面的意思就是说:
在序列化和反序列化过程中需要特殊处理的类必须使用上面的准确签名来实现特殊方法。
下面,我们看一下ArrayList是否提供了这些方法的签名。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
private void writeObject(ObjectOutputStream objectoutputstream)
throws IOException
{
int i = modCount;
objectoutputstream.defaultWriteObject();
objectoutputstream.writeInt(elementData.length);
for ( int j = 0 ; j < size; j++)
objectoutputstream.writeObject(elementData[j]);
if (modCount != i)
throw new ConcurrentModificationException();
else
return ;
}
private void readObject(ObjectInputStream objectinputstream)
throws IOException, ClassNotFoundException
{
objectinputstream.defaultReadObject();
int i = objectinputstream.readInt();
Object aobj[] = elementData = new Object[i];
for ( int j = 0 ; j < size; j++)
aobj[j] = objectinputstream.readObject();
}
|
说明:
有时候,ArrayList的容量是大于列表的实际大小的,如果序列化和反序列化object[]的所有元素,会消耗更多的资源,因此将object[]声明为transient,不调用默认的序列化/反序列化方法,而是提供自己的序列化、反序列化实现,从而仅仅序列化实际列表的元素。
|
举例说明:
Book:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class Book implements Cloneable{
private String name;
private double price;
private Author author;
@Override
protected Object clone() throws CloneNotSupportedException {
return super .clone();
}
public Book(String name, double price, Author author) {
super ();
this .name = name;
this .price = price;
this .author = author;
}
}
|
Author:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class Author {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this .name = name;
}
public Author(String name) {
super ();
this .name = name;
}
}
|
测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
Author author = new Author( "zhangfengzhe" );
Book book1 = new Book( "java" , 1 ,author);
Book book2 = (Book)book1.clone();
System.out.println( "book1 : " + book1.getName() + " , author:" +
book1.getAuthor().getName());
System.out.println( "book2 : " + book2.getName() + " , author:" +
book2.getAuthor().getName());
book2.setName( "python" );
System.out.println( "book1 : " + book1.getName() + " , author:" +
book1.getAuthor().getName());
System.out.println( "book2 : " + book2.getName() + " , author:" +
book2.getAuthor().getName());
author.setName( "lisi" );
System.out.println( "book1 : " + book1.getName() + " , author:" +
book1.getAuthor().getName());
System.out.println( "book2 : " + book2.getName() + " , author:" +
book2.getAuthor().getName());
|
结果:
book1 : java , author:zhangfengzhe
book2 : java , author:zhangfengzhe
book1 : java , author:zhangfengzhe
book2 : python , author:zhangfengzhe
book1 : java , author:lisi
book2 : python , author:lisi
说明:
如果一个类实现了Cloneable接口,并提供了默认的clone(),那么这个类的对象就具备了克隆的能力,并且JAVA的默认的clone()是对象的浅拷贝。
那么,在实际开发中,我们常常需要深拷贝,应该如何做呢?
第一种方式:改写clone()方法
核心逻辑如下:
1
2
3
4
5
6
7
|
@Override
public Object clone() throws CloneNotSupportedException {
Book tmp = (Book) super .clone();
tmp.author = (Author)author.clone();
return tmp;
}
|
实际上,就是将Book类中的所有对象类型手动的来一次clone
第二种方式:序列化
Book,Author不需要在实现Cloneable,而是需要实现Serializable。同时Book提供一个深拷贝的方法,如下:
1
2
3
4
5
6
7
8
9
10
11
|
public Object deepCopy() throws Exception{
ByteArrayOutputStream bo = new ByteArrayOutputStream();
ObjectOutputStream oo = new ObjectOutputStream(bo);
oo.writeObject( this );
ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi = new ObjectInputStream(bi);
return (oi.readObject());
}
|
将对象进行串行话后进行传递,是比较耗时的。
有了上面的知识,我们来看一下ArrayList中的clone()实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
public Object clone()
{
try
{
ArrayList arraylist = (ArrayList) super .clone();
arraylist.elementData = Arrays.copyOf(elementData, size);
arraylist.modCount = 0 ;
return arraylist;
}
catch (CloneNotSupportedException clonenotsupportedexception)
{
throw new InternalError();
}
}
|
看一个测试例子:
1
2
3
4
5
|
ArrayList<Student> stus = new ArrayList<Student>();
stus.add( new Student( 20 , "zfz" ));
ArrayList<Student> stus2 = (ArrayList<Student>)stus.clone();
stus2.get( 0 ).setName( "lisi" );
System.out.println(stus.get( 0 ).getName() + "," + stus2.get( 0 ).getName());
|
运行结果:
lisi,lisi
实际上,JAVA默认的就是浅拷贝,而浅拷贝可能带来问题。
|
先不看ArrayList怎么实现,就自己而言,应该如何实现呢?
增加:
-
数组的容量够吗?不够的话,要扩展容量
-
如果扩展容量,就是申请新的数组了,那么应该要将以前的数组的数据COPY过来
实际上,ArrayList的add方法的实现原理就是这样的。会调用ensureCapacity方法来确保数组容量,注意size也会变化。插入的过程,就是数组元素复制和移动的过程,因此这会比较耗时。
删除,可以同上分析。
查找,直接看一下indexOf方法的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public int indexOf(Object obj)
{
if (obj == null )
{
for ( int i = 0 ; i < size; i++)
if (elementData[i] == null )
return i;
} else
{
for ( int j = 0 ; j < size; j++)
if (obj.equals(elementData[j]))
return j;
}
return - 1 ;
}
|
其实,针对查找的对象是否为null,以决定在索引遍历过程中是用 == 还是 equals
lastIndexOf方法不过是逆向查找而已,思路一致。
|
ArrayList提供了3种构造方法。默认情况下,会构造一个大小为10的Object[]。
1
2
3
4
5
6
|
public ArrayList( int i){...}
public ArrayList()
{
this ( 10 );
}
public ArrayList(Collection collection){...}
|
通过对ArrayList的add/addAll分析,为了确保数组容量,会调用ensureCapacity方法。
查看下ensureCapacity方法的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public void ensureCapacity( int i)
{
modCount++;
int j = elementData.length;
if (i > j)
{
Object aobj[] = elementData;
int k = (j * 3 ) / 2 + 1 ;
if (k < i)
k = i;
elementData = Arrays.copyOf(elementData, k);
}
}
|
可以发现,在这个方法中完成了2件事情:
第一,确定新的容量,新的容量至少是 原来的容量*1.5+1
第二,完成容量扩展后的数组元素复制
由于对于ArrayList使用最频繁的方法就是add,而为了确保容量,就会调用ensureCapacity方法,而在ensureCapacity中又涉及到元素的复制,多次调用这个方法必然导致效率受到影响。
如果,我们在添加大量元素前,大致判断列表的大小,在构造ArrayList指定其容量,或者构造完成后调用ensureCapacity方法,通过提前分配好空间,避免递增式的分配空间,从而提高运行速度。
看一个例子:
1
2
3
4
5
6
7
|
ArrayList<String> list = new ArrayList<String>( 1000000 );
long start = System.currentTimeMillis();
for ( int i = 0 ; i < 1000000 ; i++){
list.add( "" + i);
}
System.out.println( "耗时: " + (System.currentTimeMillis() - start));
|
提前分配容量,耗时:594
没有提前分配容量,耗时:625
说明,在大量添加元素到列表中时,考虑提前分配空间,可以提高效率。
|
ArrayList转成数组
涉及到的方法是:
public Object[] toArray(){...}
public Object[] toArray(Object aobj[]){...}
有些时候,我们调用toArray()经常遇到java.lang.ClassCastException。比如:
1
2
3
|
List<String> list = new ArrayList<String>();
list.add( "1" );
String[] array = (String[])list.toArray();
|
究其原因,是因为toArray方法返回的是Object[],要知道将Object[]强制转化成String[]就会报:
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.String;
这个时候,我们应该调用的是toArray(Object aobj[])来实现。比如:
1
2
3
4
|
List<String> list = new ArrayList<String>();
list.add( "1" );
String[] array = (String[])list.toArray( new String[ 0 ]);
System.out.println(array[ 0 ]);
|
实际上,toArray(Object aobj[])所返回的数组,就是aobj类型的。
数组转成ArrayList
1
2
3
4
5
6
|
String[] tmp = new String[ 10 ];
tmp[ 0 ] = "a" ;
tmp[ 1 ] = "b" ;
tmp[ 2 ] = "c" ;
List<String> list2 = Arrays.asList(tmp);
System.out.println(list2.get( 1 ));
|
利用Arrays.asList方法,省时省力。
|
3.ArrayList小结
分享到:
相关推荐
主要为大家详细介绍了Java集合系列之ArrayList源码,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
本资源根据个人学习和总结,主要介绍Java中ArrayList扩容机制源码的解析,主要包含文字和代码等内容,以源码的形式进行分析,详细介绍了ArrayList扩容机制的整个流程,特此将该资源分享
主要为大家详细介绍了Java集合框架ArrayList源码分析,感兴趣的小伙伴们可以参考一下
计算机后端-Java-Java核心基础-第24章 集合01 14. ArrayList的源码分析.avi
? ArrayList?是一种变长的集合类,基于定长数组实现...本节课程会带着大家去学习集合底层源码是什么个结构,他在做什么事情,能做到什么事情,会出现的问题以及解决方法,希望同学能够仔细听,详细你会收到丰富的回报的
能学到什么:ArrayList的源码分析,自动扩容和自动缩容的源码分析,相关参数的深度解析,从是什么,为什么,怎么做三个角度进行讲解,用通俗易懂的白话进行介绍,LinkedList和Vector以及ArrayList的区别以及使用场景...
主要介绍了Java编程中ArrayList源码分析,具有一定借鉴价值,需要的朋友可以参考下。
来自视频课笔记 面试肯定没问题 包含线程安全的list和不安全的list
提供的源码资源涵盖了Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速...
│ │ 13.RPC底层通讯原理之Netty线程模型源码分析.wmv │ │ │ ├─14.分库分表之后分布式下如何保证ID全局唯一性 │ │ 14.分库分表之后分布式下如何保证ID全局唯一性.mp4 │ │ │ └─15.大型公司面试必答之...
java8 源码 List相关实现类的源码解析(JDK1.8) 2018.9.22- List的架构图 ArrayList 继承关系: ArrayList -> AbstractList 实现 List接口 ArrayList 是一个数组队列,相当于 动态数组。与Java中的数组相比,它的...
ArrayList源码分析 打开ArrayList的源码我们能看到ArrayList实现了List接口,扩展至AbstractList,其本质是一个可变长度的数组。 Java8中ArrayList包含注释一起一共1468行代码,算是一个比较复杂的类,所以这当中...
Collections 源码 java Java Java的ArrayList、LinkedList、HashMap、TreeMap、LinkedHashMap、HashSet、TreeSet相关源码分析,及相关问题和应用总结。
分析源码发现,在add方法中的elementData[size++] = e;存在线程不安全的风险。 elementData与size都是全局变量,但没有进行sychronization同步处理,elementData是共享的线程不安全的mutable可变数据。 public class...
这篇集合总结一共包括十二节,介绍了一些接口和实现类的底层源码以及基本的增加、删除元素等的操作(包括List、Map、Set接口、ArrayList、Vector、LinkedList、HashSet、TreeSet、HashMap、TreeMap等实现类)。...
源码分析 : ArrayList 源码+扩容机制分析 HashMap(JDK1.8)源码+底层数据结构分析 ConcurrentHashMap 源码+底层数据结构分析 IO IO 基础知识总结 IO 设计模式总结 IO 模型详解 并发 知识点/面试题总结 : (必看 ) ...
源码分析: ArrayList 源码+扩容机制分析 HashMap(JDK1.8)源码+底层数据结构分析 ConcurrentHashMap 源码+底层数据结构分析 IO IO 基础知识总结 IO 设计模式总结 IO 模型详解 并发 知识点/面试题总结 : (必看 ) ...
主要介绍了Java中ArrayList与LinkedList列表结构的源码,文章最后对LinkedList和ArrayList以及Vector的特性有一个对比总结,需要的朋友可以参考下