深入Java源码解析容器类ListSetMap

Posted tezlikai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入Java源码解析容器类ListSetMap相关的知识,希望对你有一定的参考价值。

1 常用容器继承关系图

    先上一张网上的继承关系图

    个人觉得有些地方不是很准确,比如Iterator不是容器,只是一个操作遍历集合的方法接口,所以不应该放在里面。并且Map不应该继承自Collection。所以自己整理了一个常用继承关系图如下:

    如上图所示,接下去会自顶向下解释重要的接口和实现类。

2 Collection和Map

    在Java容器中一共定义了2种集合, 顶层接口分别是Collection和Map。但是这2个接口都不能直接被实现使用,分别代表两种不同类型的容器。

    简单来看,Collection代表的是单个元素对象的序列,(可以有序/无序,可重复/不可重复 等,具体依据具体的子接口Set,List,Queue等);Map代表的是“键值对”对象的集合(同样可以有序/无序 等依据具体实现)

2.1 Collection

    根据Java官方文档对Collection的解释

    The root interface in the collection hierarchy. A collection represents a group of objects, known as its elements. Some collections allow duplicate elements and others do not. Some are ordered and others unordered. The JDK does not provide any direct implementations of this interface: it provides implementations of more specific subinterfaces like Set and List. This interface is typically used to pass collections around and manipulate them where maximum generality is desired.

    大概意思就是:

    是容器继承关系中的顶层接口。是一组对象元素组。有些容器允许重复元素有的不允许,有些有序有些无序。 JDK不直接提供对于这个接口的实现,但是提供继承与该接口的子接口比如 List Set。这个接口的设计目的是希望能最大程度抽象出元素的操作。

    接口定义:


    泛型即该Collection中元素对象的类型,继承的Iterable是定义的一个遍历操作接口,采用hasNext next的方式进行遍历。具体实现还是放在具体类中去实现。

    我们可以看下定义的几个重要的接口方法

add(E e) 

clear()

contains(Object o)

isEmpty()

iterator()

remove(Object o)

retainAll(Collection<?> c)

size()

toArray()

toArray(T[] a)

 ...

    上面定义的接口就代表了Collection这一类容器最基本的操作,包括了插入,移除,查询等,会发现都是对单个元素的操作,Collection这类集合即元素对象的存储。其中有2个接口平时没用过但是觉得很有用

  1. retainAll(Collection<?> c) 保留指定的集合

  2. toArray(T[] a) 可以转为数组

2.2 Map

    Java官方文档对Map的解释

    An object that maps keys to values. A map cannot contain duplicate keys; each key can map to at most one value.

    This interface takes the place of the Dictionary class, which was a totally abstract class rather than an interface.

    The Map interface provides three collection views, which allow a map’s contents to be viewed as a set of keys, collection of values, or set of key-value mappings. The order of a map is defined as the order in which the iterators on the map’s collection views return their elements. Some map implementations, like the TreeMap class, make specific guarantees as to their order; others, like the HashMap class, do not.

    大概意思就是:

    一个保存键值映射的对象。 映射Map中不能包含重复的key,每一个key最多对应一个value。

    这个接口替代了原来的一个抽象类Dictionary。

    Map集合提供3种遍历访问方法,1.获得所有key的集合然后通过key访问value。2.获得value的集合。3.获得key-value键值对的集合(key-value键值对其实是一个对象,里面分别有key和value)。 Map的访问顺序取决于Map的遍历访问方法的遍历顺序。 有的Map,比如TreeMap可以保证访问顺序,但是有的比如HashMap,无法保证访问顺序。

    接口定义如下:


    泛型分别代表key和value的类型。这时候注意到还定义了一个内部接口Entry,其实每一个键值对都是一个Entry的实例关系对象,所以Map实际其实就是Entry的一个Collection,然后Entry里面包含key,value。再设定key不重复的规则,自然就演化成了Map。(个人理解)

    下面介绍下定义的3个遍历Map的方法。

  1. SetkeySet()

    会返回所有key的Set集合,因为key不可以重复,所以返回的是Set格式,而不是List格式。(之后会说明Set,List区别。这里先告诉一点Set集合内元素是不可以重复的,而List内是可以重复的) 获取到所有key的Set集合后,由于Set是Collection类型的,所以可以通过Iterator去遍历所有的key,然后再通过get方法获取value。如下



  2. Collectionvalues()

    直接获取values的集合,无法再获取到key。所以如果只需要value的场景可以用这个方法。获取到后使用Iterator去遍历所有的value。如下

    Map<String,String> map = new HashMap<String,String>();
    map.put("01", "zhangsan");
    map.put("02", "lisi");
    map.put("03", "wangwu");
    
    //返回值是个值的Collection集合
    Collection<String> collection = map.values();
    System.out.println(collection);
  3. Set< Map.Entry< K, V>> entrySet()

    是将整个Entry对象作为元素返回所有的数据。然后遍历Entry,分别再通过getKey和getValue获取key和value。如下


    通过以上3种遍历方式我们可以知道,如果你只想获取key,建议使用keySet。如果只想获取value,建议使用values。如果key value希望遍历,建议使用entrySet。(虽然通过keySet可以获得key再间接获得value,但是效率没entrySet高,不建议使用这种方法)

