JAVA基础面试题详解及源码分析

目录

1.== 和equals的区别

2.hashCode 和equals 的关系

3. ArrayList 和LinkedList 的区别

4.ConcurrentHashMap 的底层原理

5.final关键字

6.CopyOnWriteArrayList

7.HashMap 和HashTable

8.java 异常体系

9.List 和Set

10.String StringBuilder StringBuffer 区别

11.HashSet 和TreeSet的区别

12.Java的4钟引用类型

13.总结


1.== 和equals的区别


        ==比较的是jvm栈中的数据,如果是基础类型,则比较的是变量的值,如果是引用类型,则比较的是堆内存中对象到栈中的引用地址
        equals 默认情况使用的是Object对象的equals方法,比较规则和==是一样的,但是如果比较string的话,string对equals进行了重写,会先判断比较对象是否为string类型,然后判断长度后逐个拿出char字符来进行比较
        String变量比较,如果是直接定义赋值的变量,并且在堆内存中的String常量池中不存在,会创建并存放到常量池冲中,将引用地址赋值给变量,如果存在,则直接把引用地址赋值给变量;
如果是new的对象,则会在堆内存中创建String对象并赋值引用地址。
所以 String == 比较的时候,直接赋值和new同一个值,是不相等的
String a = "test" 
String b = "test"
String c  = new String ("test");
a == b true
a == c false


2.hashCode 和equals 的关系


        hashCode是java.Object对象的native方法,他会更具对象生成一个int整数作为hash码,在堆内存中会维护一张hash表,K,V格式,K为hash码,V为对象在堆内存中的引用地址
以HashSet为例,添加对象的时候就会先通过对象生成的hashCode码获取对应的内存地址,如果内存地址已经存在对象,则通过equals判断对象是否相等,如果相等则不存放,如果不相等则会重新计算出新的Hash码然后和要添加的对象的内存地址进行关联
2.1两个对象相等,则HashCode一定相等
2.2两个对象相等,则equals一定相等
2.3两个对象hashCode相等,对象不一定相等
2.4如果重写equals方法,则需要重写hashCode方法
源码参考这位老哥的博客:String的equals底层源码解读_Venhoul的博客-CSDN博客


3. ArrayList 和LinkedList 的区别


        ArrayList 底层是由动态数据组成,是一块连续的内存,查找的时候可以通过下标进行随机查找,时间复杂度是O(1),如果初始化的时候没有定义长度,默认是0,
add的时候会进行扩容,第一次扩容默认长度是10,如果list.size+1超过默认容量10,则会进行下一次扩容,计算出新的容量(旧容量+旧容量/2),如果新容量大于默认值10,继续判断新容量是否大于最大容量  Integer.MAX_VALUE -8 ,如果大于则新容量为最大容量,然后使用Arrays.copy将elementData拷贝到新容量的数组里面,如果不是尾部添加,则需要进行数据移动(底层还是使用复制数据扩容)如果一开始指定List长度,一直进行尾部新增,效率比LinkedList要快很多。

源码参考这位老哥的博客:ArrayList源码分析(超详细)_算不出来没办法的博客-CSDN博客
        LinkedList 是由链表组成,底层内存不连续,通过指针指向下一个数据,新增和删除只需要改变指针指向的位置,LinkedList继承了Deque,是一个双端队列,可以头部插入和尾部插入,ADD方法是添加到链表的尾部,很快但是不适合查询,查询的时候需要逐一遍历去查询,查询时间复杂度为O(N),而且最好使用Iterator(迭代器)去遍历,如果使用for循环,每次get(i)都需要对list进行遍历,性能消耗很大。另外最好不要使用IndexOf返回元素索引,然后用返回的索引进行遍历,当结果为空时会遍历整个list。


4.ConcurrentHashMap 的底层原理


        ConcurrentHashMap 在1.7是用Reentrantlock + segment + hashEntry 结构实现的,多线程是通过segment实现,线程安全是通过继承reentrantlock锁定segment实现
