Java集合超详解

一、Java集合概述

Java集合可以分为Collection和Map两种体系

java.util.Collection:单列数据,定义了存取一组对象的方法的集合

      ------------子接口:List:存储有序的、可重复的数据

                  --------------ArrayList(主要实现类)、LinkedList、Voctor

      ------------子接口:Set:存储无序的、不可重复的数据

                  --------------HashSet(主要实现类)、LinkedHashSet、TreeSet

Java.util.Map:双列数据,保存具有映射关系“key-value”对的集合

       ------------HashMap(主要实现类)、LinkedHashMap、TreeMap、Hashtable、Properties

Collection接口方法:

1.添加:add(object obj) add(Collection coll)

2.获取有效元素的个数:int size()

3.清空集合:void clear()

4.是否为空集:boolean isEmpty()

5.是否包含某个元素:

boolean contains(Object obj):通过元素的equals的方法来判断是否是同一个对象

boolean containsAll(Collection c):通过调用元素的equals方法来比较,那两个集合的元素 挨个比较

6.删除:

remove(Object obj):通过元素的equals方法判断是否是要删除的那个元素,只会删除找到的 第一个元素
remove(Collection c):取当前集合的差集

7.取两个集合的交集:retainAll(Collection coll):把的结果存放在当前集合中,不影响coll

8.转成对象数组:toArray()

9.获取集合对象的哈希值:hashCode()

List:

二、List简介

  List是有序集合,使用List可以控制列表中每个元素的插入位置,可以通过整数索引(列表中的位置)访问元素,并搜索列表中的元素。

  List通常允许重复的元素。

  使用List存储的特点:元素有序、可重复。

  List最常见的实现方式是ArrayList和LinkedList。以下是List接口常见实现类的对比:

数据元素在内存中,主要有2种存储方式:顺序存储和链式存储。

1、顺序存储

  这种方式,相邻的数据元素存放于相邻的内存地址中,整块内存地址是连续的。可以根据元素的位置直接计算出内存地址,直接进行读取。读取一个特定位置元素的平均时间复杂度为O(1)。正常来说,只有基于数组实现的集合,才有这种特性。

  在List的实现类中,顺序存储以ArrayList为代表。

2、链式存储

  这种方式,每一个数据元素,在内存中都不要求处于相邻的位置,每个数据元素包含它下一个元素的内存地址。不可以根据元素的位置直接计算出内存地址,只能按顺序读取元素。读取一个特定位置元素的平均时间复杂度为O(n)。主要以链表为代表。

  在List的实现类中,链式存储以LinkedList为代表。

三、遍历List

  对于List集合的实现类,常见的遍历方式是:for循环、for each遍历和迭代器遍历。

2.1 不同遍历方式的使用

1、for循环遍历

  基于计数器。在集合外部维护一个计数器,然后依次读取每一个位置的元素,当读取到最后一个元素后停止。这种遍历方式主要就是需要按元素的位置来读取元素。示例:

	for (int i = 0; i < list.size(); i++) {
	    list.get(i);
	}

2、迭代器遍历

  示例:

	Iterator iterator = list.iterator();
	while (iterator.hasNext()) {
	    iterator.next();
	}

3、foreach 循环遍历

  foreach内部也是采用了Iterator的方式实现,使用时不需要显式声明Iterator或计数器。优点是代码简洁,不易出错;缺点是只能做简单的遍历,不能在遍历过程中操作数据集合,例如删除、替换。示例:

	for (String str : list) {
	}

2.2 不同遍历方式的适用场合

1、for循环遍历

  for循环遍历,适用于遍历顺序存储集合,因为读取性能比较高。扩展了说,for循环遍历适合实现了RandomAccess接口的集合。如果一个数据集合实现了该接口,就意味着它支持顺序访问,按位置读取元素的平均时间复杂度为 O(1),如ArrayList。如果没有实现该接口,表示不支持顺序访问,如LinkedList。

2、迭代器遍历

  顺序存储:如果不是太在意时间,推荐选择此方式,毕竟代码更加简洁。

  链式存储:平均时间复杂度降为O(n),所以推荐此种遍历方式。

  使用迭代器更加线程安全,因为它可以确保,在当前遍历的集合元素被更改的时候,它会抛出ConcurrentModificationException。

  综合而言,推荐的做法就是,支持顺序访问的List可用for循环遍历,否则建议用Iterator或foreach遍历。此外,如果需要在遍历时修改元素,则需要使用迭代器的遍历方式,否则会出现ConcurrentModificationException。

三、常见List实现类比较

3.1 ArrayList和LinkedList

  LinkedList是一个双链表,在添加和删除元素时具有比ArrayList更好的性能,但在get与set方面弱于ArrayList。当然,这些对比都是指数据量很大或者操作很频繁。

  综合来说,在需要频繁读取集合中的元素时,更推荐使用ArrayList,而在插入和删除操作较多时,更推荐使用LinkedList。

3.2 ArrayList和Vector

  这两个类都实现了List接口,他们都是有序集合。相同点:

