什么时候需要用到ConcurrentHashMap和 linkedHashMap?

首先看一下基础的

Collection包括set map list


set 无序不重复

TreeSet 内部元素进行排序,是不同步的。

HashSet 内部数据结构是哈希表,是不同步的 LinkHashSet

list 有序可重复

Vector 内部是数组数据结构,是同步的(线程安全的)。增删查询都很慢。

Stack ArrayList 内部是数组数据结构 线程不安全 ----和LinkList用法一样,功能主要是查询

LinkedList 内部是双向循环链表数据结构,是不同步的(线程不安全的) ----增删

Map

HashMap 遍历时数据是随机的,允许一条记录的键是NULL ,允许多条记录的值是NULL

HashTable 是hashmap的线程安全版,支持现成的同步,效率低,键和值都不能为NULL ConcurrentHashMap线程安全,而且锁分离

LinkHashMap 保存了记录插入的顺序,用Iteraor遍历时先得到的肯定是先插入的,遍历时比hashmap慢,有HashMap的全部特性

TreeMap实现的SortMap接口,能够把它保存的记录根据键排序,默认是升序(自然),也可以指定排序的比较器,用Iterator遍历TreeMap时,得到的记录是排过序的,不允许key空,非同步 TreeMap的实现原理是红黑树实现的

HashMap的实现原理

1 HashMap概述: HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。

2 HashMap的数据结构:在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的HashMap也不例外。 HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

3 当我们往Hashmap中put元素时,首先根据key的hashcode重新计算hash值,根绝hash值得到这个元素在数组中的位置(下标),
如果该数组在该位置上已经存放了其他元素,那么在这个位置上的元素将以链表的形式存放,新加入的放在链头,
最先加入的放入链尾.如果数组中该位置没有元素,就直接将该元素放到数组的该位置上.


    HashMap初始化可以分为三种情况:
  1. 什么都不指定,阈值是0.75,容量大小是16
 
  public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;  
    }
  1. 初始化的时候指定容量

 //容量是自己设置的值,阈值是默认的0.75
  public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
  1. 初始化指定大小和阈值
 //initialCapacity(初始化容量)
//loadFactor  (初始化阈值)
  public HashMap(int initialCapacity, float loadFactor) {
		//如果初始化容量小于0,抛出异常
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
	    //如果 初始化容量大于最大值,就设置成最大值
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
		//如果阈值小于等于0或者不是一个数字,抛出异常
		//比如 HashMap mm =new HashMap(1, 0.0f);
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }

hashmap扩容机制:

HashMap的扩容机制并不像ArrayList等,在容量不够用时才会扩容。
这边也可以引申到一个问题就是HashMap是先插入数据再进行扩容的,
但是如果是刚刚初始化容器的时候是先扩容再插入数据。
HashMap在容量达到某个百分比时就会开始扩容,这个百分比就是loadFactor,
threshold就是对应这个百分比的容量,即capacity *oadFactor。
HashMap中将loadFactor的默认值设为0.75f。

第一种:使用默认构造方法初始化HashMap。从前文可以知道HashMap在一开始初始化的时候会返回一个空的table,并且thershold(阈值)为0。因此第一次扩容的容量为默认值DEFAULT_INITIAL_CAPACITY也就是16。同时threshold = DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR = 12。

