inheritance
- Collection
- List—允许有重复,它对集合中的对象进行索引
- Vector
- synchronizes on each individual operation
- obsolete
- LinkedList
- 链表
- 新增和删除操作add和remove,效率高
- 查询操作性能比较低
- ArrayList
- 数组
- 因为地址连续, ArrayList要移动数据,所以插入和删除操作效率比较低
- Vector
- Set—不允许对象有重复的值
- HashSet,使用hash表存储元素,元素无序且唯一,遍历输出顺序不一定和插入的顺序一致
- LinkedHashSet,内部用了Double linked list记录顺序,输出结果顺序和插入顺序一致
- TreeSet,使用二叉树[红黑树]实现,元素唯一,而且根据值排序
- HashSet,使用hash表存储元素,元素无序且唯一,遍历输出顺序不一定和插入的顺序一致
- List—允许有重复,它对集合中的对象进行索引
- Collection
- Map
- TreeMap
- 基于红黑树对所有key进行排序
- HashMap
- LinkedHashMap,迭代时输出结果和插入顺序一致
- IdentifyHashMap
- IdentityHashMap使用 == 判断两个key是否相等,而HashMap使用的是equals方法比较key值
- TreeMap
- Map
HashMap
how it work
- HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。
- 当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。
- HashMap是在bucket中储存键对象和值对象,作为Map.Entry
- 当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。
- HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中
HashMap的数据结构为数组 + 链表
- 计算位置方法:tab[i = (n - 1) & hash]
- JDK8优化,当链表超过8时,链表就转换为红黑树
- HashMap vs HashTable—HashMap几乎可以等价于Hashtable
- HashTable key 和value都不允许为null
- HashMap key和value都允许为null:key只能有一个为null,而value则可以有多个为null
- 线程安全
- HashMap是非synchronized的
- HashTable是synchronized,速度可能会慢
- 位置索引方法
- HashTable-%运算
- HashMap-&运算
- CocurrentHashMap比HashTable更加好
- 同步性能更好,因为它仅仅根据同步级别对map的一部分进行上锁
- HashTable key 和value都不允许为null
- HashMap优化:自定义数组容量和加载因子,HashMap(int initialCapacity, float loadFactor)
- 初始容量默认为16,负载因子(loadFactor)默认是0.75; map扩容后,要重新计算阈值
- 当元素个数 大于新的阈值时,map再自动扩容;以默认值为例,阈值=16*0.75=12,当元素个数大于12时就要扩容
- loadFactor过大时,map内的数组使用率高了,内部极有可能形成Entry链,影响查找速度
- loadFactor过小时,map内的数组使用率较低低,占用更多的内存
Collections.synchronizedMap vs HashTable
- 都是简单粗暴的通过synchronized来保证线程安全
- api上面大同小异
- HashTable会多一些api(map interface + enhanced interface)
- synchronizedMap遵循map interface
- 锁
- HashTable都是在方法加锁
- Collections.synchronizedMap(Map)内部会有object mutex变量,synchronized + mutex 细粒度
- HashTable尽管性能不如ConcurrentHashmap,但是不能完全被取代
- hashtable是强一致性—操作是阻塞的
- ConcurrentHashMap是弱一致性—设计是非阻塞
重写equals的时候必须要重写hashcode
- 保证通过equal判断相等的两个对象,调用hashcode方法也需要返回同样的整数值
- equal方法判断不同两个对象,hashcode可能相同[hash冲突]
- 重写hashcode,保证语义上相等的对象,hashcode是一样的
HashMap<Person,String> map = new HashMap<Person, String>();
Person person = new Person(1234,"乔峰");
//put到hashmap中去
map.put(person,"天龙八部");
//get取出,从逻辑上讲应该能输出“天龙八部”, 但是实际结果输出为null
System.out.println("结果:"+map.get(new Person(1234,"萧峰")));
为什么String, Interger这样的wrapper类适合做为key
- 因为获取对象的时候要用到equals()和hashCode()方法
- 如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能
HashTable和ConcurrentHashMap不允许key和value为null
- HashTable和ConcurrentHashMap都要考虑线程安全
- 如果允许的话, HT和CHM使用map.get(key)是返回null无法判断key是不存在还是值为空
- 普通HashMap还可以通过map.contains(key)来检查
- HT和CHM因为线程安全问题,因为线程调度,两次调用间已经发生了改变
Collections Thread Safe
大体分为两类:synchronized Collections 和 concurrent Collections
synchronized Collections vs concurrent Collections
其实都是粒度问题
- synchronized Collections 基本都是简单使用synchronized,性能偏低
- entire collection are locked
- Concurrent Collections 会有更加优化方案,更细粒度
- ConcurrentHashMap
synchronizedCollection(Collection c)
- 使用一个wrapper类: SynchronizedCollection implements Collection, Serializable
- 里面的add, addAll等方法都使用synchronized加锁
val syncColleciton = Collections.synchronizedCollection(ArrayList<Int>())
val thread1 = thread {
syncColleciton.addAll(listOf(1, 2, 3, 4, 5, 6))
}
val thread2 = thread {
syncColleciton.addAll(listOf(1, 2, 3, 4, 5, 6))
}
thread1.join()
thread2.join()
//如果不使用线程安全的类,有时候是12 有时候不是
assert(syncColleciton.size == 12)
- 和上面的synchronizedCollection类似,都是返回一个wrapper对象,使用synchronized保证线程安全
val syncList = Collections.synchronizedList(ArrayList<Int>())
val syncMap = Collections.synchronizedMap(HashMap<String, String>())
val syncSortedMap = Collections.synchronizedSortedMap(TreeMap<String, String>())
val syncSet = Collections.synchronizedSet(HashSet<String>())
concurren collections
- ConcurrentHashMap
- 数组+链表, Segment—-jdk 1.7
- Segment
[]数组来将Hash表实现分段存储,从而实现分段加锁 - Segment继承了ReetrantLock,表示Segment是一个可重入锁
- 每个Segment元素,即每个分段则类似于一个Hashtable
- Segment
- 数组+链表+红黑树, CAS + Synchronized—JDK 1.8
- put操作,先判断bucket,tabAt(tab, i = (n - 1) & hash)==null
- bucket为null的时候,采用CAS操作,如果CAS操作失败说明多线程写,别的线程抢先写了,也就是hash冲突
- bucket不为null的时候,使用锁,synchronized更新或者插入节点
- 使用CAS 增加baseCount, 失败则把当值放进counters数组
- tabAt函数调用,采用Unsafe.getObjectVolatie()来获取,而不是直接用table[index]
- Volatile access methods
- size方法,元素总数 = baseCount + sum(CounterCell)
- put操作,先判断bucket,tabAt(tab, i = (n - 1) & hash)==null
- 数组+链表, Segment—-jdk 1.7
- BlockingQueue