ArrayList和Vector都是继承了相同的父类和实现了相同的接口。

底层都是数组实现的。

初始默认长度都为10。

迭代器的实现都是fail-fast的。

  ArrayList和Vector的不同点:

ArrayList与Vector都可以设置初始的空间大小, Vector还可以设置增长的空间大小,而ArrayList 没有提供设置增长空间的方法。
ArrayList更加通用,因为我们可以使用Collections工具类轻易地获取同步列表和只读列表。

ArrayList

ArrayList介绍

简单来说,ArrayList是动态数组(数组的容量会随着元素数量的增加而增加,即动态变化)。
ArrayList的继承关系:

	public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

在ArrayList中,并没有规定特殊的元素操作规则(比如只能在数组两端进行增删等),所以ArrayList是操作非常自由的动态数组:

  1. 可以在数组头部 / 尾部 / 中间等任意位置插入、删除元素。
  2. 添加单个或集合中的元素时,未指明添加位置时,都是添加在尾部。
  3. 不会像队列那样在增加、取出元素时可能会产生阻塞现象。
1.1 ArrayList的特点

1、自动扩容

  ArrayList是动态数组,这里的“动态”说的就是可以自动扩容。ArrayList默认初始容量为10,当容量不足时可以自动扩容(1.5倍扩容,即:原来大小+原来大小*0.5)。

  ArrayList的初始容量可以指定,扩容倍数(1.5)不能制定。

2、线程不安全

  ArrayList是线程不安全的,也实现了fail-fast机制。

3、访问元素效率高,增删元素效率低

  ArrayList实现了RandomAccess接口,支持随机访问。get、set、isEmpty等方法的时间复杂度都是O(1),即遍历和随机访问元素效率比较高;add的时间复杂度是O(n),添加和删除元素效率比较低。

4、可添加null,有序可重复

  ArrayList在添加元素时,并不需要进行非空判定,所以可以是null。ArrayList内的元素是有序的,所以也可重复。

5、元素下标是从0开始

  因为ArrayList底层实现是数组,所以下标是从0开始。

6、大量使用System.arraycopy()

  ArrayList是动态数组,所以肯定会有元素拷贝动作,ArrayList的实现中大量地调用了Arrays.copyof()和 System.arraycopy()方法,其实Arrays.copyof()内部也是调用 System.arraycopy()。System.arraycopy()为Native方法。

7、支持克隆和序列化

  ArrayList实现了Cloneable接口,支持克隆。

  ArrayList实现了Serializable接口,支持序列化,能通过序列化去传输。

1.2 ArrayList的使用
  • 1、构造方法
    可以指定初始容量,也可以不指定。
	//构造一个初始容量为10的空ArrayList
	//(其实是构造了一个容量为空的ArrayList,第一次添加元素时,扩容为10)
	public ArrayList()
	//构造具有指定初始容量的空ArrayList  
	public ArrayList(int initialCapacity)
  • 2、添加元素
    可以直接添加到尾部,也可以添加到指定位置。
	//将元素追加到此ArrayList的尾部
	public boolean add(E e)
	//在指定位置插入元素
	public void add(int index, E element)
  • 3、删除元素
    可以删除某个元素,也可以清空ArrayList。
	//删除指定位置的元素
	public E remove(int index)
	//从ArrayList中删除第一个出现指定元素的(如果存在)
	public boolean remove(Object o)
	//清空ArrayList
	public void clear()
  • 4、获取元素
    根据下标获取对应位置的元素,和数组类似。
	public E get(int index)
  • 5、获取指定元素的下标
    可以从前向后,也可以从后向前,查找某个元素在ArrayList中的下标。
	//返回指定元素的第一次出现的索引,如果此ArrayList不包含元素,则返回-1
	public int indexOf(Object o)
	//返回指定元素的最后一次出现的索引,如果此ArrayList不包含元素,则返回-1
	public int lastIndexOf(Object o)
  • 6、替换元素
    和数组类似,替换指定下标的元素,并且将原下标对应的元素返回。
	//替换元素,返回原有元素
	public E set(int index, E element)
  • 7、截取ArrayList
    截取ArrayList的子串。
	//返回此ArrayList中指定的 fromIndex (包括)和 toIndex(不包括)之间的子ArrayList
	public List< E > subList(int fromIndex, int toIndex)

8、通用性方法
判断ArrayList是否为空、获取总的元素数等。

	//判断ArrayList是否为空
	public boolean isEmpty()
	//返回ArrayList中的元素数
	public int size()
	//判断是否包含某个元素
	public boolean contains(Object o)

LinkedList

LinkedList介绍

简单来说,LinkedList是一个双向链表:

LinkedList中存放的不是普通的某个中类型的元素,而是节点(Node)。

  LinkedList通过prev、next将不连续的内存块串联起来使用。LinkedList是双向链表,除头节点的每一个元素都有prev(前驱指针)同时再指向它的上一个元素,除尾节点的每一个元素都有next(后继指针)同时再指向它的下一个元素。

  LinkedList的继承关系:

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

