1.HashMap和Hashtable的区别?
HashMap是最常用的Map,它根据键的HashCode值存储数据,根据键可以直接获取它的值,具有很快的访问速度,遍历时,取得数据的顺序完全是随机的。因为键对象不可以重复,所以HashMap最多只允许一条记录的键为Null,允许多条记录的值为null,是非同步的,不安全。
Hashtable与HashMap类似,是HashMap的线程安全版,它支持线程的同步,即任一时刻只有一个线程能写Hashtable,因此也导致了Hashtale在写入时会比较慢,他继承自Dictionary类,不同的是它不允许记录的键或者值为null,同时效率较低。
2.HashMap的基本原理,HashMap结构,get(),put()是如何实现的
HashMap的初始化容量(即初始化table时用到),默认为16。
loadFactor为负载因子,默认为0.75。容量默认是2的幂次方,因为put()值的时候,key进行hash的值要去找下标,也就是 hash&(length-1), length为2的幂次方,即偶数,length-1为奇数,二进制最低位为1,因为&hash 获得的值完全取决于hash值,奇偶情况存在,在table中分布均匀。
底层结构 数组+链表的方式
put的实现,头插
1public V put(K key, V value) {
2 // 对key为null的处理
3 if (key == null)
4 return putForNullKey(value);
5 // 根据key算出hash值
6 int hash = hash(key);
7 // 根据hash值和HashMap容量算出在table中应该存储的下标i,table大小=默认容量值16
8 int i = indexFor(hash, table.length);
9 for (Entry<K,V> e = table[i]; e != null; e = e.next) {
10 Object k;
11 // 先判断hash值是否一样,如果一样,再判断key是否一样,因为不同对象的hash值可能一样
12 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
13 // 找到一样的key, 旧值替换成新值
14 V oldValue = e.value;
15 e.value = value;
16 e.recordAccess(this);
17 return oldValue;
18 }
19 }
20
21 // 直接增加新值
22 modCount++;
23 addEntry(hash, key, value, i);
24 return null;
25 }
- get(),遍历table的过程,因为我们可以用key的hash值算出key对应的Entry所在链表在在table的下标。这样,我们只要遍历单向链表就可以了,时间复杂度降低到O(n)。
- 线程不安全
- 基本原理:
- 通过hash的方法,通过put和get存储和获取对象。存储对象时,我们将K/V传给put方法时,它调用hashCode计算hash从而得到bucket位置,进一步存储,HashMap会根据当前bucket的占用情况自动调整容量(超过Load Facotr则resize为原来的2倍)。获取对象时,我们将K传给get,它调用hashCode计算hash从而得到bucket位置,并进一步调用equals()方法确定键值对。如果发生碰撞的时候,Hashmap通过链表将产生碰撞冲突的元素组织起来,在Java 8中,如果一个bucket中碰撞冲突的元素超过某个限制(默认是8),则使用红黑树来替换链表,从而提高速度。
- resize为原容量的2倍
- 1.7,碰撞太多,链表查询效率低下O(N),故而在java1.8中,碰撞大于8,链表转为红黑树存储元素,提高效率O(logn)。
3.ConcurrentHashMap的get(),put(),又是如何实现的?ConcurrentHashMap有哪些问题? ConcurrentHashMap的锁是读锁还是写锁?
put() 线程安全,首先第一步的时候会尝试获取锁,如果获取失败肯定就有其他线程存在竞争,则利用 scanAndLockForPut() 自旋获取锁。
将当前 Segment 中的 table 通过 key 的 hashcode 定位到 HashEntry。
遍历该 HashEntry,如果不为空则判断传入的 key 和当前遍历的 key, 是否相等,相等则覆盖旧的 value。
不为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容。
最后会解除在 1 中所获取当前 Segment 的锁。get() 只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。由于 HashEntry 中的 value 属性是用 volatile 关键词修饰的,保证了内存可见性,所以每次获取时都是最新值。
ConcurrentHashMap 的 get 方法是非常高效的,因为整个过程都不需要加锁。1.8 优化,put() 通过cas自旋转锁+syschronized保证数据写入。
5.匿名内部类是什么?如何访问在其外面定义的变量?
匿名内部类是没有名字的内部类。因为没有名字,所以匿名内部类只能只能使用一次,通常用来简化代码编写。使用匿名内部类有个前提条件:必须继承一个父类或者实现一个接口。
匿名内部类对象可以访问同一个方法中被定义为final类型的局部变量。定义为final后,编译器会把匿名内部类对象要访问的所有final类型局部变量,都拷贝一份作为该对象的成员变量。这样,即使栈中局部变量已经死亡,匿名内部类对象照样可以拿到该局部变量的值,因为它自己拷贝了一份,且与原局部变量的值始终保持一致(final类型不可变)
6.Java中的NIO,BIO,AIO分别是什么?
- https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/BIO-NIO-AIO.md