Java集合基本介绍

在Java中我们经常使用到集合,集合是帮我们存储数据的,可以理解为一个容器。

在JDK中集合类都在java.util包中,下面按照容器特性区分一下我们常用的集合:
- Collection: 集合中存储的是普通对象
- List: 有序,可重复
- Set:不可重复
- Queue: 有序,先进先出
- Map: 集合中存储的是成对出现的”键值对”对象

我们先来了解一下Collection,Collection是一个集合的比较顶层抽象接口,实现了Iterable接口,从而使得Collection所有的实现类都抽象出Iterator中的遍历方法。

注:对于接口继承接口,个人喜欢叫”实现”,而不喜欢叫”继承”,其实就是extents和implements的区别,一个叫法而已。

Collection接口抽象了Collection集合通用的一些方法,如add、remove、size、contains等。

Collection的子实现中常用的有三个接口List、Set和Queue,这三个接口分别抽象了他们的子实现的一些通用方法。下面我们一个个的来看。

List

List接口实现Collection接口,并且新增了indexOf、lastIndexOf、subList等方法。

通过抽象出的这些和对象顺序有关的方法,我们可以知道List接口的子类实现元素都有序。

List也还是一个抽象的接口,下面再划分一下常用的List接口的子实现类:
- List
- ArrayList: 基于数组,线程不安全
- Vector:基于数组,线程安全
- LinkedList: 基于链表,线程不安全

ArrayList

transient Object[] elementData

ArrayList类中定义了一个成员变量elementData,是一个Object数组,ArrayList这个集合中的元素都存储在这个数组中。

private int size;

ArrayList类中定义了一个成员变量size,记录当前集合中存在的对象数量。

ArrayList的构造方法中可以传入一个参数initialCapacity,标识ArrayList初始化的时候数组的长度,也可以不传initialCapacity,使用默认值。

ArrayList的add方法(指所有添加元素方法,不止有add)就是往elementData数组中添加元素。

但是数组存在下标越界的问题,所以ArrayList在每次add方法之前都会检查数组中是否有剩余空间,如果没有足够空间就会扩容。

Vector

Vector的实现方式和ArrayList基本一致,但是Vector是线程安全的。

Vector几乎对所有的方法都加上了synchronized锁,所以可以在多线程场景下保证线程安全。

Vector还有一个子类Stack,实现了”栈”的数据结构

Stack

Stack类继承了Vector类,具有Vector类的特性。

同时Stack类还提供了,push、pop、peek等方法,这些方法是基于Vector类中的方法去实现的,只是多了一层封装,使得Stack有了”栈”的特性(后进先出)。

LinkedList

LinkedList类实现了Deque(双向队列)接口,实现了Deque中抽象出的双向队列的方法。

transient Node<E> first;

transient Node<E> last;

LinkedList定义了两个成员变量first和last,都是Node类型,使用Node组成可一个双向链表。

因为实现了List接口,所以LinkedList也支持根据索引来遍历元素。


  • 这里对List的几个常用的实现类多一个总结:
    • ArrayList:使用数组实现,相对于LinkedList查询快,增删慢,线程不安全
    • LinkedList:使用双向链表实现,相对于ArrayList增删快,查询慢,线程不安全
    • Vector:实现方式和ArrayList几乎一致,对大多数方法加锁,线程安全,但是效率低,开发中一般不会使用

Set

Set接口实现Collection接口,没有新增什么特别的接口。

A collection that contains no duplicate elements

但是我们通过Set接口的注释,可以知道Set接口没有重复元素,可以实现元素的去重。

Set也还是一个抽象的接口,下面再划分一下常用的Set接口的子实现类:
- Set
- HashSet: 使用HashMap的key存储元素,无序,不重复
- LinkedHashSet: 使用LinkedHashMap的key存储元素,有序,不重复
- TreeSet:基于数组,使用TreeMap的key存储元素,有序,不重复

HashSet

private transient HashMap<E,Object> map

HashSet类中定义了一个成员变量map,是一个HashMap,key是存储的元素,value是一个固定的Object。

HashSet充分利用了HashMap中key唯一的特性,使用hashCode和equals方法来确保元素的不重复,并且加快元素的查询速度。

具体HashMap是怎样保证key唯一的,等下面到HashMap的时候介绍。

HashSet还有一个子类常被用到,LinkedHashSet,保证了元素的存储顺序。

LinkedHashSet

正如HashSet是用HashMap实现元素唯一性的一样,LinkedHashSet是使用LinkedHashMap去实现元素的唯一性,并且利用LinkedHashMap中的双向链表实现元素的顺序。

因为LinkedHashSet还需要维护一个双向链表,所以LinkedHashSet的增删速度会比HashSet慢。

而进行遍历的话,因为LinkedHashSet维护了一个链表,所以遍历速度也是很快的。

TreeSet

和HashSet、LinkedHashSet一样,TreeSet也是使用TreeMap的key来存储元素的。

从上面的介绍中可以看到TreeSet也是有序的,但是TreeSet的有序和LinkedHashSet的有序是不一样的。

LinkedHashSet维护的顺序是进出容器的顺序,而TreeSet维护的是元素的大小。