1、底层实现是双向链表

  LinkedList内部是一个双向链表(可以双向遍历)的实现,一个节点除了存储自身的数据外,还持有前、后两个节点的引用。

2、增删快、查询慢

  LinkedList具有对前后元素的引用,删除、插入节点很快,因为不需要移动其他元素,只需要改变部分节点的引用即可。

  LinkedList元素存储地址不连续,不支持随机访问,所以查询速度相比于ArrayList,是比较慢的。

3、线程不安全

  LinkedList是线程不安全的,也实现了fail-fast机制。

4、元素有序可重复

  LinkedList中的前后指针可以保证元素的顺序,因此可以重复。

5、删除、添加操作时间复杂度为O(1),查找时间复杂度为O(n)

  查找函数有一定优化,容器会先判断查找的元素是离头部较近,还是尾部较近,来决定从头部开始遍历还是尾部开始遍历,因此推荐使用迭代器进行遍历。

6、实现了Deque接口,因此也可以作为栈、队列和双端队列来使用

  LinkedList在首尾两端都可以操作,因此可以充当栈、队列和双端队列的实现工具。

7、可以存储Null值

  Node中的item可以为null。

1.2 LinkedList的使用
  • 1、 构造方法
    一般构造空链表。
	//构造一个空链表
	public LinkedList()
	//用已有的集合创建链表的构造方法
	public LinkedList(Collection<? extends E> c) 

2、添加元素

  因为LinkedList是双向链表,在首尾都可以操作,所以从首部和尾部添加元素均可。添加元素的方法分为3类:addXxxx、offerXxx、push。

  addXxxx:可在首部尾部添加,无返回值。

  offerXxx:可在首部尾部添加,有返回值。

  push:在首部添加,无返回值。

	//将元素追加到此链表末尾
	public boolean add(E e)
	//在链表的指定位置插入元素
	public void add(int index, E element) 
	//在链表的首部插入元素
	public void addFirst(E e)
	//将元素追加到链表末尾
	public void addLast(E e)
	//将元素添加到链表的尾部
	public boolean offer(E e)
	//在链表的首部插入元素
	public boolean offerFirst(E e)
	//在链表的末尾插入元素
	public boolean offerLast(E e)
	//在链表的头部添加元素
	public void push(E e)

3、删除元素

  因为LinkedList是双向链表,在首尾都可以操作,所以从首部和尾部删除元素均可。添加元素的方法分为3类:removeXxx、clear。

  removeXxx:删除后可以返回被删除的元素,也可以返回表示删除结果的boolean值。

  clear:删除所有元素,即清空链表。

	//删除链表中指定位置的元素
	public E remove(int index)
	//从链表中删除指定元素的第一个出现(如果存在)
	public boolean remove(Object o)
	//从链表中删除并返回第一个元素
	public E removeFirst()
	//删除链表中指定元素的第一个出现(从头到尾遍历列表时)
	public boolean removeFirstOccurrence(Object o)
	//从链表中删除并返回最后一个元素
	public E removeLast()
	//删除链表中指定元素的最后一次出现(从头到尾遍历列表时)
	public boolean removeLastOccurrence(Object o)
	//清空链表
	public void clear()
  • 4、检索并删除元素
    删除首部/尾部的元素,并把该元素返回。分为pop、remove、pullXxx3类。
    pop/remove:删除并返回链表首部的元素。
    pullXxx:可以删除并返回首部/尾部的元素。
	//检索并删除链表的首部元素
	public E poll()
	//检索并删除链表的第一个元素,如果此LinkedList为空,则返回 null 
	public E pollFirst()
	//检索并删除链表的最后一个元素,如果链表为空,则返回 null 
	public E pollLast()
	//删除并返回链表的第一个元素
	public E pop()
	//检索并删除链表的首部元素
	public E remove()
  • 5、查找元素
    仅查找、不删除元素,有element、getXxx、peekXxx3类。
	//检索但不删除链表首部元素
	public E element()
	//返回链表指定位置的元素
	public E get(int index)
	//返回链表中的第一个元素
	public E getFirst()
	//返回链表中的最后一个元素
	public E getLast()
	//获取且不删除链表首部节点中的元素
	public E peek()	
	//检索但不删除链表的第一个元素,如果链表为空,则返回 null 
	public E peekFirst()
	//检索但不删除链表的最后一个元素,如果链表为空,则返回 null 
	public E peekLast()
  • 6、检索元素位置
    从前向后、从后向前
	//返回链表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1
	public int indexOf(Object o)
	//返回链表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1
	public int lastIndexOf(Object o)
  • 7、替换元素
    用指定的元素替换链表中指定位置的元素,并返回被替换的元素。
	public E set(int index, E element)
  • 8、通用性方法
    获取链表元素的个数等。
	//返回链表中的元素数
	public int size()
	//判断链表中是否包含某个元素
	public boolean contains(Object o)

