(1)Map

Map主要用于存储健值对,根据键得到值,因此不允许键重复,但允许值重复。
Map接口概述:Java.util.Map接口:是一个双列集合
Map集合的特点: 是一个双列集合,有两个泛型key和value,使用的时候key和value的数据类型可以相同。也可以不同.
Key不允许重复的,value可以重复的;
一个key只能对应一个value
底层是一个哈希表(数组+单向链表):查询快,增删快, 是一个无序集合

Map接口中的常用方法:
1.get(key) 根据key值返回对应的value值,key值不存在则返回null
2.put(key , value); 往集合中添加元素(key和value)
  注意:添加的时候,如果key不存在,返回值null
  如果Key已经存在的话,就会新值替换旧值,返回旧值
3. remove(key); 删除key值对应的键值对;如果key不存在 ,删除失败。返回值为 null,如果key存在则删除成功,返回值为删除的value

Map遍历方式
第一种方式:通过key找value的方式:
   Map中有一个方法:
      Set keySet(); 返回此映射包含的键的Set 集合
   操作步骤:
   1.调用Map集合的中方法keySet,把Map集合中所有的健取出来,存储到Set集合中
   2.遍历Set集合,获取Map集合中的每一个健
   3.通过Map集合中的方法get(key),获取value值
    可以使用迭代器跟增强for循环遍历
第二种方式:Map集合遍历键值方式
    Map集合中的一个方法:
    Set> entrySet(); 返回此映射中包含的映射关系的Set视图
 使用步骤
     1.使用Map集合中的方法entrySet,把键值对(键与值的映射关系),取出来存储到Set 集合中
    
2.遍历Set集合,获取每一个Entry对象
     3.使用Entry对象中的方法getKey和getValue获取健和值
  可以使用迭代器跟增强for循环遍历

Collection中的集合元素是孤立的,可理解为单身,是一个一个存进去的,称为单列集合
Map中的集合元素是成对存在的,可理解为夫妻,是一对一对存进去的,称为双列集合

Map中存入的是:键值对,键不可以重复,值可以重复
Map主要用于存储带有映射关系的数据(比如学号与学生信息的映射关系)

Map没有继承Collection接口,Map提供key到value的映射。一个Map中不能包含相同的key,每个key只能映射一个 value。Map接口提供3种集合的视图,Map的内容可以被当作一组key集合,一组value集合,或者一组key-value映射。
Map具有将对象映射到其他对象的功能,是一个K-V形式存储容器,你可以通过containsKey()和containsValue()来判断集合是否包含某个减或某个值。Map可以很容以拓展到多维(值可以是其他容器甚至是其他Map):
*Map>

Map集合的数据结构仅仅针对键有效,与值无关。

(2)HashMap

HashMap非线程安全,高效,支持null;
根据键的HashCode 来存储数据,根据键可以直接获取它的值,具有很快的访问速度。遍历时,取得数据的顺序是完全随机的。
HashMap最多只允许一条记录的键为Null;允许多条记录的值为 Null。(不允许键重复,但允许值重复)
HashMap不支持线程的同步(任一时刻可以有多个线程同时写HashMap,即线程非安全),可能会导致数据的不一致。如果需要同步,可以用 Collections的synchronizedMap() 方法使HashMap具有同步的能力,或者使用ConcurrentHashMap。
Hashtable与 HashMap类似。不同的是:它不允许记录的键或者值为空;它支持线程的同步(任一时刻只有一个线程能写Hashtable,即线程安全),因此也导致了 Hashtable 在写入时会比较慢。
HashMap里面存入的值在取出的时候是随机的,它根据键的HashCode来存储数据,根据键可以直接获取它的值,具有很快的访问速度。在Map 中插入、删除和定位元素,HashMap 是最好的选择。
HashMap基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
值得注意的是HashMap不是线程安全的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap。
Map map = Collections.synchronizedMap(new HashMap());

HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。学过数据结构的同学都知道,解决hash冲突的方法有很多,HashMap底层是通过链表来解决hash冲突的。

