个人愚见,如有错误之处,望见谅。
是否有序 | 顺序存储 | 无序 |
是否可重复 | 可以重复 | 不可以重复 |
存储结构 | 数据或者链表 | 使用Map来存储数据 |
数据结构存储方式有两种:1. 数组 2. 链表。
List 集合实现有多种方式,不同方式数据结构存储方式不一样,下面从源码的角度看一下不同实现的原理:
ArrayList
1. 内部维护一个数组,每次添加相当于向数组中添加数据。
2. 初始化时容量为0,只有当第一次添加数据时才会将容量修改为10。每次扩容为原来大小的1.5倍,然后通过Arrays.copyOf(elementData, newCapacity)方法返回扩容后的数据。
3. ArrayList 添加和删除每次操作都会进行一次数组拷贝
4. fail-fast 在迭代的时候,首先会判断expectedModCount 和 modCount是否相等,如果不相等则抛出ConcurrentModificationException异常.其中expectedModCount等于迭代时的modCount值,当每次有人调用add,remove方法时,modeCount值就会改变,所以这时expectedModCount 和 modCount是不相等,抛出异常
见链接:Java中ArrayList最大容量为什么是Integer.MAX_VALUE-8?
public ArrayList() {
//创建对象时,如果没有指定容量,则是一个空数组,容量为0
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
public boolean add(E e) {
ensureCapacityInternal(size + 1); //确保容量足够
elementData[size++] = e; //元素存放在数组中
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//如果数组为空,则初始化容量为10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
//确定明确的容量
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 当需要的最小内存比原有容量大时扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//真正的扩容操作
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//新容量 = 旧容量 + (旧容量/2) 1.5倍扩容
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);
}
取数据时,首先判断下表是否越界,然后返回数组中对应下标的数据。
public E get(int index) {
//检查下标是否越界
rangeCheck(index);
return elementData(index);
}
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;
}
}
每当有新数据添加的时候,会将元素添加到链表的最后位置,并将元素的prev 指向前一个元素,前一个元素的next指向需要添加的元素
public E get(int index) {
//检查下标是否越界
rangeCheck(index);
return elementData(index);
}
public boolean add(E e) {
linkLast(e);
return true;
}
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;//将原始节点的next属性指向新节点组成循环链表
size++;//记录链表的长度
modCount++;
}
Node<E> node(int index) {
// assert isElementIndex(index);
//个人提示:如果所取元素位置小于list大小的一般,就从前向后取,反之从后向前取
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
set集合暂时只介绍HashSet
HashSet 内部是一个map,通过HashMap来保证数据的唯一性,因此是非线程安全和无序的
public HashSet() {
map = new HashMap<>();
}
数据时没有get方法,只能通过迭代的方式获取数据map中所有的key
private static final Object PRESENT = new Object();
public boolean add(E e) {
//存入map中
return map.put(e, PRESENT)==null;
}
//present 作用:map移除会返回value,如果底层用null的话无法分辨是否移除成功
public boolean remove(Object o) {
return map.remove(o)==PRESENT;
}