校招面试题2--阿里

 

阿里

1.HashMap和HashTable的区别,及其实现原理

HashTable和HashMap都是map接口的实现类,这两个类的实现原理基本是一致的,都是基于数组加链表的数据结构,因此把他们放到一起分析。

一、实现原理:

HashTable和HashMap都实现了map接口,只是HashTable继承了Dictionary抽象类而HashMap继承了AbstractMap类,他们的底层实现基本是一致的,都是基于数组(Entry类型)加链表这种数据结构实现的,称为“拉链法”实现HashTable

1、HashTable和HashMap的构造方法:二者的构造方法都是一样的,有以下4中(以HashMap为例)

HashMap()
构造一个空的 HashMap ,默认初始容量(16)和默认负载系数(0.75)。
HashMap(int initialCapacity)
构造一个空的 HashMap具有指定的初始容量和默认负载因子(0.75)。
HashMap(int initialCapacity, float loadFactor)
构造一个空的 HashMap具有指定的初始容量和负载因子。
HashMap(Map<? extends K,? extends V> m)
构造一个新的 HashMap与指定的相同的映射 Map 。
HashMap的一个实例有两个影响其性能的参数: 初始容量(initialCapacity)和负载因子(loadFactor) 。 容量是哈希表中的桶(bucket)数,初始容量只是创建哈希表时的容量。 负载因子是在容量自动增加之前允许哈希表得到满足的度量。 当在散列表中的条目的数量超过了负载因数和初始容量的乘积,哈希表被重新散列 (即,内部数据结构被重建),使得哈希表具有桶的大约两倍。

作为一般规则,默认负载因子(.75)提供了时间和空间成本之间的良好折中。 更高的值会降低空间开销,但会增加查找成本(反映在HashMap类的大部分操作中,包括get和put )。 在设置其初始容量时,应考虑地图中预期的条目数及其负载因子,以便最小化重新散列操作的次数。 如果初始容量大于最大条目数除以负载因子,则不会发生重新排列操作。

 

2、HashTable和HashMap的主要方法(基于JDK1.8.0_102):

HashTable和HashMap的API中提供了很多的方法,其中主要两个方法是put()和get()

①put()方法:

思路:

根据key计算可以key的hashCode,然后计算出hashCode在数组中对应的bucket的位置,如果该位置的key为null,那么在这个位置创建一个新的Entry类型的Node:

if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);

▶如果这个位置的key不为null,那么就要判断待添加的元素和该位置的元素是否相同,如果相同的话,就把key对应的旧节点e替换为新的节点:

if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
▶如果该节点是一个树节点的实例,那说明HashMap的底层已经转为数组加红黑树的结构,那么把该节点添加到红黑树中:

else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
▶如果以上都不是,那么开始对该位置指向的链表遍历,对每一个节点,如果他们的hashCode相同且key相同,那么退出遍历,该节点就是要被替换成新节点的节点e,对于这个节点,做如下处理:将该节点的value换成新的value,并返回旧的value

if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
▶如果遍历完该位置的链表都没有找到重复的节点(hashCode一致且key相同),那么在链表尾部添加新节点,并且如果添加新节点后,链表长度大于TREEIFY_THRESHOLD那么将链表转换为红黑树的结构:

if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
在JDK1.8.0_102中,TREEIFY_THRESHOLD等于8,也就是说hashmap,当链表长度大于8时,将链表转换为红黑树的结构,这样当该位置(bucket)的节点数很多时,访问效率会提高很多。


②get()方法:

HashMap源码:根据给出的key计算hashCode,然后计算在该HashMap中对应的bucket位置,如果找到fail位置,并且key。equals()返回true,那么返回该可以映射的value,如果没有找到核分条件的key,那么返回null,但是这里要说明,返回null也不一定是没有找到符合条件的null,也可能是这个key映射的value本来就是null,那么这个时候就需要用containsKey()这个方法来区别这两种情况。


    public V get(Object key) {
        Node<K,V> e;
        return (e = getNode(hash(key), key)) == null ? null : e.value;
    }
 
    final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            if (first.hash == hash && // always check first node 
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
                //如果first不是符合条件的key,那么对后面的bucket,如果是树,则查询树的节点key是否
                //符合条件,如果是链表,则遍历链表中的节点,查看是否有符合条件的key
                if ((e = first.next) != null) {
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null; //如果没有查找到则返回null
    }
HashTable源码:因为HashTable的key和value都不允许为null并且HashTable不会转换为红黑树的结构,因此要简单很多,只要遍历每个bucket的链表,查询是否有符合条件的key即可。

 public synchronized V get(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return (V)e.value;
            }
        }
        return null;
    }

