Java集合类

概述

Java 容器主要分为Collection和Map两种,实现Collection接口的容器存的是一个个的对象,而实现Map接口的容器都是以<Object,Object>键值对的形式存在。

image.png

List简介:List是列表,ArrayList底层是数组实现,LinkedList底层是双向链表实现,Stack是栈,Vector和ArrayList基本相同,不同点在于1.Vector的方法都会被Synchronized修饰,效率较低,在项目中较少使用。2.Vector是2倍扩容,ArrayList是1.5倍扩容。在Web项目比较常用的是ArrayList。

Set简介:Set是集合,集合的特点当然是元素唯一。Set的底层其实就是Map的实例。在应用上,Set一般使用HashSet就够了,应用于一些需要去重的场景。

Map简介:一般用HashMap。TreeMap可以按key进行排序。

对于一个集合类,我们主要研究以下几点:

  • 底层数据结构(在什么数据结构下实现的增删改查?)
  • 扩容方式(初始容量?容量检查后几倍扩容)
  • 线程是否是安全(方法是否被Synchronize修饰,效率过低)

接下来对常用、面试常问的几个容器从以上几点进行介绍

List

ArrayList

ArrayList的底层是一个对象数组,源码如下

/**
* 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

根据注释可以看出,ArrayList的容量就是这个数组的长度,空ArrayList的容量为0,而一旦加入元素,就会扩容到 DEFAULT_CAPACITY = 10。transient修饰的变量不进行序列化,ArrayList的序列化是以另外一种方式进行。

扩容操作


/**
* 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;
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);
}

从grow函数可以看出来,容量不够的时候会进行1.5倍的扩容操作,并且会使用一次Arrays.copyof()方法进行复制。扩容一次就会对数组元素进行一次拷贝,反复扩容会对性能造成一些影响,因此指定初始容量会稍微的改善一些性能。另外,Arrays.copyof()只是浅克隆,对于引用数据类型,只复制在栈里的指针,而不会在堆里开辟新的内存空间。

ArrayList的最大容量就是数组的最大长度,即231 - 1。因为Int型是4个字节,占16位,而首位的0,1表示正负,所以在除去0后就是231 - 1。-0在计算机中当作最小整型-231

LinkedList

LinkedList实现了implements List(List操作方法), Deque(双端队列), Cloneable(可克隆), Serializable(可序列化)

public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
{

LinkedList底层是一个双向链表

/**
* Pointer to first node.
* Invariant: (first == null && last == null) ||
* (first.prev == null && first.item != null)
*/
transient Node<E> first;

/**
* Pointer to last node.
* Invariant: (first == null && last == null) ||
* (last.next == null && last.item != null)
*/
transient Node<E> last;

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;
}
}

如上所示,定义了头指针、尾指针,以及一个双向链表结点类型。

LinkedList是链表,不存在什么扩容的说法,是线程不安全的。此外,暂时没有别的要说的。

Map

HashMap

HashMap是除了ArrayList之外最常用的集合类了,先看看它的注释

Hash table based implementation of the Map interface. This implementation provides all of the optional map operations, 
and permits null values and the null key. (The HashMap class is roughly equivalent to Hashtable, except that it is
 unsynchronized and permits nulls.) This class makes no guarantees as to the order of the map; in particular, it does not
 guarantee that the order will remain constant over time.

这段话对HashMap做了一个很好的概括,告诉我们HashMap键值允许为空,线程不安全的,无序。并且与HashTable仅有微小的差别(键值允许为空、线程不安全)。这也从侧面反映HashTable在设计上是线程安全、键值不允许为空的。

HashMap实现了Map<K,V>(map规范), Cloneable(可克隆), Serializable(可序列化)

public class HashMap<K,V> extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable {

HashMap的底层数据结构

static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;

Node(int hash, K key, V value, Node<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
}


/**
* The table, initialized on first use, and resized as
* necessary. When allocated, length is always a power of two.
* (We also tolerate length zero in some operations to allow
* bootstrapping mechanics that are currently not needed.)
*/
transient Node<K,V>[] table;

可以看出HashMap的底层就是一个链表数组,其实就是数据结构中的十字链法进行冲突处理,发生冲突时就在那个数组的结点下连成链表。当冲突较多的时候,链表就会很长,这样链表的查询就会很费时间,所以在链表结点数目超过阈值时,会将链表转化为红黑树。

对于table数组的扩容是两倍扩容。

HashMap加入元素

// 节选冲突处理部分
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}

可以看到在binCount >= TREEIFY_THRESHOLD - 1时,会执行treeifyBin(tab, hash);将链表变为红黑树,大大的提高了查询效率。

TreeMap

TreeMap实现了NavigableMap<K,V>(有序Map), Cloneable(可克隆), java.io.Serializable(可序列化)

public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, java.io.Serializable
{

TreeMap的底层数据结构

private transient Entry<K,V> root;

static final class Entry<K,V> implements Map.Entry<K,V> {
K key;
V value;
Entry<K,V> left;
Entry<K,V> right;
Entry<K,V> parent;
boolean color = BLACK;

/**
* Make a new cell with given key, value, and parent, and with
* {@code null} child links, and BLACK color.
*/
Entry(K key, V value, Entry<K,V> parent) {
this.key = key;
this.value = value;
this.parent = parent;
}
}

TreeMap的底层也是一个结点数组,但和HashMap不同的是,其结点直接就是红黑树。

另外与HashMap的不同点在于

  • TreeMap是按照键值有序排列的
  • 由于要做比较,所以key,value都不允许为null

Set

HashSet

HashSet实现了Set(Set规范), Cloneable,(可克隆) java.io.Serializable(可序列化)

public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable

HashSet的底层数据结构

private transient HashMap<E,Object> map;

// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();

public boolean add(E e) {
return map.put(e, PRESENT)==null;
}

可以看出其实HashSet底层就是一个HashMap,仅仅只是把value置为空而已

TreeSet

同理,TreeSet等同于value为空的TreeMap

线程安全的容器

synchronized类

  • Vector
  • HashTable

所有方法都加synchronized修饰,鬼才用

并发容器

  • ConcurrentHashMap:分段
  • CopyOnWriteArrayList:写时复制
  • CopyOnWriteArraySet:写时复制

其他

  • ConcurrentSkipListMap:是TreeMap的线程安全版本
  • ConcurrentSkipListSet:是TreeSet的线程安全版本
  • ......

总结

常用容器就ArrayList、HashMap,再加上HashSet和ConcurrentHashMap,其他的一般是刷题的时候可能会使用到,例如使用LinkedHashMap作为LRU缓存

posted @   andandan  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· .NET Core 中如何实现缓存的预热?
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
点击右上角即可分享
微信分享提示