Vector

一、Vector介绍

Vector的继承关系:

	public class Vector<E> extends AbstractList<E>
    	implements List<E>, RandomAccess, Cloneable, java.io.Serializable

 Vector与ArrayList很相似,也是基于数组来实现,与ArrayList相比,Vector是线程安全的(在每个方法上几乎都增加了synchronized关键字来实现线程间的同步,也正因为如此,Vector的效率会比较低)。可以简单地将Vector理解为线程安全版的ArrayList。

1.1 Vector的特点

1、底层实现与ArrayList相似

  Vector底层也是数组,所以也支持随机访问。

2、线程安全

  Vector与ArrayList最大的不同就在于其是线程安全的,元素修改的方法都是用synchronized关键字修饰的。

3、扩容时容量与ArrayList不同

  ArrayList不可以设置扩展的容量,默认1.5倍。

  Vector可以设置扩展的容量,如果没有设置,默认2倍。

4、初始容量与ArrayList不同

  ArrayList的无参构造方法中初始容量为0(自动扩容时才扩容为10),而Vector的无参构造方法中初始容量 为10。可以简单地理解为这两个容器的初始容量都是10。

1.2 Vector的使用

  • 1、构造方法

  在构造Vector对象时,可以指定初始容量initialCapacity和每次的增量capacityIncrement。当capacityIncrement小于等于0时,数组成倍扩容;当capacityIncrement大于0时,数组扩容时就增加相对的数值。

	//构造一个空Vector,其内部数据数组的大小为10 ,标准容量增量为0
	public Vector()
	//构造具有指定初始容量,并且其容量增量等于0的空Vector
	public Vector(int initialCapacity)
	//构造具有指定的初始容量和容量增量的空Vector
	public Vector(int initialCapacity, int capacityIncrement)
  • 2、添加元素
    添加的元素的方式和ArrayList类似,不过每个方法上多了synchronized ,保证线程安全。
	//将指定的元素追加到此Vector的末尾
	public synchronized boolean add(E e)
	//在此Vector中的指定位置插入指定的元素
	public void add(int index, E element)
	//将指定的元素添加到此向量的末尾
	public synchronized void addElement(E obj)
	//在指定的index位置插入指定元素
	public synchronized void insertElementAt(E obj, int index)
  • 3、返回此Vector的当前元素个数
	public synchronized int capacity()
  • 4、清空Vector
	public void clear()
  • 5、判断是否包含某元素
	public boolean contains(Object o)
  • 6、获取元素
	//返回指定位置的元素
	public synchronized E elementAt(int index)
	//返回此Vector的第一个元素
	public synchronized E firstElement()
	//返回此Vector中指定位置的元素
	public synchronized E get(int index)
	//获取Vector中的最后一个元素
	public synchronized E lastElement()
  • 7、比较两个Vector是否相等
	public synchronized boolean equals(Object o)
  • 8、支持for each循环
	public synchronized void forEach(Consumer<? super E> action)
  • 9、检索元素位置
	//返回此Vector中指定元素的第一次出现的索引,如果此向量不包含元素,则返回-1
	public int indexOf(Object o)
	//返回此Vector中指定元素的第一次出现的索引,从 index向前检索,如果未找到
	//该元素,则返回-1
	public synchronized int indexOf(Object o, int index)
	//返回此Vector中指定元素的最后一次出现的索引,如果此向量不包含元素,则返回-1
	public synchronized int lastIndexOf(Object o)
	//返回此Vector中指定元素的最后一次出现的索引,从 index开始 ,如果未找到
	//元素,则返回-1
	public synchronized int lastIndexOf(Object o, int index)
  • 10、Vector是否为空
	public synchronized boolean isEmpty()
  • 11、返回一个迭代器
	public synchronized Iterator< E > iterator()
  • 12、删除元素
	//删除此Vector中指定位置的元素
	public synchronized E remove(int index)
	//删除此Vector中第一个出现的指定元素,如果Vector不包含该元素,则不会更改
	public boolean remove(Object o)
	//删除指定索引处的元素
	public synchronized void removeElementAt(int index)
  • 13、替换元素
	//用指定的元素替换此Vector中指定位置的元素
	public synchronized E set(int index, E element)
	//用指定的元素替换此Vector中指定位置的元素
	public synchronized void setElementAt(E obj, int index)
  • 14、返回此Vector中的元素数
	public synchronized int size()

List实现类源码分析

jdk7版本ArrayList源码解析

//底层会初始化数组,数组长度为10 Object[] elementDate = new Object[10];
ArrayList <Sting> list = new ArrayList<>();
	list.add("AA");//elementDate[0] = "AA";
	list.add("BB");//elementDate[1] = "BB";
	...
//当要添加第11个元素时,底层的elementDate数组已满,则需要扩容。
//默认扩容长度为原来长度的1.5倍,并将原有数组中的元素复制到新数组中。

jdk8版本ArrayList源码解析

