数组:内存存储连续 占用内存多 寻址容易,时间复杂度: 索引查找 (O(1)),按值查找(O(log(2)n)
链表: 内存存储不连续 占用内存少 插入与删除方便,时间复杂度(O(n)) ,
为了结合数组寻址容易与链表的插入删除容易,HashTable应运而生;
java为数据结构中的映射定义了一个接口java.util.Map;它有四个实现类,分别是HashMap
Hashtable LinkedHashMap 和TreeMap.
HashMap: 数据结构:数组+链表+红黑树(或称之为链表的数组),HashMap里面有一个Entry类(key,value,next),可以理解为其存储结构是一个线性数组(Entry[]),
put():
根据key的hashCode得到hash值,然后hash对Entry[].length取模得到下标index,在数组的index 下标位置处添加value,当然这时就要注意当hash值重复时就会发生hash冲突。
解决Hash冲突: 假设Entry[0] = A;进来了一个键值对B,其index值与A相同,这时让B.next = A;
Entry[0] = B;...,这时就解决了hash冲突。也就是说数组中存储的是最后插入的元素。
get():
根据key.hashCode得到hash值对Entry[]取模得到index,这时根据index得到所对应的链表或红黑树,然后根据equea()判断key值是否相等,相等时返回该值,不相等时判断下一个元素,直到该链表中再无元素。
区别:null值,安全性,同步,速度
总结:
Map: map用于存储键值对,根据键得到值,键不允许重复(重复就覆盖了),但允许值重复。
HashMap:hashMap是一个最常用的Map。它根据键的Hashcode值存储数据,根据键可以直接得到它的值,具有很快的访问速度,遍历时,取得数据的顺序都是完全随机的。HashMap只允许一条记录的键为null,允许多个记录的值为null。HashMap不支持线程的同步,即任一时刻可以有多个线程操作HashMap;可能会导致数据不一致。(解决方法:使用SynchronizedMap或者ConcurrentHashMap)所以线程不安全,多个线程无法共享一个HashMap.。
WeakHashMap:WeakHashMap是一种改进的HashMap,它对key实行“弱引用”,当一个key不被外界所引用,则该Key会被GC回收。
HashTable: 与HashMap类似,不同点:不允许键或者值为null,synchronized,支持线程同步,线程安全,多个线程可以共享一个HasTable,即任一时刻,只允许一个线程能写HashTable,这也就导致了HashTable在写入时比较慢。
LinkedHashMap:LinkedHashMap是HashMap的一个子类,保存了记录的插入顺序,在Iterator遍历LinkedHashMap时,先得到的记录一定是先插入的,也可以在构造时用带参数,按照应用次序排序。在遍历时比HashMap慢,不过有种情况例外,当HashMap的容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢,因为LinkedHashMap的遍历速度只与实际数据有关,与容量无关,而HashMap的遍历速度与容量有关。
TreeMap:TreeMap实现了SortMap的接口,能够将它保存的记录按照键排序,默认是升序排序。也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。
SynchronizedMap:hashMap存在线程安全问题,因此,在Collections类提供了一个方法返回一个同步版本的HashMap用于多线程环境,该方法返回一个SynchronizedMap实例。SynchronizedMap类是定义在Collection中的一个静态内部类。他实现了Map接口,并对其中的每一个方法实现,通过Synchronized关键字进行了同步控制。但还是存在潜在的安全问题。
ConcurrentHashMap:ConcurrentHashMap提供了HashTable和HashMap以及SynchronizedMap中所不同的锁机制。比起SynchronizedMap,它提供了好得多的并发性。多个读操作几乎总可以并发执行,同时进行的读和写操作也能并发执行,而同时进行的写操作仍然可以不时的并发执行。HashTable采用的锁机制是一次锁定整个hash表,从而同一时刻只能由一个线程对其进行操作;
ConrrentHashMap采用的锁机制是每一次锁定一个桶。
ConcurrentHahMap默认将hash表分为16个桶,诸如get、put、remove等寻常操作只锁当前需要用到的桶。这样一来,原本只能一个线程进入,现在却能同时有16个写线程进行执行,并发性的提升显而易见。前面所说的16个线程指的是写线程,而读操作基本不需要用到锁。只有在size等操作时才会锁定整个表。
在迭代方面,ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当Iterator被创建后集合在再发生改变就不会再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据,iterator完成后再将头指针替换为新的数据,这样Iterator线程就可以使用原来老的数据,从而写线程也可以并发的完成改变。
Hashmap与LinkedHashMap与TreeMap使用场景:
一般情况下,我们用的最多的是HashMap,在Map 中插入、删除和定位元素,HashMap 是最好的选择。但如果您要按自然顺序或自定义顺序遍历键,那么TreeMap会更好。如果需要输出的顺序和输入的相同,那么用LinkedHashMap 可以实现,它还可以按读取顺序来排列.
Hashmap与SynchronizedMap与ConcurrentHashMap使用场景:
一般情况下使用HashMap,但是HashMap线程不安全,所以在多线程的场景下使用SynchronizedMap,但是SynchronizedMap也存在一些潜在的安全问题(如迭代时数据更改),这时我们拥有更好的选择concurrenthashMap。
扩展:
CopyOnWriteArrayList可以用于什么应用场景?
答:CopyOnWriteArrayList用于读多写少的并发场景,比如白名单、黑名单。
CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
CopyOnWriteArrayList(免锁容器)的好处之一是多个迭代器同时遍历和修改这个列表时,不会抛出ConcurrentModificationException。在CopyOnWriteArayList中,写入将会导致创建整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修改时,读取操作可以安全地执行。