XGQ 2020-06-21
ArrayList是集合的一种实现,实现了接口List,List接口继承了Collection接口。Collection是所有集合类的父类。ArrayList使用非常广泛,不论是数据库表查询,excel导入解析,还是网站数据爬取都需要使用到,了解ArrayList原理及使用方法显得非常重要。
特点:当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。
创建一个ArrayList对象
new ArrayList<>()
时,将一个空数组{}赋值给了elementData,这个时候集合的长度size为默认长度0;new ArrayList<>(100)
时,根据传入的长度,new一个Object[100]赋值给elementData,当然如果玩儿的话,传了一个0,那么将一个空数组{}赋值给了elementData;注意:在传入集合的ArrayList的构造方法中,有这样一个判断
if (elementData.getClass() != Object[].class),
给出的注释是:c.toArray might (incorrectly) not return Object[] (see 6260652),即调用toArray方法返回的不一定是Object[]类型,查看ArrayList源码
public Object[] toArray() { return Arrays.copyOf(elementData, size);}我们发现返回的确实是Object[],那么为什么还会有这样的判断呢?
如果有一个类CustomList继承了ArrayList,然后重写了toArray()方法呢。。
public class CustomList<E> extends ArrayList { @Override public Integer [] toArray() { return new Integer[]{1,2}; }; public static void main(String[] args) { Object[] elementData = new CustomList<Integer>().toArray(); System.out.println(elementData.getClass()); System.out.println(Object[].class); System.out.println(elementData.getClass() == Object[].class); } }
执行结果:
class [Ljava.lang.Integer; class [Ljava.lang.Object; false
接着说,如果传入的集合类型和我们定义用来保存添加到集合中值的Object[]类型不一致时,ArrayList做了什么处理?读源码看到,调用了Arrays.copyOf(elementData, size, Object[].class);
,继续往下走
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) { T[] copy = ((Object)newType == (Object)Object[].class) ? (T[]) new Object[newLength] : (T[]) Array.newInstance(newType.getComponentType(), newLength); System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); return copy; }
我们发现定义了一个新的数组,将原数组的数据拷贝到了新的数组中去。
扩容机制
arraylist的默认大小是10,初始化时可以自定义容量大小,扩容算法为当前容量加上当前容量的无符号右移一位(oldCapacity + (oldCapacity >> 1) )也就是原容量的1.5倍
add(E element)
我们通过源码来看一下add("element")到底发生了什么
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; }
首先通过 ensureCapacityInternal(size + 1)
来保证底层Object[]数组有足够的空间存放添加的数据,然后将添加的数据存放到数组对应的位置上,我们看一下是怎么保证数组有足够的空间?
private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); }
这里首先确定了Object[]足够存放添加数据的最小容量,然后通过 grow(int minCapacity)
来进行数组扩容
private void grow(int minCapacity) { // 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); }
扩容规则为“数组当前足够的最小容量 + (数组当前足够的最小容量 / 2)”,即数组当前足够的最小容量 * 1.5,当然有最大值的限制。
简介:Vector 与 ArrayList 一样,也是通过数组实现的,不同的是它支持线程的同步,即某一时刻只有一
个线程能够写 Vector,避免多线程同时写而引起的不一致性,但实现同步需要很高的花费,因此,
访问它比访问 ArrayList 慢。
原理:构造方法还有增删改查的操作发现,都有一个synchronized关键字
详细细节
Vector继承于AbstractList,实现了List、RandomAccess、Cloneable、 Serializable等接口。
Vector 继承了AbstractList,实现了List接口。
Vector实现了RandmoAccess接口,即提供了随机访问功能。
Vector 实现了Cloneable接口,即实现克隆功能。
Vector 实现Serializable接口,表示支持序列化。
Vector实现的这些接口,表示会有这样的能力。Vector是线程安全的。下面我们从源码的角度来分析一下Vector是如何实现这些接口和保持线程安全的特性的。
构造方法
Vector的构造方法一共有四个,因为四个都比较重要,所以在这里就给出四个
线程安全
从构造方法还有增删改查的操作我们发现了,都有这么一个synchronized关键字,就是这个关键字为Vector容器提供了一个安全机制,保证了线程安全。
Vector与其他容器的区别
ArrayList是线程非安全的,因为ArrayList中所有的方法都不是同步的,在并发下一定会出现线程安全问题。另一个方法就是Vector,它是ArrayList的线程安全版本,其实现90%和ArrayList都完全一样,区别在于:
1、Vector是线程安全的,ArrayList是线程非安全的
2、Vector可以指定增长因子,如果该增长因子指定了,那么扩容的时候会每次新的数组大小会在原数组的大小基础上加上增长因子;如果不指定增长因子,那么就给原数组大小*2
由于性能效率很低,所以Vector与HashTable都开始逐渐被淘汰
简介:LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,他还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作堆栈、队列和双向队列使用。
对比
参考
https://www.cnblogs.com/LiaHon/p/11089988.html
https://baijiahao.baidu.com/s?id=1638844080997170869&wfr=spider&for=pc