3 List、Set和Queue

    在Collection这个集成链中,我们介绍List、Set和Queue。其中会重点介绍List和Set以及几个常用实现class。Queue平时实在没用过。

    先简单概述下List和Set。他们2个是继承Collection的子接口,就是说他们也都是负责存储单个元素的容器。但是最大的区别如下

  1. List是存储的元素容器是有个有序的可以索引到元素的容器,并且里面的元素可以重复。

  2. Set里面和List最大的区别是Set里面的元素对象不可重复。

3.1 List

    Java文档中介绍

    An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list.

    Unlike sets, lists typically allow duplicate elements. More formally, lists typically allow pairs of elements e1 and e2 such that e1.equals(e2), and they typically allow multiple null elements if they allow null elements at all. It is not inconceivable that someone might wish to implement a list that prohibits duplicates, by throwing runtime exceptions when the user attempts to insert them, but we expect this usage to be rare.

    …

    The List interface provides a special iterator, called a ListIterator, that allows element insertion and replacement, and bidirectional access in addition to the normal operations that the Iterator interface provides. A method is provided to obtain a list iterator that starts at a specified position in the list.

    大概意思是:

    一个有序的Collection(或者叫做序列)。使用这个接口可以精确掌控元素的插入,还可以根据index获取相应位置的元素。

    不像Set,list允许重复元素的插入。有人希望自己实现一个list,禁止重复元素,并且在重复元素插入的时候抛出异常,但是我们不建议这么做。

    List提供了一种特殊的iterator遍历器,叫做ListIterator。这种遍历器允许遍历时插入,替换,删除,双向访问。 并且还有一个重载方法允许从一个指定位置开始遍历。

    然后我们再看下List接口新增的接口,会发现add,get这些都多了index参数,说明在原来Collection的基础上,List是一个可以指定索引,有序的容器。在这注意以下添加的2个新Iteractor方法。


    我们再看ListIterator的代码:


    一个集合在遍历过程中进行插入删除操作很容易造成错误,特别是无序队列,是无法在遍历过程中进行这些操作的。但是List是一个有序集合,所以在这实现了一个ListIteractor,可以在遍历过程中进行元素操作,并且可以双向访问。

这个是之前开发中一直没有发现的,好东西。mark

    以上就是List的基本概念和规则,下面我们介绍2个常用List的实现类,ArrayList和LinkedList。

3.1.1 ArrayList

    就Java文档的解释,整理出以下几点特点:

  1. ArrayList是一个实现了List接口的可变数组

  2. 可以插入null

  3. 它的size, isEmpty, get, set, iterator,add这些方法的时间复杂度是O(1),如果add n个数据则时间复杂度是O(n).

  4. ArrayList不是synchronized的。

    然后我们来简单看下ArrayList源码实现。这里只写部分源码分析。

    所有元素都是保存在一个Object数组中,然后通过size控制长度。