//底层会初始化数组 Object elementDate = new Object[]{};
ArrayList <String> list = new ArrayList<>();
	list.add("AA");//首次添加元素时,会初始化数组elementDate = new Object[10];  
                	//elementDate[0] = "AA";
	list.add("BB");//elementDate[1[ = "BB";
	...
//当要添加第11个元素时,底层的elementDate数组已满,则需要扩容。
//默认扩容长度为原来长度的1.5倍,并将原有数组中的元素复制到新数组中。

Vector源码解析(jdk1.8)

//底层初始化数组,长度为10  Object[] elementDate = new Object[10];
Vector v = new Vector();
v.add("AA");//elementDate[0] = "AA";
v.add("BB");//elementDate[1] = "BB";
...
//当添加到第11个元素时,需要扩容。默认扩容为原来的2倍.

LinkedList源码解析

LinkedList <String> = new LinkedList<>();
list.add("AA");//将"AA"封装到一个Node对象1中,list对象的属性first、last都指向此Node对象1
list.add("BB");//将"BB"封装到一个Node对象2中,对象1和对象2构成一个双向链表,
            	//同时last指向此Node对象2
...
//因为LinkedList使用的是双向链表,不需要考虑扩容问题

//LinkedList内部声明:
  private static class Node<E>{
      E item;
      Node<E> next;
      Node<E> prev;
  }

//添加的内部声明
  void linkLast(E e){
      final Node<E> l = last;
      final Node<E> newNode = new Node<>(l,e,next:null);
	  last = new newNode;
      if(l = null)
          first = newNode;
	  else
          l.next = newNode;
      size++;
	  modCount++;
}

开发建议:

1.ArrayList底层使用数组结构,查找和添加(尾部添加)操作效率高,时间复杂度为O(1),删除和插入操作效率 低,时间复杂度为O(n)。

LinkedList底层使用双向链表结构,删除和插入操作效率高,时间复杂度为O(1),查找和添加(尾部添加)操作效 率低,时间复杂度为O(n)。

2.在选择了ArrayList的前提下,new ArrayList():底层创建长度为10的数组

new ArrayList(int capacity):底层创建指定capacity长度的数组

在开发中,如果大体确认数组的长度,则推荐使用ArrayList(int capacity)构造器,避免了底层的扩容,复制数 组的操作。

四、Map简介

一、Map简介

Collection接口的实现类中存储的是具体的单个元素,Map中存储的是键值对。
常用的Map实现类有:HashMap、LinkedHashMap、TreeMap、HashTable和ConcurrentHashMap。

二、常见Map实现类比较

2.1 HashMap和TreeMap

HashMap

一、HashMap介绍

  HashMap是基于哈希表的Map实现,其底层结构是数组,但数组中存的不是普通的某种类型的元素,存储的是链表或红黑树(1.7是数组+链表,1.8则是数组+链表+红黑树结构)。

哈希表

  哈希表可以简单理解为哈希表=哈希函数+数组。比如一个键值对(key,value),哈希函数可以通过key计算出一个值,这个值就是数组的下标index,然后在数组的index把value存进去,这样就完成了一个键值对的存储。

  HashMap使用哈希表来存储的,哈希表为解决冲突,可以采用开放地址法和链地址法等来解决,HashMap采用了链地址法。链地址法简单来说就是数组加链表的结合,在每个数组元素上都有一个链表结构,当数据被hash后,得到数组下标位置,把数据放在对应数组下标元素的链表上。

HashMap里面是一个数组,数组中的每个元素是一个单向链表。上图中,每个绿色的实体是嵌套类Entry的实例,Entry包含四个属性:key、value、hash和用于单向链表的next。

JDK1.8中的HashMap结构

  JDK1.8中对HashMap进行了一些修改,最大的不同就是利用了红黑树,所以其由数组+链表+红黑树组成。

  在JDK1.7中的HashMap中,查找元素时,根据hash值能够快速定位到数组的具体下标。之后需要顺着链表一个个比较下去才能找到我们需要的元素,时间复杂度取决于链表的长度,为 O(n)。为了降低这部分的开销,在JDK1.8中,当链表中的元素超过了8个以后,会将链表转换为红黑树,在这些位置进行查找的时候可以降低时间复杂度为 O(logN)。

  JDK1.8版本里,HashMap的结构示例:

1.1 HashMap的特点

1、底层实现是 链表+数组+红黑树(JDK 1.8)

  JDK 1.8的HashMap的数据结构是数组为主干,链表或红黑树为辅助(链表节点较少时仍然是以链表存在,当链表节点较多时(大于8)会转为红黑树)。当一个元素要存储HashMap时,先通过哈希方法找到要存入的数组的下标,然后将value存在对应的位置上。

  JDK1.8中引入红黑树,提高了HashMap的性能。

2、HashMap 的底层是个Node(键值对) 数组

  在数组的具体索引位置,如果存在多个节点,则可能是以链表或红黑树的形式存在。

3、HashMap的key和value都允许为空