HashMap其实也是一个线性的数组实现的,所以可以理解为其存储数据的容器就是一个线性数组。这可能让我们很不解,一个线性的数组怎么实现按键值对来存取数据呢?这里HashMap有做一些处理。首先HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。

HashMap是常用的Java集合之一,是基于哈希表的Map接口的实现。与HashTable主要区别为不支持同步和允许null作为key和value。由于HashMap不是线程安全的,如果想要线程安全,可以使用ConcurrentHashMap代替。
HashMap的底层是哈希数组,数组元素为Entry。HashMap通过key的hashCode来计算hash值,当hashCode相同时,通过“拉链法”解决冲突
相比于之前的版本,jdk1.8在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。原本Map.Entry接口的实现类Entry改名为了Node。转化为红黑树时改用另一种实现TreeNode。
16.jpg

1.8中最大的变化就是在一个Bucket中,如果存储节点的数量超过了8个,就会将该Bucket中原来以链表形式存储节点转换为以树的形式存储节点;而如果少于6个,就会还原成链表形式存储。
为什么要这样做?前面已经说过LinkedList的遍历操作不太友好,如果在节点个数比较多的情况下性能会比较差,而树的遍历效率是比较好的,主要是优化遍历,提升性能。

HashMap:去掉了contains(),保留了containsKey(),containsValue()
HashMap:key,value可以为空.null作为key只能有一个,null作为value可以存在多个
HashMap:使用Iterator
HashMap:数组初始大小为16,扩容方式为2的指数幂形式
HashMap:重新计算hash值

HashMap是基于哈希表的Map接口的实现,HashMap是一个散列表,存储的内容是键值对(key-value)映射,键值对都可为null;
HashMap继承自 AbstractMap 并实现 Map, Cloneable, Serializable接口;
HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。底层是个数组,数组上存储的数据是Entry类型的链表结构对象。
HashMap是无序的,LinkedHashMap和treeMap是有序的;

HashMap基于哈希原理,可以通过put和get方法存储和获取对象。当我们将键值对传递给put方法时,它调用键对象的hashCode()方法来计算hashcode,然后找到对应的bucket位置存储键对象和值对象作为Map.Entry;如果两个对象的hashcode相同,所以对应的bucket位置是相同的,HashMap采用链表解决冲突碰撞,这个Entry(包含有键值对的Map.Entry对象)会存储到链表的下一个节点中;如果对应的hashcode和key值都相同,则修改对应的value的值。HashMap在每个链表节点中存储键值对对象。当使用get()方法获取对象时,HashMap会根据键对象的hashcode去找到对应的bucket位置,找到对应的bucket位置后会调用keys.equals()方法去找到连表中对应的正确的节点找到对象。

HashMap是基于哈希表实现的,每一个元素是一个key-value对,其内部通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。
HashMap 实现了Serializable接口,因此它支持序列化,实现了Cloneable接口,能被克隆。
HashMap存数据的过程是:
HashMap内部维护了一个存储数据的Entry数组,HashMap采用链表解决冲突,每一个Entry本质上是一个单向链表。当准备添加一个key-value对时,首先通过hash(key)方法计算hash值,然后通过indexFor(hash,length)求该key-value对的存储位置,计算方法是先用hash&0x7FFFFFFF后,再对length取模,这就保证每一个key-value对都能存入HashMap中,当计算出的位置相同时,由于存入位置是一个链表,则把这个key-value对插入链表头。

HashMap中key和value都允许为null。key为null的键值对永远都放在以table[0]为头结点的链表中。
HashMap内存储数据的Entry数组默认是16,如果没有对Entry扩容机制的话,当存储的数据一多,Entry内部的链表会很长,这就失去了HashMap的存储意义了。所以HasnMap内部有自己的扩容机制。HashMap内部有:
变量size,它记录HashMap的底层数组中已用槽的数量;
变量threshold,它是HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量加载因子)
变量DEFAULT_LOAD_FACTOR = 0.75f,默认加载因子为0.75
HashMap扩容的条件是:当size大于threshold时,对HashMap进行扩容
扩容是是新建了一个HashMap的底层数组,而后调用transfer方法,将就HashMap的全部元素添加到新的HashMap中(要重新计算元素在新的数组中的索引位置)。 很明显,扩容是一个相当耗时的操作,因为它需要重新计算这些元素在新的数组中的位置并进行复制处理。因此,我们在用HashMap的时,最好能提前预估下HashMap中元素的个数,这样有助于提高HashMap的性能。

