【十一】常见容器

目录

一、hashMap,hashTable,ConcurrentHashMap,hashSet

1、HashTable

2、HashMap

3、ConcurrentHashMap

4、hashSet

二、TreeMap与TreeSet比较

三、 ArrayList LinkedList Vector的区别

四、使用迭代器遍历ArrayList的时候修改List报ConcurrentModificationException异常

一、hashMap,hashTable,ConcurrentHashMap,hashSet

HashMap和hashTable都实现了Map、Cloneable、Serializable三个接口。但是HashMap继承自抽象类AbstractMap,而HashTable继承自抽象类Dictionary。HashMap和hashTable都是使用哈希表来进行存储的。

每一个数组中的entry就是一个哈希桶

1、HashTable

HashTable是线程安全的,在整个表上添加锁,意思是将多个线程同时访问时,转化为串行进行操作。但是当使用复合操作时,线程不安全

table.contants()与table.contantsValue()相同。

HashTable查找key:先计算hash值,直接用key的hashCode代替,再计算在数组中的索引值,找到“key对应的Entry(哈希桶)”,然后在链表中找出“键值”与查找的key都相等(使用equals方法)的元素

查找value:从Entry数组,从后向前遍历每一个链表进行寻找(使用equals方法比较)。

hashTable中key或value值为null则会抛出异常。

 

2、HashMap

HashMap是非线程安全的

HashMap查找key:将“key为null”的元素存储在table[0]位置,“key不为null”的则调用hash()计算哈希值,在“该hash值对应的哈希桶”(其实调用了函数计算了索引值)上查找“键值等于key(使用equals方法)”的元素 。  

查找value:若键值为null,调用containsNullValue()方法进行查找,若“value不为null”,则查找HashMap中是否有值为value的节点(从Entry数组,从前向后遍历每一个链表,还是使用equals方法比较)

两个集合添加元素时,hashCode可能存在冲突的情况,也叫发生了碰撞,当碰撞发生时,计算出的哈希桶编号(bucketIndex)的也是相同的,这时会取到bucketIndex位置已存储的元素,最终通过equals来比较,equals方法就是哈希码碰撞时才会执行的方法,所以说HashMap很少会用到equals。HashMap通过hashCode和equals最终判断出K是否已存在,如果已存在,则使用新V值替换旧V值,并返回旧V值,如果不存在 ,则存放新的键值对<K, V>到该哈希桶的表头位置。

HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。

HashTable默认的初始大小为11,之后每次扩充为原来的2n+1(使简单的取模哈希的结果会更加均匀,哈希表更加均匀)。HashMap默认的初始化大小为16,之后每次扩充为原来的2倍(使用位运算效率高,定位哈希桶的位置更快)。如果在创建时给定了初始化大小,那么HashTable会直接使用你给定的大小,而HashMap会将其扩充为2的幂次方大小。

hashMap调用put()方法时,会使用addEntry()方法,然后判断是否需要调用resize()方法进行扩容(进行扩容时,会首先会新生成一个新的容量的数组,然后对原数组的所有键值对重新进行计算和写入新的数组,之后table指向新生成的数组。)。

当哈希表中的条目数超出了加载因子与当前容量(数组长度)的乘积时,则要对该哈希表进行 resize 操作(即扩容)。加载因子默认为0.75

当哈希桶中链表长度大于8时,JDK 1.8 中将其转化为红黑树    

3、ConcurrentHashMap

segment数组默认长度与HashMap相同都是16,扩容时只对某个segment端中的HashEntry进行扩容,

jdk1.8前采用锁分段机制,jdk1.8后采用CAS算法,可参考https://blog.csdn.net/u011328417/article/details/79284730

ConcurrentHashMap除了与HashTable一样线程安全外,它对于复合操作同样是线程安全的。

4、hashSet

1、HashSet底层是采用HashMap实现的。HashSet 的实现比较简单,HashSet 的绝大部分方法都是通过调用 HashMap 的方法来实现的,因此 HashSet 和 HashMap 两个集合在实现本质上是相同的。

2、HashMap的key就是放进HashSet中对象,value是Object类型的。

3、当调用HashSet的add方法时,实际上是向HashMap中增加了一行(key-value对),该行的key就是向HashSet增加的那个对象,该行的value就是一个Object类型的常量

添加元素时,实际将该元素作为key放入HashMap。 

