集合设计原理

HashMap 的实现原理
Hash?Map?从字面意思可以看出一定的数据结构,是基于哈希表(散列表结构)的 Map 接口的非同步实现,允许使用 null 值和 null 键。HashMap 不保证映射的顺序,特别是它不保证该顺序恒久不变。至于为什么呢?来轻轻地看看HashMap 的数据结构:

是不是很清晰明了??我都觉得是。 
横向一个是Entry[]数组,纵向是一条单链表。原来我面试的时候……不提了~( ~T T)。

HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体,链表节点是由一个个Entry(K,V)组成的,当我们往HashMap 中 put 元素的时候,先根据 key 的 hashCode 重新计算 hash 值,根据 hash 值得到这个元素在数组中的位置(即下标),如果数组该位置上已经存放有其他元素了,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放在链尾。如果数组该位置上没有元素,就直接将该元素放到此数组中的该位置上。

HashSet 的实现原理
Hash?Set?它不是Set接口的实现吗?为什么这个快就介绍Collection集合了,因为对于 HashSet 而言,它是基于 HashMap 实现的,底层采用 HashMap 来保存元素。相关 HashSet 的操作,基本上都是直接调用底层 HashMap 的相关方法来完成,我们应该为保存到 HashSet 中的对象覆盖 hashCode() 和 equals()。

如果在HashSet 添加add()元素对象,添加的是在 HashSet 中不存在的,则返回 true;如果添加的元素已经存在,返回 false。其原因在于 HashMap 的 put 方法。该方法在添加 key 不重复的键值对的时候,会返回 null。

HashTable 的实现原理
Hash?Table?这个时候会问Table和Map有啥不一样。和 HashMap 一样,Hashtable 也是一个散列表,它存储的内容也是键值对。

HashTable 基于 Dictionary 类,而 HashMap 是基于 AbstractMap。Dictionary 是任何可将键映射 到相应值的类的抽象父类,而 AbstractMap 是基于 Map 接口的实现,它以最大限度地减少实现此接口所需的工作。

HashMap 的 key 和 value 都允许为 null,而 Hashtable 的 key 和 value 都不允许为 null。HashMap 遇到 key 为 null 的时候,调用 putForNullKey 方法进行处理,而对 value 没有处理;Hashtable遇到 null,直接返回 NullPointerException。

Hashtable 方法是同步,而HashMap则不是。我们可以看一下源码,Hashtable 中的几乎所有的 public 的方法都是 synchronized 的,而有些方法也是在内部通过 synchronized 代码块来实现。我们也可以这样理解:HashTable 基于 Dictionary 类,Dictionary 是任何可将键映射到相应值的类的抽象父类,每个映射都是独立的,所以线程是安全的。

Hashtable的容量为 11,加载因子为 0.75,HashMap一个初始容量为 16,加载因子为 0.75。

HashMap、HashSet、HashTable之所有以链表散列散列的形式存储,是为了方便删除和添加,集合了数组和链表的优点。但是当Map需要扩容的时候,Entry数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中,不但如此,Entry数组各个位置及其散列表中的Key还需要重新计算当前的hash值,进行重新排布。

LinkedHashMap的实现原理
对于LinkedHashMap,Link?是如何在HashMap的基础上实现有序排列的,没错熟悉数据结构的朋友就知道,双链表结构,在每个Entry节点上增加了 before 和 after 的引用,指的是上一个元素和下一个元素的引用。如下图清晰明了:

ArrayList 的实现原理
ArrayList 我们用得很多,也称动态数组,它的容量能动态增长。ArrayList 是 List 接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。前面也讨论过数组自动增长得坏处,随着向 ArrayList 中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,通常因此造成线程不同步。

ArrayList 继承了 AbstractList,实现了 List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。

ArrayList 实现了 RandmoAccess 接口,即提供了随机访问功能。RandmoAccess 是 java 中用来被 List 实现,为 List 提供快速访问功能的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。这也是前面所提及到得数组得优点。但是添加、删除、修改性能较差。

LinkedList的实现原理
LinkedList 和 ArrayList 一样,都实现了 List 接口,但其内部的数据结构有本质的不同。LinkedList 是基于链表实现的(通过名字也能区分开来),所以它的插入和删除操作比 ArrayList 更加高效。但也是由于其为基于链表的,所以随机访问的效率要比 ArrayList 差。

LinkedList 是基于链表结构实现,所以在类中包含了 first 和 last 两个指针(Node)。Node 中包含了上一个节点和下一个节点的引用,这样就构成了双向的链表。每个 Node 只能知道自己的前一个节点和后一个节点,但对于链表来说,这已经足够了。这也是LinkList有序得原因。

ConcurrentHashMap 的实现原理
在ConcurrentHashMap没有出现以前,jdk使用hashtable来实现线程安全,但是hashtable是将整个hash表锁住,所以效率很低下。

ConcurrentHashMap 可以说是HashMap得升级版, HashMap 是非线程安全的。ConcurrentHashMap得出现使得HashMap 可以实现线程安全。

ConcurrentHashMap 分析
ConcurrentHashMap 的成员变量中,包含了一个 Segment (桶)数组。查了一下资料发现, Segment 是 ConcurrentHashMap 的内部类,然后在 Segment 这个类中,包含了一个 Entry 数组(table)。Entry 中,包含了 key 和 value 以及 next 指针(类似于 HashMap 中 Entry),所以 HashEntry 可以构成一个链表。

所以通俗的讲,ConcurrentHashMap 数据结构为一个 Segment 数组,Segment 的数据结构为 Entry 的数组(table),而 Entry 存的是我们的键值对,可以构成链表。以下为ConcurrentHashMap 得数据结构图:

每一个Segment中又包含了多个Entry数组(table),没错!是多个,图中只有一个。

ConcurrentHashMap 实现线程同步的原因就在加锁操作是针对的 hash 值对应的某个 Segment。因为 put 操作只是在这个 Segment 中完成。所以,其他的线程也可以对另外的 Segment 进行 put 操作,因为虽然该 Segment 被锁住了,但其他的 Segment 并没有加锁。同时,读线程并不会因为本线程的加锁而阻塞。

其实,明明用了ConcurrentHashMap,可是始终线程不安全。后来发现,原来ConcurrentHashMap的线程安全指的是,它的每个方法单独调用(即原子操作)都是线程安全的,但是代码总体的互斥性并不受控制。所以,多个方法调用ConcurrentHashMap的时候还是需要添加同步锁(synchronized)的。

文献参考 http://wiki.jikexueyuan.com/project/java-collection/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值