List集合框架
Vector和ArrayList以及LinkedList区别和联系,以及分别的应用场景?
1:Vector
Vector的底层的实现其实是一个数组
protected Object[] elementData;
他是线程安全的,为什么呢?
由于经常使用的add()方法的源码添加synchronized,所以说他是一个同步方法 ,就连不会对数据结构进行修改的get()方法上也加了synchronized
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
如果不手动指定它的容量的话,它默认的容量是10
/**
* Constructs an empty vector so that its internal data array
* has size {@code 10} and its standard capacity increment is
* zero.
*/
public Vector() {
this(10);
}
2.LinkedList
LinkedList的底层其实是一个双向链表,每一个对象都是一个Node节点,Node就是一个静态内部类
private static class Node<E> {
//当前节点
E item;
//下一个节点
Node<E> next;
//上一个节点
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
它是线程不安全的,所有的方法都有加锁或者进行同步
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
3.ArrayList
这里先简单介绍一下,下面会对ArrayList的扩容机制进行分析
ArrayList是线程不安全的,如果不指定它的初始容量,那么它的初始容量是0,当第一次进行添加操作的时候它的容量将扩容为10
三种集合的使用场景
- Vector很少用,有其他线程安全的List集合
- 如果需要大量的添加和删除则可以选择LinkedList 原因是:它查询的时候需要遍历整个链表,插入和删除的时候无需移动节点
- 如果需要大量的查询和修改则可以选择ArrayList 原因:底层为数组,删除和插入需要移动其他元素,查询的时候根据下标来查
我们想要使用线程安全的List集合,你有什么办法?
1:可以使用Vector
2.自己重写类似于ArrayList的但是线程安全的集合
3.可以使用Collections(工具类)中的方法,将ArrayList变成一个线程安全的集合
4.可以使用java.util.concurrent包下的CopyOnWriteArrayList,它是线程安全的
那你说说CopyOnWriteArrayList是怎么实现线程安全的?
它是juc包下的,专门用于并发编程的,他的设计思想是:读写分离,最终一致,写时复制
它不能指定容量,初始容量是0.它底层也是一个数组,集合有多大,底层数组就有多大,不会有多余的空间
最常使用的add()方法的源码
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//获取一把锁
final ReentrantLock lock = this.lock;
//加锁
lock.lock();
try {
//获取当前集合(数组)
Object[] elements = getArray();
//获取当前集合的长度
int len = elements.length;
//复制一个新的数组,由于是添加操作,新数组的长度比原数组长度大1
Object[] newElements = Arrays.copyOf(elements, len + 1);
//原数组的长度就是新数组最大下标,将要添加的元素添加到最后
newElements[len] = e;
//更改引用,新数组替代原数组
setArray(newElements);
return true;
} finally {
//释放锁
lock.unlock();
}
}
remove()方法的实现逻辑也是大同小异,只不过需要移动元素,新数组是减1
/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices). Returns the element that was removed from the list.
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index);
int numMoved = len - index - 1;
if (numMoved == 0)
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index + 1, newElements, index,
numMoved);
setArray(newElements);
}
return oldValue;
} finally {
lock.unlock();
}
}
CopyOnWriteArrayList的缺点
底层是数组,删除插入的效率不高,写的时候需要复制,占用内存,浪费空间,如果集合足够大的时候容易触发GC
数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。【当执行add或remove操作没完成时,get获取的仍然是旧数组的元素】。CopyOnWriteArrayList读取时不加锁只是写入和删除时加锁
应用场景:读操作远大于写操作的时候
CopyOnWriteArrayList和Collections.synchronizedList区别
CopyOnWriteArrayList和Collections.synchronizedList是实现线程安全的列表的两种方式。两种实现方式分别针对不同情况有不同的性能表现,其中CopyOnWriteArrayList的写操作性能较差,而多线程的读操作性能较好。而Collections.synchronizedList的写操作性能比CopyOnWriteArrayList在多线程操作的情况下要好很多,而读操作因为是采用了synchronized关键字的方式,其读操作性能并不如CopyOnWriteArrayList。因此在不同的应用场景下,应该选择不同的多线程安全实现类。
说一下ArrayList的扩容机制
废话不多说,直接撸源码,红色的方法名代表会有解析
无参构造方法
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
//其实就是空数组
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
底层的数组
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
transient 这个关键字的用处是:ArrayList实现了Serializable接口,用transient修饰的字段或者对象不会进行实例化
扩容是再添加元素时才会出现的情况,有的情况是不指定初始容量第一次添加元素时,直接看add()方法
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
//先将集合的大小加一,代表有一个元素要加进来,开口有没有它的容身之处
ensureCapacityInternal(size + 1); // Increments modCount!!
//将新元素添加到集合中
elementData[size++] = e;
return true;
}
跳转到ensureCapacityInternal方法中进行验证
private void ensureCapacityInternal(int minCapacity) {
//DEFAULTCAPACITY_EMPTY_ELEMENTDATA初始化的值,也就是空
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//如果是为空的话,默认的DEFAULT_CAPACITY=10传入的minCapacity哪个大取哪个
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
继续调用ensureExplicitCapacity方法,传入判断之后的值,第一次add的话这个就是默认的10
private void ensureExplicitCapacity(int minCapacity) {
//对集合操作的次数
modCount++;
// overflow-conscious code
//传入的参数减去数组的长度是否大于0,大于0的话就代表要进行扩容了
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
判断传入的参数(第一次为10)减去数组的长度是否大于0,大于0的话调用grow扩容方法,数组的长度是elementData.length也可以说是容量,集合的大小是size,两个值是不同的
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
private void grow(int minCapacity) {
// overflow-conscious code
//旧的容量为当前数组的长度
int oldCapacity = elementData.length;
//新的容量为旧容量1.5倍,>>1代表右移一位,也就是÷2
int newCapacity = oldCapacity + (oldCapacity >> 1);
//新容量-旧容量是否小于0,一般是不指定容量,第一次add时才会进
if (newCapacity - minCapacity < 0)
//新容量等于传入的参数
newCapacity = minCapacity;
//如果新的容量超过了集合的阈值
if (newCapacity - MAX_ARRAY_SIZE > 0)
//调用hugeCapacity方法进行在一步的计算
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
//底层的数组进行copy后长度变为新的容量
elementData = Arrays.copyOf(elementData, newCapacity);
}
当新容量大于集合的阈值时,调用hugeCapacity方法
private static int hugeCapacity(int minCapacity) {
//为负数的话抛出异常,一般没这个可能
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
//三元表达式:新容量大于集合容量阈值时,新的容量为Integer的最大阈值,否则为集合的阈值
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
MAX_ARRAY_SIZE的值其实为Integer.MAX_VALUE-8,为什么要减8呢?因为数组也是一个对象,对象需要一定的内存存储对象头信息,对象头信息最大占用内存不可超过8字节。
整个ArrayList扩容的机制就如上所示,自己理解的,有不对之处还望指教
你能自己重写一个ArrayList吗?
这个就考验你对源码的理解程度,我自己对照着ArrayList写了一个,就是主要的增删改查还有扩容,写的比较拙劣,做个参考嘛
package com.yjc.list;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.List;
public class MyList<E> extends AbstractList<E> implements List<E> {
//定义无参构造方法
public MyList(){
this.elementData=EMPTY_ELEMENT_DATA;
}
//定义带参构造方法
public MyList(int capacity){
//验证容量是否合法
if (capacity>0) {
this.elementData = new Object[capacity];
}else if(capacity==0){
this.elementData=EMPTY_ELEMENT_DATA;
}else{
//为负数则抛出异常
throw new IllegalArgumentException("参数"+capacity+"不合法,参数不能为负数");
}
}
//定义底层数据结构
transient public Object [] elementData;
//定义初始化容量
private static final Integer DEFAULT_CAPACITY=10;
//集合的最大容量
private static final Integer MAX_CAPACITY=Integer.MAX_VALUE-8;
//创建一个空的数组,
private static final Object[] EMPTY_ELEMENT_DATA = {};
//用于记录当前数组的大小
private int size;
public boolean add(E e) {
ensureCapacityInternal(size+1);
//将size+1空间判断是否够用
elementData[size++]=e;
return true;
}
//用于判断数组是否够用
private void ensureCapacityInternal(Integer capacity){
//代表是第一次添加数据
if (elementData==EMPTY_ELEMENT_DATA){
capacity=DEFAULT_CAPACITY;
}
if(capacity-elementData.length>0){
//扩容
grow(capacity);
}
}
private void grow(Integer capacity) {
//获取原数组长度
int oldCapacity=elementData.length;
//右移两位,相当于除以2
int newCapacity=oldCapacity+(oldCapacity>>1);
//不指定初始大小的时候,第一次执行add方法会走到这
if (newCapacity-capacity<0){
newCapacity=capacity;
}
//代表超过集合的最大容量
if (newCapacity-MAX_CAPACITY>0){
newCapacity=(capacity>MAX_CAPACITY)?Integer.MAX_VALUE:MAX_CAPACITY;
}
elementData= Arrays.copyOf(elementData,newCapacity);
}
@Override
public E set(int index, E element) {
checkIndex(index);
Object oldValue=elementData[index];
elementData[index]=element;
//返回旧值
return (E) oldValue;
}
@Override
public E remove(int index) {
checkIndex(index);
Object oldValue=elementData[index];
size--;
for (int i = index; i <size-1; i++) {
elementData[i]=elementData[i+1];
}
return (E) oldValue;
}
@Override
public void add(int index, E element) {
checkIndex(index);
ensureCapacityInternal(size+1);
for (int i = size+1; i> index; i--) {
elementData[i]=elementData[i-1];
}
elementData[index]=element;
size++;
}
@Override
public int size() {
return this.size;
}
@Override
public E get(int index) {
checkIndex(index);
return (E) elementData[index];
}
private void checkIndex(int index){
//验证下标是否正确
if ((index>=size)||(index<0)){
throw new IndexOutOfBoundsException("输入的下标不正确,当前集合大小为:"+size);
}
}
}

浙公网安备 33010602011771号