一、有序列表List接口:
1、常见实现
有三种实现方式:ArrayList、Vector和LinkedList,都是同一个接口的实现,所以操作和方法都是相同的,但是具体的实现方式不一样。
(1)ArrayList:数组实现方式,查询速度快,按位增删速度慢;
(2)LinkedList:链表的实现方式,按位增删速度快,查询速度慢;
(3)Vector:多线程时线程同步的,保证数据安全。
二、ArrayList:
1、原理:
内部是数组实现;
2、线程安全问题:
线程不安全。底层是一个数组,多个线程会修改长度。
public class ContainerNotSafeTest {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
for(int i = 0; i < 30; i++){
int tempInt = i;
new Thread(()->{
list.add(tempInt);
System.out.println(list);
}).start();
}
}
}
线程不安全解决方法
(1)使用 Vector
。List的古老实现类,JDK1.0引入;
(2)Collections工具类的synchronizedList方法
List<String> list = Collections.synchronizedList(new ArrayList<>());
(3)CopyOnWriteArrayList写时复制技术
List<String> list = new CopyOnWriteArrayList<>();
3、扩容
1)new无参对象时,底层是一个空数组;
2)当添加第一个元素时,会进行扩容,将底层数组长度扩为10,其中扩容触发的条件是:存元素时,即先让size+1的值判断是否大于底层elementData.length的长度,如果大于,则先扩容再添加,扩容的倍数为1.5倍,数组长度在有小数时会向下取整比如说:在效果图数组长度15时:在添加第16个元素时扩容后的长度为:原来长度*0.5+原来长度(向下取整)=新长度;运用这个公式就可以得到新的扩容数组长度为22。
通过查看源码看下详细过程:
3.1、初始化:
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
可以发现它的底层其实是一个Object类型的数组变量elementData(其实后面添加的元素就是存入这个数组中),初始化是空。
注意:当然我们也可以设置长度为其他值
例如: ArrayList list=new ArrayList<>(50); 当我们打印集合长度是就为:50
好处:可以减少扩容次数,提高程序在处理,运行时的速度
3.2、添加第一个元素
接下来执行 ensureCapacityInternal(size + 1)这个方法,就是去检测以下容器(elelmentData数组)长度是否够用,将size+1的值作为是否触发扩容的标准,这个值满足内在的设计条件,则扩容,不满足就不扩容,表示elementData数组容量还凑合,暂时不加长了。
注意:size其实代表的就是我们实际放入集合中的元素个数(而非elementData.length)。
private int size;
public boolean add(E e) {
// 将size+1的值作为判断接下来要不要扩容的标准,具体代码就在下一个方法,
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private static final int DEFAULT_CAPACITY = 10;
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
protected transient int modCount = 0;
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {//minCapacity=10
int oldCapacity = elementData.length;//此时elementData依然是空数组,长度自然为0,因此oldCapacity=0
int newCapacity = oldCapacity + (oldCapacity >> 1);```
**>>1 这个表示是二进制右移一位,0右移一位依然是0,因此newCapacity=0+0=0,后面当添加到11个元素时,也会在此扩容,扩容后的长度就是这样子算的, 10+10>>1 右移1位相当于乘以0.5 ,因此newCapacity=10+10*0.5=15 ,也就是说,扩容倍数为1.5倍**
```java
if (newCapacity - minCapacity < 0)//minCapacity:传入方法参数值10, 0-10=-10<0 为true,if成立
newCapacity = minCapacity; //将10赋值给新的数组长度 newCapacity=10
if (newCapacity - MAX_ARRAY_SIZE > 0)// MAX_ARRAY_SIZE =int的4个字节一共32位bit的最大值大概21亿多减去8的值,显然不成立
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);//通过Arrays工具类将老数组内容复制到一个新的数组,并指定此数组的长度为newCapacity:10 ,因此grow完成了扩容,返回的是长度为10的数组,里面存了系统给的默认值10个null。
}
补充:a >> b,则移动后a = a / 2^b,源码中
int newCapacity = oldCapacity + (oldCapacity >> 1);
右移1位,所以newCapacity = oldCapacity + oldCapacity / 2,即扩容为1.5倍。
二、LinkedList:
1、底层原理:LinkedList的实现是基于双向链表的,且头结点中不存放数据。双向链表不仅存储下一处的地址,还存储上一处的。
(1)随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为 LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
(2)增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。
2、线程安全问题:线程不安全。