4、HashMap的key不允许重复

  当向HashMap中存入key相同的数据时,后者会覆盖前者,value允许重复。

5、非线程安全

  HashMap是非线程安全的,在并发场景下使用线程安全的集合ConcurrentHashMap来代替。

6、元素无序

  此处的无序指的是遍历HashMap中元素的顺序和存入HashMap中元素的顺序是基本不一致的(HashMap的遍历顺序是不确定的)。

HashMap中元素的特点:

1、HashMap中的所有的key彼此之间是不可以重复的,无序的。所有的key就构成了一个Set集合

2、HashMap中的所有value之间是可重复的、无序的。所有的value就构成了一个Collection集合

3、HashMap中的一个key-value,就构成了一个entry

4、HashMap中的所有entry彼此之间是不可重复的、无序的。所有的entry就构成了一个Set集合

1.2 HashMap的使用
  • 1、构造方法
	//构造一个空的 HashMap ,具有默认初始容量(16)和默认负载因子(0.75)
	public HashMap()
	//构造一个空的 HashMap,具有指定的初始容量和默认负载因子(0.75)
	public HashMap(int initialCapacity)
	//构造一个空的 HashMap,具有指定的初始容量和负载因子
	public HashMap(int initialCapacity, float loadFactor)
  • 2、遍历
    //获取HashMap中key的集合,用来遍历key
	public Set< K > keySet()
	//返回HashMap中Entry构成的Set,用来遍历<key,value>键值对
	public Set<Map.Entry<K,V>> entrySet()
	//返回此HashMap中包含的value的集合
	public Collection< V > values()
  • 3、获取某个key对应的value
	//返回到指定key所映射的value,不存在该key则返回null
	public V get(Object key)
	//返回到指定key所映射的值,不存在该key则返回默认值
	public V getOrDefault(Object key, V defaultValue)	
  • 4、添加键值对
	//存入一个键值对,如果key存在则替换原有的键值对
	public V put(K key, V value)
  • 5、删除键值对
	//从该Map中删除指定key的映射
	public V remove(Object key)
	//删除指定key和指定value构成的键值对
	public boolean remove(Object key, Object value)	
  • 6、键值对替换
	//替换指定key的value
	public V replace(K key, V value)
	//只有当指定key和指定value构成键值对时,才替换指定的value
	public boolean replace(K key, V oldValue, V newValue)
  • 7、包含判断
	//如果此HashMap包含指定key,则返回true
	public boolean containsKey(Object key)
	//如果此HashMap包含指定value,则返回true
	public boolean containsValue(Object value)
  • 8、其他
	//判断HashMap中是否包含元素
	public boolean isEmpty()
	//清空HashMap
	public void clear()	
	//返回此HashMap中键值对的数量
	public int size()	

LinkedHashMap

一、LinkedHashMap介绍

  LinkedHashMap是有序版本的HashMap。LinkedHashMap是HashMap的子类,所以LinkedHashMap自然会拥有HashMap的所有特性。同时,HashMap是无序的,即迭代HashMap所得到的元素顺序并不是它们最初添加到HashMap的顺序。而LinkedHashMap可以保证迭代元素的顺序与存入容器的顺序一致。

  本质上,HashMap和双向链表合二为一就是LinkedHashMap。更准确地说,它是一个将所有Entry节点链入一个双向链表双向链表的HashMap。

  一般来说,如果需要使用的Map中的key无序,选择HashMap;如果要求key有序,则选择TreeMap。但是选择TreeMap会有性能问题,因为TreeMap的get操作的时间复杂度是O(log(n))的,相比于HashMap的O(1)还是差不少的,LinkedHashMap的出现就是为了平衡这些因素,使得能够以 O(1)时间复杂度增加查找元素,又能够保证key的有序性。

  在HashMap有一些空方法,比如:

    void afterNodeAccess(Node<K,V> p) { }
    void afterNodeInsertion(boolean evict) { }
    void afterNodeRemoval(Node<K,V> p) { }

LinkedHashMap重写了这些方法,用来保持列表的有序。


1.1 LinkedHashMap特点

1、由于继承HashMap类,所以默认初始容量是16,加载因子是0.75。

2、线程不安全。

3、具有fail-fast的特征。

4、底层使用双向链表,可以保存元素的插入顺序,顺序有两种方式:一种是按照插入顺序排序,一种按照访问顺序做排序(可以做LRU策略的实现类)。默认以插入顺序排序。

5、key和value允许为null,key重复时,新value覆盖旧value,即:最多只允许一条Entry的键为null。

6、可以用来实现LRU算法。

7、LinkedHashMap与HashMap的存取数据操作基本是一致的,只是增加了双向链表保证数据的有序性。

8、LinkedHashMap继承HashMap,基于HashMap+双向链表实现。(HashMap是数组+链表+红黑树实现的)。