加载因子,如果加载因子越大,对空间的利用更充分,但是查找效率会降低(链表长度会越来越长);如果加载因子太小,那么表中的数据将过于稀疏(很多空间还没用,就开始扩容了),对空间造成严重浪费。如果我们在构造方法中不指定,则系统默认加载因子为0.75,这是一个比较理想的值,一般情况下我们是无需修改的。另外,无论我们指定的容量为多少,构造方法都会将实际容量设为不小于指定容量的2的次方的一个数,且最大值不能超过2的30次方。

HashMap的初始容量为16,Hashtable初始容量为11,两者的填充因子默认都是0.75。
HashMap扩容时是当前容量翻倍即:capacity
2,Hashtable扩容时是容量翻倍+1即:capacity*2+1。
HashMap和Hashtable的底层实现都是数组+链表结构实现。
HashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取摸:

  1. static int hash(int h) {
  2. h ^= (h >>> 20) ^ (h >>> 12);
  3. return h ^ (h >>> 7) ^ (h >>> 4);
  4. }
  5. static int indexFor(int h, int length) {
  6. return h & (length-1);
  7. }

HashMap多线程put操作后,get操作导致死循环。为何出现死循环?
大家都知道,HashMap采用链表解决Hash冲突,具体的HashMap的分析因为是链表结构,那么就很容易形成闭合的链路,这样在循环的时候只要有线程对这个HashMap进行get操作就会产生死循环。但是,我好奇的是,这种闭合的链路是如何形成的呢。在单线程情况下,只有一个线程对HashMap的数据结构进行操作,是不可能产生闭合的回路的。那就只有在多线程并发的情况下才会出现这种情况,那就是在put操作的时候,如果size>initialCapacityloadFactor,那么这时候HashMap就会进行rehash操作,随之HashMap的结构就会发生翻天覆地的变化。很有可能就是在两个线程在这个时候同时触发了rehash操作,产生了闭合的回路。

简单来说,HashMap由数组+链表组成的,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,如果定位到的数组位置不含链表(当前entry的next指向null),那么对于查找,添加等操作很快,仅需一次寻址即可;如果定位到的数组包含链表,对于添加操作,其时间复杂度依然为O(1),因为最新的Entry会插入链表头部,急需要简单改变引用链即可,而对于查找操作来讲,此时就需要遍历链表,然后通过key对象的equals方法逐一比对查找。所以,性能考虑,HashMap中的链表出现越少,性能才会越好。

HashMap存储自定义类型:使用HashMap储存自定义类形式,因为要保证key的唯一性。需要 自定义类重写 hashCode()跟equals()方法;
HashMap的方法基本都是Map中声明的方法
实现原理:实现一个哈希表,存储元素(key/value)时,用key计算hash值,如果hash值没有碰撞,则只用数组存储元素;如果hash值碰撞了,则相同的hash值的元素用链表存储;如果相同hash值超过8个,则相同的hash值的元素用*红黑树存储
。获取元素时,用key计算hash值,用hash值计算元素在数组中的下标,取得元素如果命中,则返回;如果不是就在红黑树或链表中找。
PS:存储元素的数组是有冗余的。

采用了Fail-Fast机制,通过一个modCount值记录修改次数,在迭代过程中,判断modCount跟初始过程记录的expectedModCount是否相等,如果不相等就表示已经有其他线程修改了Map,马上抛出异常;另外扩容过程中还有可能产生环形链表。
synchronized是针对整张Hash表的,即每次锁住整张表让线程独占

(3)HashTable