transient Object[] elementData;private int size;

    这时候看下add的代码分析


    其实在每次add的时候会判断数据长度,如果不够的话会调用Arrays.copyOf,复制一份更长的数组,并把前面的数据放进去。

    我们再看下remove的代码是如何实现的。


    其实就是直接使用System.arraycopy把需要删除index后面的都往前移一位然后再把最后一个去掉。

PS:终于发现以前学习的数据结构用到用场了。O。O

3.1.2 LinkedList

    LinkedList是一个链表维护的序列容器。和ArrayList都是序列容器,一个使用数组存储,一个使用链表存储。

    数组和链表2种数据结构的对比:

  1. 查找方面。数组的效率更高,可以直接索引出查找,而链表必须从头查找。

  2. 插入删除方面。特别是在中间进行插入删除,这时候链表体现出了极大的便利性,只需要在插入或者删除的地方断掉链然后插入或者移除元素,然后再将前后链重新组装,但是数组必须重新复制一份将所有数据后移或者前移。

  3. 在内存申请方面,当数组达到初始的申请长度后,需要重新申请一个更大的数组然后把数据迁移过去才行。而链表只需要动态创建即可。

    如上LinkedList和ArrayList的区别也就在此。根据使用场景选择更加适合的List。

    下面简单展示LinkedList的部分源码解析。

    首先是链表的节点的定义,非常简单的一个双向链表。


    然后每个LinkedList中会持有链表的头指针和尾指针


    列举最基本的插入和删除的链表操作


    上面6个方法就是链表的核心,头尾中间插入,头尾中间删除。其他对外的调用都是围绕这几个方法进行操作的

    同时LinkedList还实现了Deque接口,Deque接口是继承Queue的。所以LinkedList还支持队列的pop,push,peek操作。

总结

List实现 使用场景 数据结构
ArrayList 数组形式访问List链式集合数据,元素可重复,访问元素较快 数组
LinkedList 链表方式的List链式集合,元素可重复,元素的插入删除较快 双向链表

3.2 Set

    Set的核心概念就是集合内所有元素不重复。在Set这个子接口中没有在Collection特别实现什么额外的方法,应该只是定义了一个Set概念。下面我们来看Set的几个常用的实现HashSet、LinkedHashSet、TreeSet

3.2.1 HashSet

    HashSet的核心概念。Java文档中描述

    This class implements the Set interface, backed by a hash table (actually a HashMap instance). It makes no guarantees as to the iteration order of the set; in particular, it does not guarantee that the order will remain constant over time. This class permits the null element.

    大概意思是:

    HashSet实现了Set接口,基于HashMap进行存储。遍历时不保证顺序,并且不保证下次遍历的顺序和之前一样。HashSet中允许null元素。

    进入到HashSet源码中我们发现,所有数据存储在:


    意思就是HashSet的集合其实就是HashMap的key的集合,然后HashMap的val默认都是PRESENT。HashMap的定义即是key不重复的集合。使用HashMap实现,这样HashSet就不需要再实现一遍。

    所以所有的add,remove等操作其实都是HashMap的add、remove操作。遍历操作其实就是HashMap的keySet的遍历,举例如下


3.2.2 LinkedHashSet

    LinkedHashSet的核心概念相对于HashSet来说就是一个可以保持顺序的Set集合。HashSe

以上是关于深入Java源码解析容器类ListSetMap的主要内容,如果未能解决你的问题,请参考以下文章

Java源码解析容器类ListSetMap

Java容器(ListSetMap)知识点快速复习手册(下)

Java容器(ListSetMap)知识点快速复习手册(上)

Java 集合Hashtable源码深入解析

Java 集合Hashtable源码深入解析

Java深入研究2LinkedList源码解析