List集合
集合底层使用动态数组实现,随机查询效率非常快,插入和删除需要移动整个数组、效率低,ArrayList不是线程安全的、 Vector是线程安全的集合。
Arraylist与Vector集合
相同点:
都是实现与List接口、底层采用数组实现
不同点:
Arraylist线程不安全、Vector线程是安全集合底层采用synchronized保证线程安全问题
Arraylist底层扩容是原数组的50%,Vector集合是原数组的100%
Arraylist底层数组没有默认长度为0,Vector底层数组默认长度为10
Vector集合中可以使用capacityIncrement增长容量
ArrayList:
add元素:ensureCapacityInternal进行初始化数组,判断是否需要扩容;
@Override public boolean add(E e) { ensureCapacityInternal(size + 1); elementData[size++] = e; return true; }
扩容:是以原有数组的大小扩容50%;扩容的实质就是将原有数组中的数据复制到扩容后的新数组中;扩容最大值为Integer.MAX_VALUE
private void grow(int minCapacity) { //获取原来数组的长度 int oldCapacity = elementData.length; //新的数组长度=原来数组长度+(原来数组长度除以2); int newCapacity = oldCapacity + (oldCapacity >> 1); //新数组的长度-最小容器<0 if (newCapacity - minCapacity < 0) { // 新数组长度=最小容器长度 (该判断的作用:第一次数组容量初始化使用) newCapacity = minCapacity; } if (newCapacity - MAX_ARRAY_SIZE > 0) { newCapacity = hugeCapacity(minCapacity); } // 使用 Arrays.copyOf 对我们数组容量实现扩容 elementData = Arrays.copyOf(elementData, newCapacity); }
get数据:从数组对应下标取数;
@Override public E get(int index) { // 从数组中获取到数据 return (E) elementData[index]; }
remove数据:将被移除的元素后面的数据全部前移一位;最后一个下标数据置为NULL;
public E remove(int index) { rangeCheck(index); E oldValue = get(index); // 获取移动下标 剩余后面的长度 int numMoved = size - index - 1; if (numMoved > 0) { System.arraycopy(elementData, index + 1, elementData, index, numMoved);//核心 } // 否则情况就是移除最后一个 elementData[--size] = null; return oldValue; }
Vector 源码分析:
- 默认构造函数中对数组的扩容为长度是为10;(如果创建了集合,没有使用的话是比较占用内存的);
- Vector集合实现add方法的时候有synchronized关键字修饰;
- Vector集合默认数组扩容是增长一倍,可以自己指定扩容倍数 (使用构造函数传递)
CopyOnWriteArrayList
实现了List接口
内部持有一个ReentrantLock lock = new ReentrantLock();
底层是用volatile transient声明的数组 array
读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array
public boolean add(E e) { // 1.获取原来数组的内容 Object[] elements = getArray(); // 2.获取数组的长度 int len = elements.length; // 3.在我们之前的数组基础扩容+1 Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e; // 4.保存我们原来的数组的内容 setArray(newElements); return true; }
LinkeList
public class MayiktLinkedList<E> implements MayiktList<E> { /** * 大小 */ transient int size = 0; /** * 链表中第一个元素 */ transient MayiktLinkedList.Node<E> first; /** * 链表中最后的元素 */ transient MayiktLinkedList.Node<E> last; @Override public int size() { return size; } @Override public boolean add(E e) { linkLast(e); return true; } @Override public E get(int index) { return (E) node(index).item; } MayiktLinkedList.Node<E> node(int index) { // 如果 当前获取的index <size/2 其实这就是一个折半查找 if (index < (size >> 1)) { // 获取第一个节点 MayiktLinkedList.Node<E> x = first; // 从0开始到当前查询下标位置 for (int i = 0; i < index; i++) { //获取到该元素 x = x.next; } return x; } else { // 获取到最后一个元素 MayiktLinkedList.Node<E> x = last; // 从最后一个元素开始遍历到向左遍历 for (int i = size - 1; i > index; i--) { x = x.prev; } return x; } } @Override public E remove(int index) { //获取删除的node节点 Node<E> node = node(index); //获取当前删除节点的下一个 Node<E> next = node.next; //获取当前节点删除的上一个 Node<E> prev = node.prev; if (prev == null) { //这说明删除的元素是第一个 first = next; } else { prev.next = next; } if (next == null) { // 如果删除当前没有 没有下一个next 说明是最后一个节点,则最后一个节为当前伤处的上一个 last = prev; } else { next.prev = prev; } size--; return (E) node; } void linkLast(E e) { // 获取当前最后节点元素 final MayiktLinkedList.Node<E> l = last; // 设置新增的node节点元素 final MayiktLinkedList.Node<E> newNode = new MayiktLinkedList.Node<>(l, e, null); // 最后的节点也就是当前新增的节点 last = newNode; if (l == null) { // 说明当前链表是第一次添加元素 first = newNode; } else { //当前下一个节点为 新增的节点 l.next = newNode; } size++; } /** * 存放节点元素 * * @param <E> */ private static class Node<E> { /** * 存放节点元素 */ E item; /** * 下一个节点 */ MayiktLinkedList.Node<E> next; /** * 上一个节点 */ MayiktLinkedList.Node<E> prev; // 设置新增的node节点元素 // final MayiktLinkedList.Node<E> newNode = new MayiktLinkedList.Node<>(l, e, null); // 上一个节点的last节点也就是下一个的节点的上一个节点(prev) Node(MayiktLinkedList.Node<E> prev, E element, MayiktLinkedList.Node<E> next) { // 节点元素 this.item = element; // 下一个元素 this.next = next; // 上一个元素 this.prev = prev; } } }
链表数据底层原理实现:双向链表头尾相接
- 在底层中使用静态内部类Node节点存放节点元素
三个属性 pred(关联的上一个节点) item 当前的值 next 下一个节点
- Add原理是如何实现 一直在链表之后新增
- Get原理 链表缺点:查询效率非常低,所以linkeList中采用折半查找 范围查询定位node节点
- 删除原理:就是改变头尾相结合 指向
Arraylist 查询比较多的场景
LinkeList 查询较少,新增该较多;
浙公网安备 33010602011771号