由于HashMap的put()方法添加key-value对时,当新放入HashMap的Entry中key与集合中原有Entry的key相同(hashCode()返回值相等->然后再调用hash()相等->再计算索引相等,通过equals比较也返回true),

新添加的Entry的value会将覆盖原来Entry的value,但key不会有任何改变,

因此如果向HashSet中添加一个已经存在的元素时,新添加的集合元素将不会被放入HashMap中,

原来的元素也不会有任何改变,这也就满足了Set中元素不重复的特性。(和HashMap的实现一致)

如果key计算的hash值与集合中原有Entry的key相同,但是equals返回的是false,那么将该对象加入到这个entry链表中(新添加的元素在链表头,和HashMap的实现一致)

HashSet是非线程安全的

二、TreeMap与TreeSet比较

和HashMap与HashSet类似,TreeSet的底层是由TreeMap实现(底层是红黑树)的

相同点:

TreeMap和TreeSet都是有序的集合,也就是说他们存储的值都是排好序的。

TreeMap和TreeSet都是非同步集合,因此他们不能在多线程之间共享,不过可以使用方法Collections.synchroinzedMap()来实现同步

运行速度都要比Hash集合慢,他们内部对元素的操作时间复杂度为O(logN),而HashMap/HashSet则为O(1)。

不同点:

最主要的区别就是TreeSet和TreeMap分别实现Set和Map接口

TreeSet只存储一个对象,而TreeMap存储两个对象Key和Value(仅仅key对象有序)

TreeSet中不能有重复对象,而TreeMap中key不能重复,value可以重复

TreeMap的底层采用红黑树的实现,完成数据有序的插入,排序。

红黑树概念:

性质1. 节点是红色或黑色。

性质2. 根节点是黑色。

性质3 每个叶节点(NIL节点,空节点)是黑色的。

性质4 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)

性质5. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。

三、 ArrayList LinkedList Vector的区别

参考:https://blog.csdn.net/Li_lingxiao/article/details/78299550

三者相同点:

①这三个类都实现了List接口List接口继承了Collection接口

②添加的顺序与序号一致(但不代表存储的空间位置是连续的

③可以按序号取出元素

ArrayList和Vector两者的相同点:

①都是有序集合,即存储在这两个集合中的元素的位置都在空间上是有顺序即连续的,相当于一种动态的数组

简单的说是ArrayList 和Vector 的底层数据是数组的结构

三者不同点:

LinkList:存储在该集合中的元素的空间位置是不连续的底层数据结构是循环的双向链表结构

ArrayList和Vector都是使用数组方式存储数据,此数组元素数大于实际存储的数据以便增加和插入元素,它们都允许直接按序号索引元素,查询速度快,但是插入、删除元素要涉及数组元素移动等内存操作,所以索引数据快而插入数据慢。

LinkedList使用双向链表实现存储,按序号索引数据需要进行前向或后向遍历,但是插入数据时只需要记录本项的前后项即可,所以插入,删除速度较快,查找速度较慢。

Vector是同步处理,性能较低;ArrayList是使用异步处理,性能高。

Vector是线程安全的,ArrayList、LinkedList是非线程安全。

 

ArrayList与Vector内存分配与扩容:

1>Vector与ArrayList都有一个初始的容量大小(10),当存储进它们里面的元素的个数超过了容量时,就需要增加ArrayList与Vector的存储空间(每次增加的存储单元的个数在内存空间利用与程序效率之间要取得一定的平衡);

2>.Vector默认增长为原来两倍,而ArrayList的增长策略在文档中没有明确规定(从源代码看到的是增长为原来的1.5倍

3>.ArrayList与Vector都可以设置初始的空间大小,Vector还可以设置增长的空间大小,而ArrayList没有提供设置增长空间的方法

四、使用迭代器遍历ArrayList的时候修改List报ConcurrentModificationException异常

参考:https://www.cnblogs.com/liuling/p/2013-8-21-04.html

因为调用next的时候会进行判断是否修改过集合,如果修改则抛出ConcurrentModificationException异常。而使用Collections.synchronized进行包装集合还是会出现该问题。

CopyOnWriteArrayList不会有这个异常,是由于每次修改后都会进行ArrayList的复制,操作的就是另外一个ArrayList

注意:添加操作多时,效率低,因为每次添加时都会进行复制,开销非常的大。并发迭代操作多时可以选择。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值