一、 Set
TreeSet : 内部维护了一个TreeMap,插入的值作为map的key,value用默认值。特性是:排序,去重。
HashSet: 内部维护了HashMap,特点是插入和查找,时间复杂度 o(1), 但是迭代的顺序是乱序。
LinkedHashSet:内部维护了LinkedHashMap,特点是去重,可以按照插入的顺序进行迭代。
Set没啥好讲的,原理都是Map,因此到时候理解Map即可。
二、 List
2.1 ArrayList
2.1.1 数据域
elementData是存放数据的地方,它使用了transient修饰,不参与序列化。具体序列化看下面。
// 用于序列化
private static final long serialVersionUID = 8683452581122892189L;
// 默认的容器大小
private static final int DEFAULT_CAPACITY = 10;
// 构造方法指定的容器大小 == 0, 使用这个数组
private static final Object[] EMPTY_ELEMENTDATA = {};
//构造方法不指定容器大小,使用这个数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 实际存放元素的数组,它用transient关键字修饰,意思是不参与序列化
// 因为这个数组经常放不满,因此序列化的时候会调用write,把不为空的数据序列化
// 不对这个数组序列化是因为效率问题。
transient Object[] elementData; // non-private to simplify nested class access
// 数据的实际大小
private int size;
2.1.2 扩容
扩容的代码非常高,每次都使用elementData = Arrays.copyOf(elementData, newCapacity);来进行赋值,因此最好在程序一开始就指定好需要用的长度。
https://blog.csdn.net/qq_37591656/article/details/88134813
2.1.3 删除元素
ArrayList是基于数组的,它的删除调用了System.arraycopy()将index+1往后的值都复制到index的位置上。删除的代价很高。
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
2.1.4 序列化
1)序列化对于size(实际数据量)进行,而不是对完整的Object[] data。
2)反序列化中,先获取modcount, 在最后对modCount进行比较,如果两个数不相同,那么抛出异常。
int expectedModCount = modCount;
///
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new Concurrenv
2.2 Vectory
vectory是同步的,访问速度比ArrayList要大。同步完全可以由程序员自行控制
vectory的扩容是2倍,而ArrayList是1.5倍。
2.3 CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>();
可以用来替代Vectory方法。
2.3.1 读写分离
写操作是加锁的,而且是新复制了一个数组。
适用于多读少写的操作,由于每次写都重新开辟一个数组,因此写的代价非常大。
缺点还有读操作不能读取实时性的数据,因为部分写操作的数据还未同步到读数组中。
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
final void setArray(Object[] a) {
array = a;
}
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
2.4 LinkedList
继承了双向链表的接口。
2.4.1 成员域
transient int size = 0;
//指向头结点
transient Node<E> first;
//指向最后一个结点
transient Node<E> last;
2.4.2 与ArrayList的比较
- ArrayList支持随机访问,而LinkedList不支持
- LinkedList的添加删除效率比ArrayList来的快(因为不需要扩容,整体挪动数组)
2.4.3 remove(int index)、remove(Object obj)方法
支持这两种从某个位置删除的方法,源码中的处理方式都是遍历得到该位置的Node,然后调用unlink(node)的方式,维护pre、next指针。
public E remove(int index) {
checkElementIndex(index);
return unlink(node(index));
}
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
2.4.3 迭代器Iterator
测试了迭代器的删除。正序和逆袭都是可以的。
public class LinkedListTest {
static void PrintLinkedList(LinkedList<Integer> list) {
Iterator<Integer> iterator = list.iterator();
System.out.print("begin : ");
while (iterator.hasNext()) {
System.out.print(" " +iterator.next());
}
System.out.println();
}
public static void main(String[] args) {
LinkedList<Integer> list = new LinkedList<>();
list.add(2);
list.add(3);
list.add(5);
list.add(0);
Iterator<Integer> iterator = list.iterator();
//Iterator<Integer> iterator = list.descendingIterator();
int t = 0;
while (iterator.hasNext()) {
// 打印完毕后, 游标指向下一位, 打印5 指向0
System.out.println(iterator.next());
t++;
if (t == 3) {
// 删除上一位,也就是删除5
iterator.remove();
PrintLinkedList(list);
}
}
}
}
总结LinkedList
从头尾进行遍历,有两种方式,第一种是使用头尾指针,不断地进行遍历。 另一种是使用迭代器。
LinkedList封装了remove方法,如果希望从头尾进行删除,那么使用其包装好的remove(), removeLast()方法。 如果是想边迭代边删除,就要使用迭代器中的remove,删除的是游标上一个结点。
2.5 PriorityQueue
2.5.1 数据域
// 堆数组
transient Object[] queue; // non-private to simplify nested class access
// 大小
private int size = 0;
// 比较器,默认小根堆,可以通过构造方法进行修改
private final Comparator<? super E> comparator;
2.5.2 对象的 add、poll、peek
都是堆相关的方法
add、offer:添加到size的位置,然后向上调整堆。
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
return true;
}
poll: 把元素0 和 元素 --size 进行交换, 之后siftDown(0,x)向下调整堆
public E poll() {
if (size == 0)
return null;
int s = --size;
modCount++;
E result = (E) queue[0];
E x = (E) queue[s];
queue[s] = null;
if (s != 0)
siftDown(0, x);
return result;
}
此外,还提供了remove(object) , 这个方法也好理解,遍历堆数组找到i,然后把i和最后一位进行交换,然后调整i位置。
private E removeAt(int i) {
// assert i >= 0 && i < size;
modCount++;
int s = --size;
if (s == i) // removed last element
queue[i] = null;
else {
E moved = (E) queue[s];
queue[s] = null;
siftDown(i, moved);
if (queue[i] == moved) {
siftUp(i, moved);
if (queue[i] != moved)
return moved;
}
}
return null;
}
2.5.3 迭代器Iterator
迭代器的iterator遍历到的是堆数组的顺序,并不具备有序性。因此最好不用