并发个数受segment个数限制,默认是16,可以在初始化定义指定个数,但是不支持变更(扩容),get方法无需枷锁,通过volatile关键字保证。

        ConcurrentHashMap 在1.8中底层结构Synchronied + CAS + 链表/红黑树 他的核心结构是Node数组 table,关键变量是sizeCtl,如果sizeCtl是-1,则有线程正在初始化table,如果是-N,则说明有N-1个线程在进行扩容,如果是table未初始化,sizeCtl表示table数组的大小,如果已经初始化,sizeCtl表示table数组扩容的阈值(容量*哈希因子-0.75)
多线程在对ConcurrentHashMap进行扩容的时候,每个线程会通过字段transferIndex判断已经分配到哪一个下标了,然后根据下标,计算出自己的扩容区间,再通过CAS对transferIndex进行更改,这样的话多个线程就扩容自己区间的数据,扩容一个Node对象后会在table数组里当前位置设置为 ForwardingNode(FWD),表示如果一个线程在进行put的时候发现对象为FWD,则在nextTable里面去找对应的最新数据。

        ConcurrentHashMap 1.7源码请参考这位老哥的博客:深入理解ConcurrentHashMap1.7源码_concurrenthashmap源码1.7_XHHP的博客-CSDN博客

        ConcurrentHashMap 1.8源码请参考这位老哥的博客:【ConcurrentHashMap源码详细解析 jdk1.8版本 包括putVal、addCount、fullAddCount和transfer方法详解】_concurrenthashmap源码分析1.8_鱼跃龙门^我跃架构的博客-CSDN博客
 

5.final关键字


        final关键字修饰类,不能被继承,修饰方法不能被重写,但可以重载,修饰变量,一旦赋值,则不能更改final修饰基本类型,不能再更改值,修饰引用类型,不能更改引用地址,但是可以修改引用地址对应对象里面的值
        匿名内部类和局部内部类访问主类的局部变量,必须用final修饰,因为匿名内部类和主类再编译的时候会生成两个class文件,当主类方法执行完成后,会进行回收,但是匿名内部类还需要访问主类的变量。这样的话匿名内部类
就会访问到一个已经回收了的变量,为了防止这种问题,就将主类的局部变量复制了一份到匿名内部类里面,为了防止主类对变量进行修改而导致两个类的变量不一致,所以要求主类里面对匿名内部类访问的局部变量添加final关键字,局部内部类原理一样的。 


6.CopyOnWriteArrayList

        CopyOnWriteArrayList线程安全的,它在添加对象的时候,通过复制一个size+1的数组,来将原来的对象进行copy,将新对象添加到数组最后一个,并且写操作会加锁,保证数据安全,然后将原数组指向复制的数组,读对象会在新创建的数组里面读,适合读多写少的场景,读出来的数据可能不是最新的,所以不适合实时性很强的场景


7.HashMap 和HashTable

        HashMap是线程不安全的,HashTable是线程安全的,但是HashTable底层添加和扩容都会加锁,所以性能很低,如果是多线程情况下应该使用ConcurrentHashMap,HashMap 1.7是数组+链式结构对象组成,1.8是数组+链式结构/红黑树结构对象(NODE<KEY,VALUE> 对象,属性有key,value,hash,nextNode)组成,源码原理讲解,添加KEY,VALUE时,会先计算出key的hash值,然后高16位与低16位进行异或运算(主要是为了增加hash值的随机性),然后与数组长度减一进行与运算,算出数组下标,接着判断元素下标是否有数据,如果没有直接添加,如果有,判断下标数据key值是否一致,一致则替换,不一致,则判断元素nextnode是否有值,有值则先判断元素结构是红黑树还是链式结构(红黑树是TreeNode对象类型,不同类型数据赋值不一样),继续判断KEY是否相等,没值则创建Node对象赋值给nextNode(或者进行红黑树赋值),有值相等则替换,不相等则继续判断下一个元素期间会有链表长度判断,如果链表长度大于8,且数组长度大于64,链式结构会进行红黑树转换(如果长度大于8,但是数组长度未超过64会优先进行扩容)。
HashMap 扩容是当前数据存入后判断数据个数是否超过数组长度*哈希因子,如果超过,则会创建当前容量*2的新数组,然后对元素重新hash后插入到新数组中,如果插入到新数组中有hash冲突,1.8以后采用尾插法(为了防止环化)