HashTable线程安全,低效,不支持null ,Hashtable是同步的
HashTable这个类实现了哈希表从key映射到value的数据结构形式。任何非null的对象都可以作为key或者value。
要在hashtable中存储和检索对象,作为key的对象必须实现hashCode、equals方法。

一般来说,默认的加载因子(0.75)提供了一种对于空间、时间消耗比较好的权衡策略。太高的值(指加载因子loadFactor)虽然减少了空间开销但是增加了检索时间,这反应在对hashtable的很多操作中,比如get、put方法。
初始容量的控制也是在空间消耗和rehash操作耗时(该操作耗时较大)二者之间的权衡。 如果初始容量大于哈希表的当前最大的条目数除以加载因子,则不会发生rehash。但是,将初始容量设置过高会浪费空间。
如果有大量的数据需要放进hashtable,则选择设置较大的初始容量比它自动rehash更优。

如果不需要线程安全的实现,建议使用HashMap代替Hashtable
如果想要一个线程安全的高并发实现,那么建议使用java.util.concurrent.ConcurrentHashMap取代了Hashtable。
HashTable的父类是Dictionary

HashTable:线程安全,HashTable方法有synchronized修饰
HashTable:保留了contains(),containsKey(),containsValue()
HashTable:key,value都不能为空.原因是源码中方法里会遍历entry,然后用entry的key或者value调用equals(),所以要先判断key/value是否为空,如果为空就会抛出异常
HashTable:使用Enumeration,Iterator
HashTable:数组初始大小为11,扩容方式为2old+1
HashTable: 直接使用hashcode()
Hashtable同样是基于哈希表实现的,同样每个元素是一个key-value对,其内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。

Hashtable也是JDK1.0引入的类,是线程安全的,能用于多线程环境中。
Hashtable同样实现了Serializable接口,它支持序列化,实现了Cloneable接口,能被克隆。
Hashtable计算hash是直接使用key的hashcode对table数组的长度直接进行取模:
int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length;

底层数据结构是哈希表,特点和 hashMap 是一样的
    Hashtable 是线程安全的集合,是单线程的,运行速度慢
    HashMap 是线程不安全的集合,是多线程的,运行速度快
    Hashtable 命运和 Vector 是一样的,从 JDK1.2 开始,被更先进的 HashMap 取代
     HashMap 允许存储 null 值,null 健
    Hashtable 不允许存储 null 值,null 健
     Hashtable 他的孩子,子类 Properties 依然活跃在开发舞台

*Properties

Java.util.Properties 集合extends Hashtable 集合

Properties 集合特点:
Properties集合也是一个双列集合,key跟value都已经被内置为String类型
Properties集合是一个唯一和IO流相结合的集合
可以将集合中存储的临时数据,持久化到硬盘的文件中储存
可以把文件中储存对的键值对,读取到集合中使用

Properties集合的基本操作:添加数据,遍历集合,Key和value都已经被内置为String类型。里面包含了一些和String类的相关方法
Object setProperty(String key ,String value) 往集合中添加键值对,调用Hashtable的方法put添加
String getProperty(String key ) 通过key获取value的值,相当于Map集合中的get(key) 方法
Set stringPropertyNames()返回此属性列表的键集。相当于Map集合中的keySet()方法;

Properties类的load方法:
可以把文件中存储的键值对,读取到集合中使用
void load(Reader reader)
void load(InputStream inStream)
参数:
Reader reader:字符输入流,可以使用FileReader
InputStream inStream:字节输入流,可以使用FileInputStream
操作步骤:
1.创建Properties集合对象
2.创建字符输入流FileReader对象,构造方法中绑定要读取的数据源
3.使用Properties集合中的方法load,把文件中存储的键值对,读取到集合中使 用
4.释放资源
5.遍历Properties集合
注意:
1.流使用Reader字符流,可以读取中文数据
2.流使用InputStream字节流,不能操作中文,会有乱码
3.Properties集合的配置文件中,可以使用注释单行数据,使用#
4.Properties集合的配置文件中,key和value默认都是字符串,不用添加””(画蛇 添足)
5.Properties集合的配置文件中,key和value的连接符号可以使用=,也可以使用 空格