1.2 LinkedHashMap的使用

  由于LinkedHashMap继承自HashMap,所以HashMap有的方法LinkedHashMap也有,特殊的在于LinkedHashMap中有序性的选择。

  因为LinkedHashMap元素的有序性可分为插入顺序性和访问顺序性,所以可以在创建对象时,指定选择哪种顺序。accessOrder为false时,基于插入顺序;accessOrder为true时,基于访问顺序。

TreeMap

一、TreeMap介绍

  TreeMap是一个能比较元素大小的Map容器,会对传入的key进行了大小排序。可以使用元素的自然顺序,也可以使用集合中自定义的比较器来进行排序。TreeMap是一个通过红黑树实现有序的K-V集合。

  对于TreeMap而言,由于它底层采用一棵”红黑树”来保存集合中的Entry,这意味这TreeMap添加元素、取出元素的性能都比HashMap低。当向TreeMap中添加元素时,需要通过循环找到新增Entry的插入位置,因此比较耗性能;当从TreeMap中取出元素时,需要通过循环才能找到合适的Entry,也比较耗性能。

  TreeMap比HashMap的优势在于:TreeSet 中所有元素总是根据key的某种指定排序规则保持有序状态。

  TreeMap底层基于红黑树实现,可保证在log(n)时间复杂度内完成containsKey、get、put 和 remove 操作,效率很高。

  TreeMap的核心是红黑树,其很多方法也是对红黑树增删查基础操作的一个包装。

  为了理解TreeMap的底层实现,需要先了解排序二叉树和红黑树这两种数据结构。其中红黑树又是一种特殊的排序二叉树。排序二叉树是一种特殊结构的二叉树,可以非常方便地对树中所有节点进行排序和检索。

1.1TreeMap的特点

1、TreeMap默认会对键进行排序,根据键的自然顺序进行(升序)排序或根据提供的Comparator进行排序。

2、TreeMap底层使用的数据结构是二叉树。

3、TreeMap是线程不安全的。

4、 TreeMap的key不能为null。

5、TreeMap的查询、插入、删除效率均没有HashMap高,一般只有要对key排序时才使用TreeMap。

6、迭代器是fail-fast的。

1.2TreeMap的使用
	//使用key的自然排序
	public TreeMap() 
	//指定的比较器
	public TreeMap(Comparator<? super K> comparator)
  • 2、遍历
	//返回key集合
	public Set<K> keySet()
	//返回value集合
	public Collection<V> values()
	//返回键值对形成的集合
	public Set<Map.Entry<K,V>> entrySet()
  • 3、判断是否包含指定key/value
	//判断是否包含指定key
	public boolean containsKey(Object key)
	//判断是否包含指定value
	public boolean containsValue(Object value)
  • 4、获取特定key
	//返回大于等于给定键的最小键
	public K ceilingKey(K key)
	//返回小于等于给定key的最大key对应的键
	public K floorKey(K key)
	//返回大于指定key的最小key对应的键
	public K higherKey(K key)	
	//返回最大key对应的键
	public K lastKey()	
	//返回小于指定key的最大key对应的键
	public K lowerKey(K key)
	//返回最小key
	public K firstKey()		

5、获取特定K-V

	//返回大于等于给定键的最小键对应的键值对
	public Map.Entry<K,V> ceilingEntry(K key)
	//返回小于等于给定key的最大key对应的键值对
	public Map.Entry<K,V> floorEntry(K key)
	//返回大于指定key的最小key对应的键值对
	public Map.Entry<K,V> higherEntry(K key)	
	//返回最大key对应的键值对
	public Map.Entry<K,V> lastEntry()	
	//返回小于指定key的最大key对应的键值对
	public Map.Entry<K,V> lowerEntry(K key)	
	//返回最小key对应的键值对
	public Map.Entry<K,V> pollFirstEntry()	
  • 6、添加键值对
	public V put(K key, V value)
  • 7、删除指定key对应的键值对
	public V remove(Object key)
  • 8、替换K-V
	//替换指定key对应的value
	public V replace(K key, V value)
	//当键值对都相同时,替换value
	public boolean replace(K key, V oldValue, V newValue)
  • 9、返回比较器
	public Comparator<? super K> comparator()

HashSet

一、HashSet介绍

  HashSet是一个无序集合,其底层结构是HashMap,简单来说,HashSet是value是固定值(Object PRESENT = new Object())的HashMap。

1.1HashSet的特点

1、HashSet的底层实现是HashMap(HashSet的值存放于HashMap的key上,HashMap的value是一个统一的值)。

2、HashSet中的元素无序且不能重复(从插入HashSet元素的顺序和遍历HashSet的顺序对比可以看出:遍历顺序和存入到Set的顺序并不一致)。

3、HashSet是线程不安全的。如果要保证线程安全,其中一种方法是将其改造成线程安全的类,示例:

	Set set = Collections.synchronizedSet(new HashSet(...));
1.2 HashSet常的使用
  • 1、构造方法
	//默认初始容量为16,负载因子为0.75
	public HashSet()
	//指定初始容量,负载因子为0.75
	public HashSet(int initialCapacity)
	//指定初始容量和负载因子
	public HashSet(int initialCapacity, float loadFactor)
  • 2、添加元素
	public boolean add(E e)
  • 3、删除元素
	public boolean remove(Object o)
  • 4、判断是否包含某元素
	public boolean contains(Object o)
  • 5、其他方法