源码请参考这位老哥的博客:深入理解ConcurrentHashMap1.7源码_concurrenthashmap源码1.7_XHHP的博客-CSDN博客

HashMap 1.7头插法死循环问题请参考这位老哥的博客:多线程情况下HashMap的头插法和尾插法,为什么尾插法会避免死循环? - 知乎

我认为关键点在于线程2恢复后table数组变成线程1扩容完成后的数组了,导致线程2循环去拿next节点对象

8.java 异常体系

        Throwable 所有异常的父类,子类有Exception和Error,Exception可以被处理的异常,ERROR是不可以被处理的异常,例如OOM,Exception异常又分为RunTimeException和CheckException,后者在写代码时
可以被检测出来,RunTimeException可以被捕获并处理,例如NullpointException异常


9.List 和Set


        List 按对象添加的顺序排序,有序,允许添加多个null对象,获取对象除了使用get(i),还可以使用迭代器Iterator进行遍历
Set 无序,只允许一个null对象,获取对象只能用迭代器Iterator遍历获取

10.String StringBuilder StringBuffer 区别


        String 定义好值后不能修改值的内容,如果修改变量只是新建了一个值,然后把变量引用地址换成了新建的值,StringBuilder StringBuffer是可变的,都是在原对象上操作。
StringBuffer是线程安全的,它的append()方法通过synchronized关键字修饰 StringBuilder 线程不安全,它的append()方法并没有加锁。
性能 StringBuilder > StringBuffer > String 使用场景:优先使用StringBuilder,如果是多线程情况且需要操作共享变量优先使用StringBuffer

11.HashSet 和TreeSet的区别

懒得写了,参考一下这位老哥的博客吧:HashSet和TreeSet的区别_treeset和hashset的区别_Android _ 业炎丶的博客-CSDN博客

12.Java的4钟引用类型

12.1、强引用

当我们使用new创建对象时,被创建的对象就是强引用,如Object object = new Object(),其中的object就是一个强引用了。只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足时,JVM也会直接抛出OutOfMemoryError,不会去回收。如果想中断强引用与对象之间的联系,可以显示的将强引用赋值为null,这样一来,JVM就可以适时的回收对象了

12.2、软引用

软引用是用来描述一些非必需但仍有用的对象。在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常。这种特性常常被用来实现缓存技术,比如网页缓存,图片缓存等。在 JDK1.2 之后,用java.lang.ref.SoftReference类来表示软引用。

如果一个对象只具备软引用,如果内存空间足够,那么JVM就不会GC它,如果内存空间不足了,就会GC该对象。 

12.3、弱引用

弱引用的引用强度比软引用要更弱一些,无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。在 JDK1.2 之后,用 java.lang.ref.WeakReference 来表示弱引用。

如果一个对象只具有弱引用,只要JVM的GC线程检测到了,就会立即回收。弱引用的生命周期要比软引用短很多。不过,如果垃圾回收器是一个优先级很低的线程,也不一定会很快就会释放掉软引用的内存。

4、虚引用

虚引用是最弱的一种引用关系,如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收,在 JDK1.2 之后,用 PhantomReference 类来表示,通过查看这个类的源码,发现它只有一个构造函数和一个 get() 方法,而且它的 get() 方法仅仅是返回一个null,也就是说将永远无法通过虚引用来获取对象,虚引用必须要和 ReferenceQueue 引用队列一起使用。

如果一个对象只具有虚引用,那么它就和没有任何引用一样,随时会被JVM当作垃圾进行GC。

13.总结

看完了这些面试题讲解,相信大家对于JAVA基础已经有了很大的提升,有没有增加一点自己的信心,下面我们例举几个面试题,大家可以根据自己学到的知识进行测试

12.1 阿里二面:jdk 1.7和1.8 HashMap发生了什么变化

12.2 阿里二面:jdk 1.7和1.8 JVM虚拟机发生了什么变化(这个自己查一下)

12.3 阿里一面:说一下ArrayList和LinkedList的区别

12.4 阿里一面:说一下HashMap的put方法

12.5 京东二面:说一下泛型中extends和super的区别(自己查一下)

12.6 京东一面:说一下ConcurrentHashMap的扩容机制

12.7 蚂蚁二面:说一下ConcurrentHashMap在1.8中如何保证线程安全

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值