Properties类的store方法使用:
可以把集合中存储的临时数据,持久化都硬盘的文件中存储
void store(Writer writer, String comments)
void store(OutputStream out, String comments)
参数:
Writer writer:字符输出流,可以使用FileWriter
OutputStream out:字节输出流,可以使用FileOutputStream
String comments:注释,解释说明存储的文件,不能使用中文(乱码),默认编码格式为 Unicode编码
可以使用””空字符串
操作步骤:
1.创建Properties集合,往集合中添加数据
2.创建字符输出流FileWriter对象,构造方法中绑定要写入的目的地
3.调用Properties集合中的方法store,把集合中存储的临时数据,持久化都硬盘的文 件中存储
4.释放资源
注意:
1.流使用Writer字符流,可以写入中文数据的
2.流使用OutputStream字节流,不能操作中文,会有乱码
3.Propertie集合存储的文件,一般都以.properties结尾(程序员默认)

(4)LinkeHashMap

LinkedHashMap继承自HashMap,实现了Map接口。其内部还维护了一个双向链表,在每次插入数据,或者访问、修改数据时,会增加节点、或调整链表的节点顺序。以决定迭代时输出的顺序。
默认情况,遍历时的顺序是按照插入节点的顺序。这也是其与HashMap最大的区别。
也可以在构造时传入accessOrder参数,使得其遍历顺序按照访问的顺序输出。
LinkedHashMap在实现时,就是重写override了几个方法。以满足其输出序列有序的需求。

LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的
在遍历的时候会比HashMap慢,不过有种情况例外:当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢。因为LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和它的容量有关。

LinkedHashMap是HashMap的子类,保存了插入的顺序,需要输出的顺序和输入的顺序相同时可用LinkedHashMap;
LinkedHashMap 是HashMap的一个子类,如果需要输出的顺序和输入的相同,那么用LinkedHashMap可以实现.

LinkedHashMap取键值对时,是按照你放入的顺序来取的。
LinkedHashMap由于它的插入有序特性,也是一种比较常用的Map集合。它继承了HashMap,很多方法都直接复用了父类HashMap的方法。本文将探讨LinkedHashMap的内部实现,以及它是如何保证插入元素是按插入顺序排序的。
在分析前可以先思考下,既然是按照插入顺序,并且以Linked-开头,就很有可能是链表实现。如果纯粹以链表实现,也不是不可以,LinkedHashMap内部维护一个链表,插入一个元素则把它封装成Entry节点,并把它插入到链表尾部。功能可以实现,但这带来的查找效率达到了O(n),显然远远大于HashMap在没有冲突的情况下O(1)的时间复杂度。这就丝毫不能体现出Map这种数据结构随机存取快的优点。
所以显然,LinkedHashMap不可能只有一个链表来维护Entry节点,它极有可能维护了两种数据结构:散列表+链表。

LinkedHashMap的LRU特性
先讲一下LRU的定义:LRU(Least Recently Used),即最近最少使用算法,最初是用于内存管理中将无效的内存块腾出而用于加载数据以提高内存使用效率而发明的算法。
目前已经普遍用于提高缓存的命中率,如Redis、Memcached中都有使用。
为啥说LinkedHashMap本身就实现了LRU算法?原因就在于它额外维护的双向链表中。
在上面已经提到过,在做get/put操作时,LinkedHashMap会将当前访问/插入的节点移动到链表尾部,所以此时链表头部的那个节点就是 “最近最少未被访问”的节点。
举个例子:
往一个空的LinkedHashMap中插入A、B、C三个结点,那么链表会经历以下三个状态:
1. A 插入A节点,此时整个链表只有这一个节点,既是头节点也是尾节点
2. A -> B 插入B节点后,此时A为头节点,B为尾节点,而最近最常访问的节点就是B节点(刚被插入),而最近最少使用的节点就是A节点(相对B节点来讲,A节点已经有一段时间没有被访问过)
3. A -> B -> C 插入C节点后,此时A为头节点,C为尾节点,而最近最常访问的节点就是C节点(刚被插入),最近最少使用的节点就是A节点 (应该很好理解了吧 : ))
那么对于缓存来讲,A就是我最长时间没访问过的缓存,C就是最近才访问过的缓存,所以当缓存队列满时就会从头开始替换新的缓存值进去,从而保证缓存队列中的缓存尽可能是最近一段时间访问过的缓存,提高缓存命中率。

