1. Java中的集合框架:
2. HashMap 工作原理
(1) 数组的特点:
- 存储区间是连续的,占用内存严重,空间复杂度很大,时间复杂度为O(1);
- 优点:随机读取效率很高,原因是数组是连续的(随机访问性强、查找速度快);
- 缺点:插入和删除数据效率低,因为插入数据时,该位置后面的数据在内存中要向后移动,且数组大小固定不易动态扩展;删除数据也同样涉及到数组中数据在内存中位置的移动问题;
(2) 链表的特点:
- 存储区间是离散的,占用内存宽松,空间复杂度小,时间复杂度为O(N);
- 优点:插入和删除数据速度快,内存利用率高,没有大小限制,扩展灵活;
- 缺点:不能随机查找,每次都是从第一个开始遍历(查询效率低);
(3) 哈希表的特点:
从以上数组和链表的特点可以看出各自都有其优缺点,那么若想鱼和熊掌兼得:查询效率和增删效率都较高,哈希表可以满足!!!
HashMap 是基于 hashing 原理的,通过 put(key, value) 和 get(key) 方法存储和获取元素。当将键值对传递给 put() 方法时,它调用键对象的 hashCode() 方法来计算 hashCode,然后找到 bucket 位置来存储值对象。在获取对象时,通过键对象的 equals() 方法找到正确的键值对,然后返回值对象。HashMap 使用链表来解决碰撞问题,当发生碰撞时,对象将会存储在链表的下一个节点中。HashMap 在每个链表节点中存储键值对对象。
当两个不同的键对象的 hashCode 相同时,它们会存储在同一个 bucket 位置的链表中。键对象的 equals() 方法用来找到键值对。
HashMap 的好处非常多,有时候出于性能方面的考虑,也会经常用到 ConcurrentHashMap。
① map.put(k, v) 实现原理
第一步:将 key 和 value 封装到 Node 对象当中(节点);
第二步:底层调用 key 的 hashCode() 方法,得到其 hash 值;
第三步:通过哈希表函数/哈希算法将 key 的 hash 值转换成数组的下标;
第四步:判断下标位置上是否有元素,如果没有任何元素,就把 Node 添加到这个位置上;如果下标对应得位置上有链表存在,则会用 key 和 链表上每个节点的 key 进行 equals 比较,若所有的 equals 比较结果都是 false,那么这个新的节点将被添加到链表的末尾;若其中有一个 equals 比较结果返回 true,那么这个节点的 value 将会被覆盖。
② map.get(k) 实现原理
第一步:调用 key 的 hashCode() 方法得到其哈希值,并通过哈希算法将哈希值转换成数组的下标;
第二步:通过转换得到的数组下标快速定位到数组中对应的位置。如果这个位置上什么都没有,则返回 null;如果这个位置上有单向链表,那么就用这个 k 和单向链表上的每一个节点的 key 进行 equals 比较,若所有 equals 比较结果都返回 false,则 get(k) 方法返回 null;若其中一个节点的 key 和 get(k) 方法传入的 k 进行 equals 比较的结果返回 true,那么这个节点的 value就是查找的 k 对应的值了,该值将会通过 get(k) 方法返回。
③ 随机增删、查询效率都很高
原因:增删是在链表上完成的,而查询只需要扫描部分,则效率高。
HashMap 集合的 key,会先调用 hashCode 和 equals 方法,这两个方法都需要重写。
④ 为什么放在 HashMap 集合 key 部分的元素需要重写 equals 方法?
因为 equals 默认比较的是两个对象的内存地址;
3. HashMap 总结
无序、key不可重复可有一个为空,value可重复可有多个空值。
无序,因为其中的元素不一定是存放在哪个单向链表上的,因此插入顺序和取出也不一样;
key不可重复,在插入元素时,会使用 equals 方法来保证 HashMap 集合的 key 不重复,如果 key 重复则对应的 value 就会被覆盖掉。
存放在 HashMap 集合 key 部分的元素其实就是存放在 HashSet 集合中,则 HashSet 集合也需要重写 equals 和 hashCode 方法。
HashMap 集合的默认初始化容量为16,默认加载因子为0.75;这个默认加载因子是,当 HashMap 集合底层数组的容量达到75%时,数组就开始扩容。HashMap 集合初始化容量是2的倍数,为了达到散列均匀,提高 HashMap 集合的存取效率。
4. HashMap 和 Hashtable 的区别
HashMap 和 Hashtable 都实现了 Map 接口,在决定使用哪一个之前要先明确二者之间的区别;主要的区别在于:线程安全性、同步(synchronization)、速度。
(1) HashMap 是非 synchronized 的,非线程安全的;而 Hashtable 是 synchronized 的,线程安全的;
在多个线程访问HashTable时,不需要自己为它的方法实现同步,而HashMap就必须为之提供额外的同步;
因为 Hashtable 是线程安全的,多个线程可以共享同一个 Hashtable;而如果使用 HashMap 时没有提供额外的同步方法的话,多线程是不能共享 HashMap 的;
在Java5中,提供了 ConcurrentHashMap ,它是 Hashtable 的替代,比 Hashtable 的扩展性更好。
(2) HashMap 可以接受 null (HashMap 可以接受为 null 的键(key)和值(value));而 Hashtable则不允许;
(3) 由于 HashMap 是非线程安全的,Hashtable 是线程安全的,因此,HashMap 的执行效率要高于 Hashtable;
(4) HashMap 和 Hashtable 的另一个区别是,HashMap 的迭代器(Iterator) 是 fail-fast 迭代器,而 Hashtable 的 enumerator 迭代器不是 fail-fast 的。所以,当有其他线程改变了 HashMap 的结构 (增加或者删除了元素),将会抛出 ConcurrentModificationException异常,但迭代器本身的 remove() 方法移除元素则不会抛出该异常。但这并不是一定会发生的行为,要看 JVM。这也是 Iterator 和 Enumeration 之间的区别。
(5) HashMap 不能保证随着时间的推移,Map 中的元素次序不发生改变。
备注:
① synchronized 意味着一次只有一个线程能够更改 Hashtable。也就是说任何线程要更新 Hashtable 首先要获得同步锁,其他线程要等到同步锁被释放之后才能获得同步锁,对 Hashtable 进行相应的更新;
② fail-fast 和 Iterator 迭代器相关。如果某个集合对象创建了 Iterator 或者 ListIterator,然后其他的线程试图“结构上”更改集合对象,将会抛出 ConcurrentModificationException 异常。但其他线程可以通过 set() 方法更改集合对象是允许的,因为这并没有从“结构上”更改集合。但是假如已经从结构上进行了更改,再调用 set() 方法,将会抛出 IIIegalArgumentException 异常。
③ 结构上的更改指的是删除或者插入元素,这样会影响到 map 的结构。
④ 要使 HashMap 实现同步,可以通过:Map map = Collections.synchronizeMap(hashMap); 来实现。
5. HashMap 和 HashSet 的区别
(1) HashMap:实现了 Map 接口,Map 接口对键值对进行映射。Map 中不允许重复的键。Map 接口有两个基本的实现,HashMap 和 TreeMap。TreeMap 保存了对象的排列次序,而 HashMap 则不能;HashMap 是非线程安全的,但 collection 框架提供了能够保证 HashMap 线程安全的方法(Collections.synchronizeMap(hashMap)),这样当多个线程同时访问 HashMap 时,能够保证只有一个县城更改 Map。
(2) HashSet:实现了 Set 接口,它不允许集合中有重复的值,当使用 HashSet 时,需要在将对象存储在 HashSet 之前,确保对象重写 equals() 和 hashCode() 方法,这样才能比较对象的值是否相等,以确保 set 集合中没有存储相等的对象。如果没有重写这两个方法,将会使用这个方法的默认实现进行比较。
在向 set 集合中添加元素时,使用 public boolean add(Object o) 方法,若元素值重复时就会立即返回 false,若添加成功则会返回true。
(3) 二者的区别:
HashMap | HashSet | |
实现接口: | 实现了 Map 接口 | 实现了 Set接口 |
存储内容: | 存储键值对 | 仅仅存储对象 |
存储方法: | 存入元素使用:put(key, value) | 存入元素使用:add(Object o) |
hashCode 计算方法: | 使用键对象来计算 hashCode 值 | 使用成员对象来计算 hashCdoe 值;对于两个对象来说,hashCode 值可能相同,所以 equals() 方法来判断对象的相等性,若两个对象不同的话,返回 false |
执行速度: | 执行速度较快;因为是使用唯一的键来获取对象的 | 执行速度较慢 |