赞
踩
目录
2、HashSet、LinkedHashSet 和 TreeSet 的异同
4、ArrayList 与 LinkedList 区别?(⭐⭐⭐⭐⭐)
7、ArrayList list=new ArrayList(10) 中的list扩容几次(⭐⭐)
1、为什么要使用HashMap,底层原理是什么?(⭐⭐⭐⭐⭐)
3、HashMap 中的数组长度为什么必须是 2 的幂次方?(⭐⭐⭐)
4、JDK 1.7中HashMap 为什么会形成死循环?(⭐⭐⭐)
9、ConcurrentHashMap 和 Hashtable 的区别
10、JDK 1.7 和 JDK 1.8 的 ConcurrentHashMap 实现有什么不同?
摘自javaguide的集合总体框架图:
List:底层基于object[]数组,存储的元素有序、可重复。
Set:底层基于HashMap实现,存储的元素无序,不可重复。
Queue:单端队列,存储的元素有序、可重复。
Map:使用键值对(key-value)存储,key 是无序的、不可重复的。
Set
接口的实现类,都能保证元素唯一,并且都不是线程安全的。ArrayList三个构造函数:
ArrayList() 默认创建长度为0的数组。
ArrayList(int initialCapacity) 创建指定容量的数组。
ArrayList(Collection<? extends E> c) 使用集合c的大小作为数组容量。
ArrayList底层实现:
int newCapacity = oldCapacity + (oldCapacity >> 1);
Comparable和Comparator都是接口,都可以用来进行比较、排序,可以将Comparable理解为“内部比较器”,Comparator理解为“外部比较器”。
具体参考这篇文章 Comparable和Comparator区别
该语句声明和实例了一个 ArrayList,指定了容量为 10,未进行扩容。
数组转List后,如果修改了数组内容,list受影响吗?
List转数组后,如果修改了List内容,数组受影响吗?
关键点:①底层数据结构 ②工作方式
HashMap是一个集合了查询效率和增删效率的容器,内部存的都是一个个键值对,可以通过访问键值对其进行访问和修改。
HashMap 中的 hash 值是由hash函数产生的,所有键值对存放的位置都是由 hash值和(length-1) 与运算得到的(length必须为2的幂次方,此时与运算等价于对length-1取模)。因此,简单来说hash值就是用来定位某键值对在HashMap中存放的位置的。
length为2的幂次方可以确保(length-1)的二进制低位都是1,此时hash&(length-1) 等价于 hash%(length-1) ,并且位运算的效率较高。
JDK 1.7中在链表中添加元素的方式是头插法,当两个线程同时对HashMap进行扩容操作时,可能会形成环形链表,产生死循环。JDK 1.7中采用了尾插法来避免链表导致,从而避免产生环形链表。
具体来说:HashMap扩容时,会将旧HashMap的数据移植到 扩容的新HashMap中,而由于链表的插入方式是头插法,a->b->c 会变成 c->b->a ,旧线程仍然认为a节点后面是b,而b节点后面已经是a了,这里就会产生死循环。
- //hashMap计算hash值
- static final int hash(Object key) {
- int h;
- return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
- }
- //而hashtable直接使用hashcode值作为最终的hash值
当我们将对象加入HashSet时,HashSet会计算该对象的hashcode值,并与HashSet中其他对象作比较:若没有hashcode相同的对象,则该对象不重复,允许加入;若有hashcode相同的对象,还需要使用equals()方法检查两对象是否真的相同,如果相同则不允许加入该对象。
ps:
jdk1.8之前HashMap存在死循环和数据丢失的问题。而数据丢失是所有版本都存在的问题,主要是由于并发情况下多线程同时进行put操作,并发生了哈希冲突,此时线程A在判断完哈希冲突之后阻塞了,线程B将数据插入,然后线程B苏醒之后就会将线程A的数据覆盖掉。
主要有四种遍历方式:
使用迭代器(Iterator)的方式进行遍历。
- //使用迭代器(Iterator)EntrySet 的方式进行遍历.
- Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
- while(iterator.hasNext()){
- Map.Entry<Integer, String> entry = iterator.next();
- System.out.println(entry.getKey());
- System.out.println(entry.getValue());
- }
-
- //使用迭代器(Iterator)KeySet 的方式进行遍历;
- Iterator<Integer> iterator1 = map.keySet().iterator();
- while (iterator1.hasNext()){
- Integer next = iterator1.next();
- System.out.println(next);
- System.out.println(map.get(next));
- }
使用 For Each的方式进行遍历。
- //使用 For Each EntrySet 的方式进行遍历;
- for(Map.Entry<Integer,String> entry : map.entrySet()){
- System.out.println(entry.getKey());
- System.out.println(entry.getValue());
- }
-
- //使用 For Each KeySet 的方式进行遍历;
- for(Integer key:map.keySet()){
- System.out.println(key);
- System.out.println(map.get(key));
- }
使用 Lambda 表达式的方式进行遍历。
- map.forEach((key,value)->{
- System.out.println(key);
- System.out.println(value);
- });
使用 Streams API 的方式进行遍历。
- map.entrySet().stream().forEach((entry)->{
- System.out.println(entry.getKey());
- System.out.println(entry.getValue());
- });
从性能上来说,迭代器是遍历是最快的,使用entryset比使用keyset要快。
实现线程安全的方式不一样:
synchronized
同步锁,相当于给哈希表加了一个大锁,多线程访问的时候,大量线程会被阻塞,效率低下。CAS + synchronized
操作来保证并发的安全性,仅在链表或红黑树的首节点进行加锁,只要hash值不冲突,就不会产生并发,大大提高了效率。Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。