比如说: HashMap m1 =new HashMap(;
那么m1的一开始容量为0,阈值为0,插入数据后
容量变为16,阈值是16*0.75=12

第二种:指定初始容量的构造方法初始化HashMap。初始容量会等于threshold,接着threshold = 当前的容量(threshold) * DEFAULT_LOAD_FACTOR。

 HashMap m2 =new HashMap(7);
 那么他的初始化容量是7,阈值是7*0.75,当然是整数
 它第一次扩容,容量会变成7*2 =14 ,但是hashmap的容量必须是2的整数倍
 也就是扩容后容量会变为16

第三种:HashMap不是第一次扩容。如果HashMap已经扩容过的话,那么每次table的容量以及threshold量为原有的2倍。(前提是扩容后不超过最大容量)

这个就不举例了,很好理解,就是如果扩大两倍之后会超过最大容量,就变为2

遍历collection

Iterator it = arr.iterator(); while(it.hasNext()){ object o
=it.next(); …}

遍历Map

keySet的效率没有entrySet效率高 Iterator it = map.entrySet().iterator();
while(it.hasNext()){ Entry e =(Entry) it.next();
System.out.println(“键”+e.getKey () + “的值为” + e.getValue());

集合排序

Collections.sort();

1、你用过HashMap吗?什么是HashMap?你为什么用到它?

用过,HashMap是基于哈希表的Map接口的非同步实现,它允许null键和null值,且HashMap依托于它的数据结构的设计,存储效率特别高,这是我用它的原因

2、你知道HashMap的工作原理吗?你知道HashMap的get()方法的工作原理吗?

上面两个问题属于同一答案的问题
HashMap是基于hash算法实现的,通过put(key,value)存储对象到HashMap中,也可以通过get(key)从HashMap中获取对象。当我们使用put的时候,
首先HashMap会对key的hashCode()的值进行hash计算,根据hash值得到这个元素在数组中的位置,将元素存储在该位置的链表上。
返回的hashCode用于找到bucket位置来储存Entry对象
当我们使用get的时候,首先HashMap会对key的hashCode()的值进行hash计算,根据hash值得到这个元素在数组中的位置,将元素从该位置上的链表中取出

3、当两个对象的hashcode相同会发生什么?

hashcode相同,说明两个对象HashMap数组的同一位置上,
(找到bucket位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。完美的答案!)
接着HashMap会遍历链表中的每个元素,通过key的equals方法来判断是否为同一个key,
如果是同一个key,则新的value会覆盖旧的value,并且返回旧的value。如果不是同一个key,则存储在该位置上的链表的链头

“当两个对象的hashcode相同会发生什么?” 从这里开始,真正的困惑开始了,一些面试者会回答因为hashcode相同,所以两个对象是相等的,HashMap将会抛出异常,或者不会存储它们。然后面试官可能会提醒他们有equals()和hashCode()两个方法,并告诉他们两个对象就算hashcode相同,但是它们可能并不相等。一些面试者可能就此放弃,而另外一些还能继续挺进,他们回答“因为hashcode相同,所以它们的bucket位置相同,‘碰撞’会发生。因为HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。”这个答案非常的合理,虽然有很多种处理碰撞的方法,这种方法是最简单的,也正是HashMap的处理方法。

4、如果两个键的hashcode相同,你如何获取值对象?

遍历HashMap链表中的每个元素,并对每个key进行hash计算,最后通过get方法获取其对应的值对象

5、如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?

负载因子默认是0.75,HashMap超过了负载因子定义的容量,也就是说超过了(HashMap的大小*负载因子)这个值,那么HashMap将会创建为原来HashMap大小两倍的数组大小,来重新调整map的大小,并将原来的对象放入新的bucket数组中
作为自己新的容量,这个过程叫resize或者rehash

6、你了解重新调整HashMap大小存在什么问题吗?

当多线程的情况下,可能产生条件竞争。当重新调整HashMap大小的时候,确实存在条件竞争,如果两个线程都发现HashMap需要重新调整大小了,
它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的数组位置的时候,HashMap并不会将元素放在LinkedList的尾部,
而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了

7、我们可以使用自定义的对象作为键吗?

可以,只要它遵守了equals()和hashCode()方法的定义规则,并且当对象插入到Map中之后将不会再改变了。如果这个自定义对象时不可变的,
那么它已经满足了作为键的条件,因为当它创建之后就已经不能改变了

8 什么时候用到LinkedeHashMap?为什么要用LinkedeHashMap?

例如hashmap写入慢,读取快;linkedhashmap是个有序链表读取慢,写入快;treeMap可以帮你自动做好升序排列。

ConcurrentHashMap,线程安全,读写还快 而且锁分离

9- ConcurrentHashMap和Hashtable的区别

Hashtable和ConcurrentHashMap有什么分别呢?它们都可以用于多线程的环境,但是当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。因为ConcurrentHashMap引入了分割(segmentation),不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。简而言之,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。

10 但是还是没有说为什么要用?或者说它的使用场景是什么?

ConcurrentHashMap和CopyOnWriteArrayList保留了线程安全的同时,也提供了更高的并发性。
sun的大爷们就推出了ConcurrentHashMap,线程安全,读写还快。你这不是蒙我呢,线程安全和读写速度肯定是成反比的,怎么可能。看了源码才知道,这是一种以空间换时间的结构,跟分布式缓存结构有点像,
创建的时候,内存直接分为了16个segment,每个segment实际上还是存储的哈希表,写入的时候,先找到对应的segment,然后锁这个segment,写完,解锁,汗!就这么简单解决了,锁segment的时候,其他segment还可以继续工作。好像听着挺简单的,其实大爷们的代码看着真的很头疼,到处都是移位、与或非,就拿计算存放位置的代码来看,如何均匀的散列,减少位置碰撞都是有讲究的,还是得感叹你大爷就是你大爷。

11- 我们可以使用CocurrentHashMap来代替Hashtable吗?

这是另外一个很热门的面试题,因为ConcurrentHashMap越来越多人用了。我们知道Hashtable是synchronized的,但是ConcurrentHashMap同步性能更好,因为它仅仅根据同步级别对map的一部分进行上锁。ConcurrentHashMap当然可以代替HashTable,但是HashTable提供更强的线程安全性。

12 我们能否让HashMap同步?

Map m = Collections.synchronizeMap(hashMap);


**1. HashMap的数据结构**

数据结构中有数组和链表来实现对数据的存储,但这两者基本上是两个极端。

数组

数组存储区间是连续的,占用内存严重,故空间复杂的很大。但数组的二分查找时间复杂度小,为O(1);数组的特点是:寻址容易,插入和删除困难;

链表

链表存储区间离散,占用内存比较宽松,故空间复杂度很小,但时间复杂度很大,达O(N)。链表的特点是:寻址困难,插入和删除容易。

哈希表
什么时候需要用到ConcurrentHashMap和 linkedHashMap?_第1张图片

那么我们能不能综合两者的特性,做出一种寻址容易,插入删除也容易的数据结构?答案是肯定的,这就是我们要提起的哈希表。哈希表((Hash
table)既满足了数据的查找方便,同时不占用太多的内容空间,使用也十分方便。

哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。
首先HashMap里面实现一个静态内部类Entry,其重要的属性有 key , value, next,从属性key,value我们就能很明显的看出来Entry就是HashMap键值对实现的一个基础bean,我们上面说到HashMap的基础就是一个线性数组,这个数组就是Entry[],Map里面的内容都保存在Entry[]里面。

HashMap的存取实现

// 存储时:
int hash = key.hashCode(); // 这个hashCode方法这里不详述,只要理解每个key的hash是一个固定的int值
int index = hash % Entry[].length;
Entry[index] = value;

// 取值时:
int hash = key.hashCode();
int index = hash % Entry[].length;
return Entry[index];

 
 

你可能感兴趣的:(Java,高并发下的Java)