//清空集合
public void clear()
//判断Set是否为空
public boolean isEmpty()	
//获取迭代器
public Iterator<E> iterator()	
//返回此集合中的元素数
public int size()	

LinkedHashSet

一、LinkedHashSet介绍

  LinkedHashSet是有序集合,其底层是通过LinkedHashMap来实现的,LinkedHashMap其实也就是value是固定值的LinkedHashMap。因此LinkedHashSet中的元素顺序是可以保证的,也就是说遍历序和插入序是一致的。

  LinkedHashSet继承了HashSet。

1.1 LinkedHashSet的特点

1)底层是用LinkedHashMap来实现的。
2)线程不安全 。
3)元素有序,是按照插入的顺序排序的。
4)最多只能存一个null。

HashMap源码解析

jdk7中创建对象和添加数据过程

//创建对象的过程中,底层会初始化数组Entry[] table = new Entry[16];
HashaMap<String,Integer> map = new HashMap<>();
map.put("AA",78);//"AA"和78封装到一个Entry对象中,考虑将此对象添加到table数组中
...

添加/修改过程:

将(key1,value1)添加到当前的map中:

首先需要调用key1所在类的hashCode()方法,计算key1对应的哈希值1,此哈希值1经过某种算法(hash())之后,得到哈希值2,哈希值2再经过某种算法(indexFor())之后,就确定了(key1,value1)在数组table中的索引位置i

1.1如果此索引位置i的数组上没有元素,则(key1,value1)添加成功——>情况一

1.2如果此索引位置i的数组上有元素(key2,value2),则需要继续比较key1

和key2的哈希值2——>哈希冲突

2.1如果key1和key2de哈希值2不同,则(key1,value1)添加成功——>情况2

2.2如果key1与key2的哈希值2相同,则需要继续比较key1和key2的equals(),要调用key1所在类 的equals(),将key2作为参数传递进去

3.1调用equals(),返回false:则(key1,value1)添加成功——>情况三

3.2调用equals(),返回true:则认为key1和key2是相同的,默认情况下,value1会替换value2

情况一:将(key1,value1)存放到数组的索引i的位置

情况二,情况三:(key1,value1)元素与现有的(key2,value2)构成单项链表结构,

(key1, value1)指向(key2,value2)

随着不断地添加元素,在满足如下的条件的情况下会考虑扩容:
( size >= threshold ) && ( null != table[i] )

当元素的个数达到临界值(数组的长度*加载因子)时,就考虑扩容:

默认临界值=16*0.75=12 默认扩容为原来的2倍

jdk8与jdk7的不同之处

  1. 在jdk8中,我们创建了HashMap实例以后,底层并没有初始化table数组。当首次添加(key,value)时,进行判断,如果发现table尚未初始化,则对数组进行初始化。
  2. 在jdk8中,HashMap底层定义了Node内部类,替换jdk7中的Entry内部类。意味着,意味着我们创建的数组时Node[]。
  3. 在jdk8中,如果当前的(key,value)经过一系列判断之后,可以添加到当前的数组角标i中。如果此时角标i位置上有元素。在jdk7中时将新的 (key,value)指向自己有的旧的元素(头插法),而在jdk8中是旧的元素指向新的(key,value)元素。”七上八下“
  4. jdk7:数组+单向链表

jdk8:数组+单向链表+红黑树

什么时候使用单向链表变为红黑树?

如果数组索引i的位置上的元素的个数达到8,并且数组的长度达到64时,我们就将索引i位置上的多个元素改为红黑树结构进行储存。

红黑树进行put()/remove()/get()操作的复杂度为o(logn),比单项链表o(n)好

什么时候红黑树变为单向链表?

当使用红黑树的索引i位置上的元素个数低于6时,就会将红黑树退化为单向链表。

红黑树占用内存为单向链表的2倍。

LinkedHashMap源码解析

LinkedHashMap重写了HashMap的如下方法:
Node<K,V> newNode(int hash, K key, V value, Node<K,V> e) {
    LinkedHashMap.Entry<K,V> p =
        new LinkedHashMap.Entry<K,V>(hash, key, value, e);
    linkNodeLast(p);
    return p;
}
底层架构

LinkedHashMap内部定义了一个Entry

static class Entry<K,V> extends HashMap.Node<K,V> {
        Entry<K,V> before, after;//增加的一对双向链表
        Entry(int hash, K key, V value, Node<K,V> next) {
            super(hash, key, value, next);
        }
    }

LinkedHashMap和HashMap的关系
  1. LinkedHashMap是HashMap的子类
  2. LinkedHashMap在HashMap使用数组+单向链表+红黑树的基础上,又增加了一对双向链表,记录添加的(key,value)的先后顺序,方便遍历。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值