LinkedHashMap实现与HashMap的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可以是插入顺序或者是访问顺序。

(5)TreeMap

TreeMap实现SortMap接口,能够把它保存的记录根据键排序。
默认是按键的升序排序,也可以指定排序的比较器,当用Iterator 遍历TreeMap时,得到的记录是排过序的。
TreeMap取出来的是排序后的键值对。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。
TreeMap是基于红黑树结构实现的一种Map,要分析TreeMap的实现首先就要对红黑树有所了解。

要了解什么是红黑树,就要了解它的存在主要是为了解决什么问题,对比其他数据结构比如数组,链表,Hash表等树这种结构又有什么优点。
treeMap实现了sortMap接口,能够把保存的数据按照键的值排序,默认是按照自然数排序也可自定义排序方式。
TreeMap对键进行排序了。

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

二叉树插入元素是有顺序的,TreeSet的元素是有序的。
由于二叉树需要对结点排序(插入的结点位置),默认情况下没有排序方法,所以元素需要继承Comparator并重写compareTo方法来实现元素之间比较大小的功能。
对于TreeSet,compareTo方法来保证元素的唯一性。【这时候可以不重写equals】

二叉树需要结点排序,所以元素之间比较能够比较,所以对于自定义元素对象,需要继承Comparator并重写的compareTo方法。 两个元素相等时,compareTo返回0;左大于右时,返回正整数(一般返回1);小于时返回负整数(一般返回-1)

TreeMap的基本操作 containsKey、get、put 和 remove 的时间复杂度是 log(n)
TreeMap中默认的排序为升序

使用entrySet遍历方式要比keySet遍历方式快
entrySet遍历方式获取Value对象是直接从Entry对象中直接获得,时间复杂度T(n)=o(1);
keySet遍历获取Value对象则要从Map中重新获取,时间复杂度T(n)=o(n);keySet遍历Map方式比entrySet遍历Map方式多了一次循环,多遍历了一次table,当Map的size越大时,遍历的效率差别就越大。

HashMap通过hashcode对其内容进行快速查找,而TreeMap中所有的元素都保持着某种固定的顺序,如果你需要得到一个有序的结果你就应该使用TreeMap(HashMap中元素的排列顺序是不固定的)。
在Map 中插入、删除和定位元素,HashMap是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。使用HashMap要求添加的键类明确定义了hashCode()和 equals()的实现。

TreeMap 底层数据结构是红黑树(一种自平衡的二叉树) ,其根据比较的返回值是否是0来保证元素唯一性, 元素的排序通过两种方式:第一种是自然排序(元素具备比较性) 即让元素所属的类实现Comparable接口,第二种是比较器排序(集合具备比较性) ,即让集合接收一个Comparator的实现类对象。

Comparable 和 Comparator 的区别:
  Comparable 是一个比较的标准,里面有比较的方法,对象要具有比较的标准,就必须实现 Comparable 接口;类实现这个接口,就有比较的方法;把元素放到 TreeSet 里面去,就会自动的调用 CompareTo 方法;但是这个 Comparable 并不是专为 TreeSet 设计的;只是说,TreeSet 顺便利用而已;就像 HashCode 和 equals 也一样,不是专门为 HashSet 设计一样;只是你顺便利用而已。
  Compartor 是个比较器,也不是专门为TreeSet设计. 就是一个第三方的比较器接口;如果对象没有比较性,自己就可以按照比较器的标准,设计一个比较器,创建一个类,实现这个接口,覆写方法。

(6)WeakHashMap

WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,如果一个key不再被外部所引用,那么该key可以被GC回收。