二、HashTable和HashMap的不同点:

1、继承的类不同:HashTable继承Dictionary类,HashMap继承AbstractMap类;

2、线程安全级别不同:HashTable是线程安全的类,每个方法都有Synchronized修饰,HashMap不是线程安全的,如果想要用HashMap创建线程安全的对象,那么可以用Collections接口的SynchrinizedMap()方法封装一个HashMap的对象,使之成为线程安全的对象。由于HashMap非线程安全,在只有一个线程访问的情况下,效率要高于HashTable。

   Map m = Collections.synchronizedMap(new HashMap(...));
3、是否允许null的要求不同

HashTable:key不允许为null,value不允许为null

HashMap:key允许为null,value允许为null

4、在JDK1.8以后,HashMap在节点数大于TREEIFY_THRESHOLD(等于8)时,底层实现从数组加链表转换为数组加红黑树,因为防止查找太慢了啊;但是HashTable没有这种操作。(即hashmap=数组+链表/红黑树)(hashtable=数组+链表)
6、两者通过hash值散列到hash表的算法不一样:

HashTbale是古老的除留余数法,直接使用hashcode

int hash = key.hashCode();  
int index = (hash & 0x7FFFFFFF) % tab.length; 
而HashMap是强制容量为2的幂,重新根据hashcode计算hash值,在使用hash 位与 (hash表长度 – 1),也等价取膜,但更加高效,取得的位置更加分散,偶数,奇数保证了都会分散到。前者就不能保证

int hash = hash(k);  
int i = indexFor(hash, table.length);  
  
static int hash(Object x) {  
  int h = x.hashCode();  
  
  h += ~(h << 9);  
  h ^= (h >>> 14);  
  h += (h << 4);  
  h ^= (h >>> 10);  
  return h;  
}  
  
static int indexFor(int h, int length) {  
  return h & (length-1);  
2.ArrayList,LinkedList 和Vector的区别和实现原理。
ArrayList是基于数组的可变长数组,因为这个特性,所以它更适合实现get和set;LinkedList是基于双向链表的,所以比较适合实现插入和删除等操作;但以上两个都是非线程安全的,Vector的实现和ArrayList差不多,改进的地方是使用synchronized实现了线程安全。所以所还是hash表比较抗用啊,综合了数组和链表的属性,查的块,插入删除也快呀,哈哈哈,我说的是拉链法实现hash表哦。

3.TreeMap和TreeSet区别和实现原理。
其中 TreeMap 是 Map 接口的常用实现类,而 TreeSet 是 Set 接口的常用实现类。TreeSet 底层是通过 TreeMap 来实现的(如同HashSet底层是是通过HashMap来实现的一样),因此二者的实现方式完全一样。而 TreeMap 的实现就是红黑树算法。
相同点:
TreeMap和TreeSet都是有序的集合,也就是说他们存储的值都是拍好序的。
TreeMap和TreeSet都是非同步集合,因此他们不能在多线程之间共享,不过可以使用方法Collections.synchroinzedMap()来实现同步
运行速度都要比Hash集合慢,他们内部对元素的查询操作时间复杂度为O(logN),而HashMap/HashSet则为O(1)。
不同点:
最主要的区别就是TreeSet和TreeMap非别实现Set和Map接口
TreeMap的底层采用红黑树的实现,完成数据有序的插入,排序

3.java线程阻塞调用wait函数和sleep区别和联系,还有函数yield,notify等的作用。
wait是Object的方法,sleep是Thread类的方法;
wait让出CPU资源的同时会放弃锁,sleep让出CPU资源的同时不会释放锁;
wait需要notify或者notifyall来唤醒才能进入就绪状态,sleep在沉睡指定时间后,会自动进入就绪状态;

5.java中异常机制

Throwable是Error和Exception的父类,Error一般是指JVM抛出的错误,不需要捕获,Exception是程序错误,需要捕获处理;

 

 

 



 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值