JAVA集合基础内容

Posted by BY KiloMeter on February 28, 2019

JAVA集合主要包括Collection和Map,Collection包含了List和Set。

  1. Collection: Collection 是集合 List、 Set、 Queue 的最基本的接口。
  2. Iterator:迭代器,可以通过迭代器遍历集合中的数据
  3. Map:是映射表的基础接口

List

List是有序的Collection,List的实现类有ArrayList,Vector和LinkedList

ArrayList(数组)

ArrayList是线程不安全的,底层的数据结构是数组,支持数据的随机访问,ArrayList在添加数据时如果容量不够,会创建一个大小为原来数组大小1.5倍的数组,然后将数据全部复制过去。当在ArrayList中间插入或者删除数据时,需要对原数组进行复制、移动、代价较高,因此ArrayList适合随机查找和遍历,不适合频繁地插入和删除。

Vector(数组+同步)

Vector是线程安全的,它的实现和ArrayList基本一样,主要区别是实现了同步,因此Vector的访问速度比ArrayList慢。

LinkedList(链表)

LinkedList 是用链表结构存储数据的,很适合数据的动态插入和删除,随机访问和遍历速度比较慢。另外,还提供了 List 接口中没有定义的方法,专门用于操作表头和表尾元素,可以当作栈、队列和双向队列使用。

Set

Set中存放的数据值是唯一的,且存入取出的顺序不一定相同,存入数组的值通过hashcode判断,如果想要让两个不同的对象视为相等的,就必须覆盖对象的hashcode方法和equals方法。

HashSet(Hash表)

基于哈希表实现,支持快速查找,但不支持有序性操作。并且失去了元素的插入顺序信息,也就是说使用 Iterator 遍历 HashSet 得到的结果是不确定的。HashSet是基于HashMap实现的。

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    static final long serialVersionUID = -5024744406713321676L;

    private transient HashMap<E,Object> map;

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

从HashSet的源代码可以看到,HashSet的方法都是由HashMap实现的,由HashMap的key存放HashSet的值,HashMap的value固定是一个PRESENT。

TreeSet(红黑树)

TreeSet的底层实现依赖于TreeMap。

public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }

和HashSet类似,TreeSet的实现也是依赖于Map,不过TreeSet依赖的是TreeMap

LinkedHashSet(HashSet+LinkedHashMap)

LinkedHashSet继承至HashSet,又基于LinkedHashMap实现,底层使用LinkedHashMap存储数据,由于继承HashSet ,因此LinkedHashSet的实现方法和HashSet相同,LinkedHashSet 维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可为插入顺序或是访问顺序

Map

HashMap(数组+链表+红黑树)

HashMap的内部有三个很重要的参数,capacity (容量)、loadFactor (负载因子)、threshold (扩容阈值)。

capacity 指的是数组的大小,初识大小为16,每次扩容时数组大小都会变成当前的2倍。

loadFactor 指的是数组填满程度的最大比例,默认是0.75。

threshold 指的是HashMap中数量的大小,如果达到 capacity *loadFactor 的话就会进行扩容。

对于使用 拉链法(下文会提到)的哈希表来说,查找一个元素的平均时间是 O(1+a),a 指的是链的长度,是一个常数。特别地,若负载因子越大,那么对空间的利用更充分,但查找效率的也就越低;若负载因子越小,那么哈希表的数据将越稀疏,对空间造成的浪费也就越严重。系统默认负载因子为 0.75,这是时间和空间成本上一种折衷,一般情况下是无需修改的。

HashMap最多只允许一条Entry的键为Null(多条会覆盖),但允许多条Entry的值为Null

在Java8之前,HashMap中并没有引入红黑树,在Java8后才引入红黑树,以前的HashMap只是使用链表,在查找数据的时候能根据hashcode迅速定位到数组的下表,但是之后只能沿着链表一个个比较,在Java8后,当链表中的元素超过8个之后,会将链表转换成红黑树。

ConcurrentHashMap

ConcurrentHashMap 和 HashMap 思路是差不多的,但是因为它支持并发操作,所以要复杂一些。

整个 ConcurrentHashMap 由一个个 Segment 组成, 可以理解ConcurrentHashMap 是一个Segment 数组,Segment 通过继承ReentrantLock 来进行加锁,所以每次需要加锁的操作锁住的是一个 segment,这样只要保证每个 Segment 是线程安全的,也就实现了全局的线程安全 。

ConcurrentHashMap 的并行度值为concurrencyLevel,该值默认是16,也就是说ConcurrentHashMap 有16个Segment ,因此理论上最多支持16个线程并发写,只要这些线程的操作分布在不同的Segment上就行。

concurrencyLevel的值可以在初始化的时候指定,但是指定后无法更改。

在Java8后,ConcurrentHashMap 也引入了红黑树。

HashTable

Hashtable 是遗留类,很多映射的常用功能与 HashMap 类似,不同的是它承自 Dictionary 类,并且是线程安全的,任一时间只有一个线程能写 Hashtable,并发性不如 ConcurrentHashMap,因为 ConcurrentHashMap 引入了分段锁。 Hashtable 不建议在新代码中使用,不需要线程安全的场合可以用 HashMap 替换,需要线程安全的场合可以用 ConcurrentHashMap 替换。

TreeMap

TreeMap 实现 SortedMap 接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用 Iterator 遍历 TreeMap 时,得到的记录是排过序的。

如果使用排序的映射,建议使用 TreeMap。在使用 TreeMap 时, key 必须实现 Comparable 接口或者在构造 TreeMap 传入自定义的Comparator,否则会在运行时抛出 java.lang.ClassCastException 类型的异常。

LinkHashMap

LinkedHashMap 是 HashMap 的一个子类,保存了记录的插入顺序,在用 Iterator 遍历LinkedHashMap 时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。

Java7/8 中的 HashMap 和 ConcurrentHashMap 全解析

Java8系列之重新认识HashMap