Java集合的深入探讨
参考【Java集合后面的部分用的多吗?初级面试问的多吗? - Java3y的回答 - 知乎】
Java中关于集合的内容是比较多的,主要的集合类型分为Set(无序)、List(有序)、Queue、Map。它们关系如下图,所有的Java集合类均是这【四个接口】的实现类。下面将依次进行介绍。
List
List在Java里边是一个接口,常见的实现类有ArrayList和LinkedList,在开发中用得最多的是ArrayList。
ArrayList的底层数据结构是数组,LinkedList底层数据结构是链表。
ArrayList
先来说说为什么Java中已经有了数组,还要增加 ArrayList。
我们知道,数组必须在创建(不是声明)时指定数组的容量,但很多时候我们并不知道要存放多少数据进去,这个容量设置大了就造成内存浪费,设置小了又容易导致溢出。而ArrayList不同,它会动态扩大数组的容量,这样就避免了原生数组的这两个问题。
首先,在创建ArrayList的时候,可以指定其初始容量大小,不指定的话默认容量为10。这可以从源码看到:(下面只复制部分源码进行展示)
//默认容量大小
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] EMPTY_ELEMENTDATA = {};
//这就是ArrayList底层用于存放数据的数组
transient Object[] elementData;
//指定初始化容量
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);
}
}
//不指定容量时,用默认容量进行设置
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
transient关键字:实现了Serilizable接口的类,如果某个属性前添加关键字transient,序列化对象的时候,这个属性就不会被序列化到指定的目的地中
- 如何实现动态扩容?
当我们new ArrayList()的时候,默认会有一个空的Object数组,大小为0,源码如下:
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
当我们第一次add添加数据的时候,会给这个数组初始化一个大小,这个大小默认值为10
使用ArrayList在每一次add的时候,它都会先去计算这个数组够不够空间
如果空间是够的,那直接追加上去就好了。如果不够,那就得扩容
- 扩容,一次扩容多少?
在源码里边,有个grow方法,每一次扩原来的1.5倍。比如说,初始化的值是10,现在我第11个元素要进来了,发现这个数组的空间不够了,所以会扩到15(看下图第四块代码:newCapacity = oldCapacity + (oldCapacity >> 1)
,也就是新数组长度是old+old/2=1.5*old,即1.5倍旧数组长度)
空间扩完容之后,会调用arraycopy来对数组进行拷贝
为何在日常开发中用得最多的是ArrayList呢?
这是由底层的数据结构来决定的,在日常开发中,遍历的需求比增删要多,即便是增删也是往往在List的尾部添加就OK了。
像在尾部添加元素,ArrayList的时间复杂度也就O(1)
另外,ArrayList的增删底层调用的copyOf()被优化过,现代CPU对内存可以块操作,ArrayList的增删一点儿也不会比LinkedList慢
本文来自博客园,作者:aJream,转载请记得标明出处:https://www.cnblogs.com/ajream/p/16172383.html