元素的大小?

元素的大小指的是compareTo方法的结果,所以TreeSet中存储的元素必须要实现Comparable接口,重写compareTo方法,从而分出元素的大小。

Queue

Queue接口实现Collection接口,并且新增了offer、poll、peek等方法。

通过抽象出的这些和操作队列有关的方法,我们可以知道Queue接口的子类实现都是队列。

Queue也还是一个抽象的接口,下面再划分一下常用的Queue接口的子实现类:

  • Queue
    • BlockingQueue: 阻塞式的Queue 接口
      • ArrayBlockingQueue:BlockingQueue的数组实现
      • LinkedBlockingQueue:BlockingQueue的链表实现
    • Deque:双向队列 接口
      • LinkedList:Deque的链表实现
      • BolckingDeque:阻塞式的Deque
        • LinkedBlockingDeque: 阻塞式的LinkedList
          LinkedList

BlockingQueue

BlockingQueue接口实现了Queue接口,并添加了put、take等阻塞式方法。

和Queue接口中定义的offer、poll方法存取元素不一样的是,put方法存元素的时候如果BlockingQueue长度已达到上限(实现类构造方法可设置BlockingQueue最大长度)会阻塞,take方法取元素的时候如果BlockingQueue中没有元素也会阻塞。

ArrayBlockingQueue

ArrayBlockingQueue类是BlockingQueue接口的一个实现类,使用数组来实现BlockingQueue,适用于查询多,增删少的场景(貌似不多)。

LinkedBlockingQueue

LinkedBlockingQueue类是BlockingQueue接口的一个实现类,使用链表来实现队列,适用于查询少,增删多的场景。

Deque

Deque接口实现了Queue接口,并添加了pollFirst、pollLast、offerFirst、offerLast等双向队列特性的方法。

LinkedList

LinkedList类在上面List部分已经介绍过了,使用双链表实现,同时具有List和Deque的特性。

LinkedBlockingDeque

LinkedBlockingDuque也很好理解,同时具有Deque双向队列的特性和BlockingQueue的阻塞式特性。

Map

Map接口的实现类存储的元素都是”键值对”类型的,具有映射关系,键和值一一对应。

关于Map有一点我们需要知道的是,JDK是先实现了Map,然后针对一个个Map的实现类,将value值忽略就实现了一个个的Set,正如上面所说的,一个个Set都是基于Map来实现的。

Map接口定义了Map集合通用的一些方法,如put、keySet、values、size等。

下面划分一下Map接口常用的实现类
- Map
- HashMap:key唯一,无序,线程不安全
- LinkedHashMap:key唯一,有序,线程不安全
- HashTable:key唯一,无序,线程安全
- SortedMap:key唯一,有序 接口
- TreeMap:key唯一,有序

HashMap

HashMap是Map接口中最常用的一个实现类,底层采用数组+链表(红黑树)的方式来实现元素的存储。

public HashMap(int initialCapacity, float loadFactor) {
    ...
}

HashMap的构造方法中可以传入两个参数,initialCapacity表示数组的大小,loadFactor叫负载引子,用于计算HashMap什么时候该扩容。

首先HashMap会生成一个确定大小的数组,然后每个数组中存储一个链表(JDK1.8之后链表长度超过8会转成红黑树),下面列出put方法执行的一些规则:
- 1.计算数组长度,如果为空,执行扩容
- 2.根据key值计算hash值得到插入的数组下标
- 3.如果数组中没有链表(红黑树),则创建链表
- 4.如果数组中有链表,则遍历链表,将插入元素的value值和链表中的元素进行对比(equals方法),如果相同则替换,value和如果链表中元素都不相同,则将value插入到链表中
- 5.如果链表长度超过8,则将链表转成红黑树(JDK1.8+)
- 6.如果Map中元素数量达到数组长度*loadFactor,再插入元素会进行扩容

HashMap还有个子类LinkedHashMap

LinkedHashMap

LinkedHashMap继承自HashMap,所以具有HashMap的特性,并且LinkedHashMap自己维护了双向链表,从而可以保证元素的存储顺序。

HashTable

正如ArrayList和Vector的关系一样,HashTable的实现方式和HashMap基本一致,但是HashTable对大多数方法添加了synchronized方法,所以HashTable能在多线程场景下实现线程安全。

因为HashTable使用的是方法锁,锁的是对象,所以会导致所有被synchronized修饰的方法都会互斥,导致性能比较低。而ConcurrentHashMap基于Lock实现,锁住的是一个个的segment,而不是整个对象,性能比HashTable高很多。

所以工作中用到线程安全的Map,基本都会使用ConcurrentHashMap,而不是HashTable,由于这篇文章主要是介绍JDK中的各种集合,所以就不往下深究线程安全的问题了。

TreeMap

TreeMap类实现了Map接口,同时还具有有序的特性,就像TreeSet一样,TreeMap保证key的有序性,同样的这里的有序指的也是conpareTo方法的结果.


喜欢这篇文章的朋友,欢迎长按下图关注公众号lebronchen,第一时间收到更新内容。
扫码关注

猜你喜欢

转载自blog.csdn.net/Leon_cx/article/details/81369020
今日推荐