java面试之基础问题

上一篇写了jvm相关的问题,这一篇写一写java基础知识。其实对于工作经验少的岗位,这一类问题会问的比较多,但是工作年限要求比较高的岗位,这类问题问的比较少,虽然少但是问的都是比较深入的,所以这一部分可能会写的比较少。

HashMap

这是八股文里很经典的内容了。
HashMap的底层数据结构是数组+链表/红黑树。具体的添加一个键值对的过程如下所示:
在这里插入图片描述
常见的问题有:
1.hashmap如何计算hash值的:
将key的hashCode()高16位与低16位进行异或运算(这样可以保留高位的特征,避免一些key的hashCode高位不同,低位相同,造成hash冲突)
2.hashmap的容量及扩容时的size为什么都是2的幂次倍数?
因为当n是2的整数次幂时,在数学上hash mod n的结果等于hash&(n-1),而对于计算机来说,计算位运算的速度更快。
3.hashmap在resize的时候会出现成环问题
这个问题只会发生在JDK1.8之前。1.7中扩容的代码为

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {//遍历原数组的每一个下标
        while(null != e) {
                //将数组下标下所有元素一个一个迁移到新数组,使用头插法
            Entry<K,V> next = e.next;
            if (rehash) {
                e.hash = null == e.key ? 0 : hash(e.key);
            }
            int i = indexFor(e.hash, newCapacity);
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        } 
    }
}

JDK1.7版本的HashMap中,两个线程同时添加一个新的键值对,然后同时触发扩容时,两个线程都会进行扩容,就会造成前一个线程将某个数组下标的元素迁移过去后,另一个线程又进行迁移。假设原数组下标下有node1->node2->null,这样一个链表,线程A和线程B都在迁移,都拿到了node1,假设线程A先执行,将两个节点迁移到新的数组,假设node1和node2在新数组还是在同一下标下,那么迁移后的链表是node2->node1->null,此时如果线程B还在迁移,拿到node1又迁移,会让node1-next=node2,从而让node1和node2形成环。
在1.8中,更改了迁移的方法。首先迁移时不是拿到一个键值对就迁移一个了,而是对一个数组下标下的链表进行遍历,根据hash值的不同,分成两个链表,然后将两个链表分别设置到新的数组的下标下。

if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }

在JDK1.8以后,不需要rehash,因为键值对的Hash值主要是根据key的hashCode()的高16位与低16位进行异或计算后得到,根据hash%length,计算得到数组的下标index,因为length是2的整数次幂,当扩容后length变为原来的两倍时,hash%(2length)的计算结果结果差别在于第length位的值是1还是0,如果是0,那么在新数组中的index与旧数组的一致,如果是1,在新数组中的index会是旧数组中的数组中的index+length。故而直接可以分成两个数组。
4.hashmap什么时候会触发扩容?
在没有指定初始长度的情况下,HashMap数组的默认长度为16,在添加一个新的键值对时,会调用putVal()方法,在方法中,成功添加一个新的键值对以后,会判断当前的元素个数是否超过阀值(数组长度
负载因子0.75),如果超过那么调用resize方法进行扩容。
为了避免链表长度过长,影响查找元素的效率,当链表的长度>8时,会将链表转换为红黑树,链表的长度<6时,将红黑树转换为链表(但是红黑树转换为链表的时机不是在删除链表时,而是在扩容时,发现红黑树分解后的两个链表<6,就按链表处理,否则就建立两个小的红黑树,设置到扩容后的位置)。之所以临界点为8是因为红黑树的查找时间复杂度为logN,链表的平均时间查找复杂度为N/2,当N为8时,logN为3,是小于N/2的,正好可以通过转换为红黑树减少查找的时间复杂度。
5.concurrentHashMap是如何保证线程安全的?
使用了CAS+synchronized来保证线程安全的。
6.另外一个跟hashmap有关系的问题:为什么重写hashcode方法时也要重写equals方法?
首先我们知道,在hashmap中计算key的位置是通过对key的hashcode方法计算hash值进一步处理得到的,而判断key是否存在是先通过hash值在通过equals方法的,且hashmap中是不允许有重复key的。tips:正常来说,hashcode不相等的两个对象一定不相等,重写的时候要保证这一条。
①只重写hashcode方法:可能会出现相同的两个对象,hash值相同,但是equals方法返回的是false(因为默认的equals方法比较的是地址),这样就达不到去重的效果了
②只重写equals方法:可能会出现相同的两个对象但是hash值不同,这样就不会再去调用equals方法比较,会导致hashmap中有重复的key,无法去重。
(tips:曾经在面试中被问到过这个问题,尴尬的是一紧张全忘记了,说的糊糊涂涂)
7.TreeMap的底层数据结构
虽然它实现了map这个接口,但是treemap底层其实是红黑树,默认会根据key进行排序。

String

1.String类为什么设计成不可变的?
①jdk源码中是这么写的private final char value[] ,是用final来修饰的,所以是不可变的;但是这个不可变其实只是地址不可变,你对数组中的元素进行改变,这个final是限制不了的;
②工程师设计的时候避免了对数组元素的改变。这样可以使用常量池这个东西,另外在多线程的时候就不会存在线程安全的问题了,方便了大家的使用。
2.String,StringBuffer,StringBuilder三个类有什么区别?
String是不可变的,是字符串常量,不适合不停的修改字符串;
StringBuffer是可变的,是字符串变量,线程安全,但是效率比较低;
StringBuilder也是可变的,线程不安全,但是效率会比StringBuffer高一点。

char

这个方面问的比较少,只有快手问过一次,算是他们的经典面试题了吧。
1.char在java中占几个字节?
2个
2.char能存储汉字吗?
可以
3.2个字节如何保存汉字?2个字节上线是65536
java使用的Unicode编码,可以表示汉字,但是不能表示所有的汉字,只有在unicode字符集里边的汉字才能被标识。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值