HashMap
1.map的特点:key只有一个,而一个key可以有多个value,并且key值不可以出现重复项。就好比,一个孩子只有一个亲生父亲,而一个父亲可以派生出多个亲生孩子。他继承了AbstractMap类。
2.hashMap可以序列化(即实现了Serializable接口,推荐打开源码看一下,141行),所以线程不安全。如果再线程中一般情况下使用ConcurrentHashMap来操作。在每一个分段上都用锁进行保护,从而让锁的粒度更精细一些,并发性能更好,而 HashMap 没有锁机制,不是线程安全的。
3.hashMap的底层在jdk1.8之前主要是数组和链表组成,jdk1.8之后新增了红黑树的特性。数组是hashMap的主体,而链表是为了解决哈希冲突(下方有对hash冲突的解释)。.HashMap中主要是通过key的hashCode来计算hash值(查看每一个map的哈希值下方标注),然后通过hash值选择不同的数组来存储。
只要hashCode相同,计算出来的hash值就一样,如果存储对象多了,就有可能不同的对象计算出来的hash值是相同的,这就出现了所谓的hash冲突,HashMap的底层是通过链表来解决hash冲突的。
具体就是把相同hash值的HashMap,通过链表的形式进行存储,相当于存储的数组就是哈希表,数组的每个元素都是一个单链表的头结点,链表是用来解决hash冲突的,如果不同的key映射到了数组的同一位置,那么就将其放入单链表中。
4.插入元素的方式有jdk1.8之前的头插法,转换成了尾插法
5.HashMap的默认大小为16,并且一 定是2的指数,每次扩容都为old*2。
很多人不知道hashCode这个属性的使用场景。
在最早的数据存储中 ,很多人用txt文件来充当数据库,但是txt文件存储不了特别大量的数据,于是就会建多个txt文件来存储,但是这时候问题来了 如何能通过随机性存储,这时候就用到了hashCode来进行对txt文件连接计算的选择。通过计算HashCode值来将数据写进不同的txt存储,当然现在已经不再这样使用了,总不能回归原始社会。
但是这种写法依旧可以运用到现在分库分表的案例中。
哈希冲突
对应不同的关键字可能获得相同的hash地址,即 key1≠key2,但是f(key1)=f(key2)。这种现象就是冲突,而且这种冲突只能尽可能的减少,不能完全避免。因为哈希函数是从关键字集合和地址集合的映像,通常关键字集合比较大,而地址集合的元素仅为哈希表中的地址值。
HashCode 值查看
Map<String, Object> map = new HashMap<>(); map.put("name", "Bob"); map.put("age", "18"); map.put("sex", "Boy"); Iterator<String> iterator = map.keySet().iterator(); System.out.println("迭代器遍历"); while (iterator.hasNext()) { String key = iterator.next(); System.out.println("hashCode : >>>>>"+key.hashCode()); }
显示数据
迭代器遍历
hashCode : >>>>>113766
hashCode : >>>>>3373707
hashCode : >>>>>96511
public static void main(String[] args) {
Map<String, Object> map = new HashMap<>();
map.put("name", "Bob");
map.put("age", "18");
map.put("sex", "Boy");
Iterator<String> iterator = map.keySet().iterator();
System.out.println("迭代器遍历");
while (iterator.hasNext()) {
String key = iterator.next();
System.out.println(key);
Object values = map.get(key);
System.out.println(values);
}
System.out.println();
System.out.println("foreach遍历");
for (String key : map.keySet()) {
Object o = map.get(key);
System.out.println(key + "=" + o);
}
System.out.println();
System.out.println("获取Map中的所有key与value的对应关系");
Set<Map.Entry<String, Object>> entries = map.entrySet();
Iterator<Map.Entry<String, Object>> iterator1 = entries.iterator();
while (iterator1.hasNext()) {
Map.Entry<String, Object> keys = iterator1.next();
// System.out.println(keys);
String key = keys.getKey();
Object value = keys.getValue();
System.out.println(key);
System.out.println(value);
System.out.println(keys);
}
}
LinkedHash
HashMap和双向链表合二为一即是LinkedHashMap
LinkedHash将key和value put进集合时是有序的,根据你添加元素的顺序来决定,LinkedHash是有序的集合,
他继承了HashMap(源码164行),所以LinkedHashMap是具有HashMap的所有特性。只是在细节实现上稍有不同。
HashMap是没有顺序的,当我们希望有序的进行存储数据的时候,就可以使用LinkedMapHash进行存储。然LinkedHashMap增加了时间和空间上的开销,但是它通过维护一个额外的双向链表保证了迭代顺序。即该迭代顺序可以是插入顺序,也可以是访问顺序。
LinkedHash和HashMap区别
- LinkedHashMap是继承于HashMap,是基于HashMap和双向链表来实现的。
- HashMap无序;LinkedHashMap有序,可分为插入顺序和访问顺序两种。如果是访问顺序,那put和get操作已存在的Entry时,都会把Entry移动到双向链表的表尾(其实是先删除再插入)。
- LinkedHashMap存取数据,还是跟HashMap一样使用的Entry[]的方式,双向链表只是为了保证顺序。
- LinkedHashMap是线程不安全的。
HashTable
一般情况下不怎么使用,即使在线程中人们普遍使用的是ConcurrentHashMap。
hashTable继承了Dictionary类,hashTable的方法是同步的,即线程安全,HashMap的方法不是同步的非线程安全,在多并发的情况下我们可以使用hashTable.
HashTable中不允许有null键和null值,HashMap中允许出现一个null键,可以存在一个或者多个键的值都为null。程序中,对于HashMap,如果使用get(参数为 键)方法时,返回结果为null,可能是该键不存在,也可能是该键对应的值为null,这就出现了结果的二义性。因此,在HashMap中,我们不能使用get()方法来查询键 对应的值,应该使用containskey()方法。
HashTable是直接使用对象的hashCode。HashMap是重新计算hash值。
HashTable的底层实现的数组和初始大小和扩容方式。HashTable初始大小为11,并且每次扩容都为:2*old+1。
HashSet
- 底层是以哈希表(数组+链表/红黑树)的方式进行存储数据
- 是一个无序的容器(你怎么存进去的 不一定怎么取出来)
- 不能存储相同元素,(相同元素回覆盖)
- 因为没有下标,所以不能使用普通for循环进行遍历
- 允许有null值得出现
ConcurrentHashMap
继承了AbstractMap类
利用synchronized来锁住整张Hash表来实现线程安全,即每次锁住整张表让线程独占。