秋招备战——Java基础知识
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了秋招备战——Java基础知识相关的知识,希望对你有一定的参考价值。
垃圾回收,JVM常用参数
将内存中不再被使用的对象进行回收,GC中用于回收的方法称为收集器,由于GC需要消耗一些资源和时间,Java在对对象的生命周期特征进行分析后,按照新生代、老年代的方式来对对象进行收集,以尽可能的缩短GC对应用造成的暂停
对新生代的对象收集称为minor GC
对老生代的对象收集称为full GC
程序中主动调用System.gc()强制执行的GC为full GC。
Xms堆内存的最小大小,默认为物理内存的1/64
Xmx堆内存的最大大小,默认为物理内存的1/4
Xmn堆内新生代的大小,通过这个值也可以得到老生代的大小:Xmx减去Xmn。
char和byte的区别
一个字符类型,无符号型的,占2个字节(Unicode码),Java用char来表示一个字符;一个是字节类型,有符号型,占1个字节。
int和byte能否相互转换
可以相互转换,byte占1字节,而int占用4个字节,不会照成精度损失。
数组和链表的区别
数组是连续的内存空间,适合通过索引查找,查找和修改比较方便;链表不一定是连续的内存空间,添加和删除比较方便。
红黑树的特点
解决平衡二叉查找树的缺点:不适合需要大量插入、删除和查找的场景,需要左旋和右旋来保证平衡,引入红黑树,每条路径的黑色节点数相同,红色节点不能相同,时间复杂度O(logn)。红黑树的特性:红黑树是近似平衡的二叉查找树,支持高效的查找、插入和删除元素,查找删除和插入的效率稳定。红黑树更适合应对实际开发过程中的复杂场景。
父节点为红,子节点还能是红色吗
不可以,需要把红色节点先变成黑色。
排序算法哪些是稳定的
稳定性体现在相同数字在排序之后后面的不会出现到前面
稳定算法:插入排序、冒泡排序、归并排序、基数排序;不稳定算法:希尔排序、选择排序、堆排序、快速排序。
设计模式有哪些?单例模式、工厂模式,单例模式怎么实现?spring中的单例模式和普通单例模式有什么区别?
单例模式:枚举实现、静态内部类实现、利用Spring的依赖注入能力实现单例、双重检查锁。Spring中的单例模式和普通单例模式的区别:spring中的单例是相对于容器,既在ApplicationContext中是单例的。而平常所说的单例是相对于JVM的。另一个JVM可以有多个Spring容器,而且Spring中的单例也只是按bean的id来区分的。
collection 和 collections
collection是一个集合接口,它提供了对集合对象进行基本操作的通用接口方法,实现该接口的类主要有List和Set。
collections是针对集合类的一个包裹类,它提供了一系列静态方法实现对各种集合的搜索、排序以及线程安全化等操作。如sort()对集合进行排序,min(),max()求集合的最大值和最小值。
synchronized和reentranLock的区别
- Reentrant Lock显示地获得释放锁,synchronized隐式获得释放锁;
- ReentrantLock可响应中断,可轮回,synchronized是不可以响应中断的
- reentranLock是API级别的,synchronized是JVM级别的
- ReentrantLock可以实现公平锁;
- ReentrantLock通过Condition可以绑定多个条件;
- 底层实现不一样,synchronized是同步阻塞,使用的是悲观并发策略,lock是同步非阻塞,采用的是乐观并发策略;
- Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现
- synchronized在发生异常时,会自动释放线程中的锁,因此不会导致死锁现象发生;而lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁。
synchronized底层如何实现,锁升级的过程
synchronized底层通过不同的锁实现。线程获取共享资源时,
第一步:检查mark world里面是不是放的自己的Thread,如果是,表示当前线程是处于“偏向锁”。
第二步:如果mark world不是自己的threadID,锁升级,这时候,用CAS来执行切换,新的线程根据MarkWorld里现有的ThreadId,通知之前线程暂停,之前线程将MarkWOrld的内容置空。
第三步:两个线程都把锁对象的hashcode复制到自己新建的用于存储锁的记录空间,接着开始通过cas操作,把锁对象的MarkWorld的内容修改为自己新建的记录空间的地址的方式竞争MarkWorld。
第四步:第三步中成功执行的CAS获得资源,失败的则进入自旋。
第五步:自旋的线程在自旋过程中,成功获得资源,则整个状态依然处于轻量级锁的状态,如果自旋失败。
第六步:进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程完成并唤醒自己。
List、Set、Map的区别
List:一个有序容器,元素存入集合的顺序和取出顺序一致,,元素可以重复,可以出入null元素,元素都有索引。常用的有ArrayList、LinkedList和Vector。
Set:无序容器,存入和取出顺序不一致,不可以存储重复元素,只允许存入一个null元素,必须保证元素的唯一性。set接口常用的实现类是HashSet、LinkedHashSet、TreeSet
Map:是一个键值对集合,存储键、值和之间的映射。key无序,唯一;value不要求有序,允许重复。Map常用的有HashMap、Tree Map、Hash Table、LinkedHashMap、ConcurrentHashMap.
集合框架底层的数据结构
List集合:Arraylist和Vector使用的是 Object 数组, LinkedList使用双向循环链表
set集合:HashSet(无序,唯一):基于 HashMap 实现的,HashSet的值作为key,value是Object类型的常量
LinkedHashSet继承HashSet,并且通过 LinkedHashMap 来实现的
TreeSet(有序,唯一): 红黑树(自平衡的排序二叉树。)
Map集合:HashMap由数组+链表+红黑树组成,数组是是HashMap的主体,链表则是主要为了解决哈希冲突而存在的,当链表长度大于阈值(默认为8)并且数组长度大于64时,将链表转化为红黑树
LinkedHashMap(有序) 继承自 HashMap,底层仍然是数组+链表+红黑树组成。另外,LinkedHashMap 在此基础上,节点之间增加了一条双向链表,使得可以保持键值对的插入顺序
HashTable无序,数组+链表组成的,数组是 HashTable的主体,链表则是主要为了解决哈希冲突而存在的
TreeMap有序,红黑树
复合框架的扩容
ArrayList和Vector默认初始容量为10,当元素个数超过容量长度时都进行进行扩容,ArrayList扩容为原来的1.5倍,而Vector扩容为原来的2倍
HashSet和HashMap默认初始容量为16,加载因子为0.75:即当元素个数超过容量长度的0.75倍时,进行扩容,扩容为原来的2倍。HashSet基于 HashMap 实现的,因此两者相同
HashTable:默认初始容量为11,加载因子为0.75,扩容策略是2倍+1,如 初始的容量为11,一次扩容后是容量为23
HashMap的实现原理以及JDK1.7和JDK1.8的区别
JDK1.7是数组+链表,无冲突时,存放数组;冲突时,存放链表;采用头插法。
JDK1.8是数组 + 链表 + 红黑树,无冲突时,存放数组;有冲突存放链表或者红黑树,当链表长度大于阈值(默认为8)并且数组长度大于64时,将链表转化为红黑树;树元素小于等于6时,树结构还原成链表形式。
HashMap是怎么解决哈希冲突的?
(1)使用链地址法(链表)来链接拥有相同hash值的数据;
(2)使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;
(3)引入红黑树进一步降低遍历的时间复杂度,使得遍历更快。
扰动函数的解释:
如果只使用hashCode取余,那么相当于参与运算的只有hashCode的低位,高位是没有起到任何作用的,所以我们的思路就是让hashCode的高位也参与进行运算,来获取hash值,进一步降低hash碰撞的概率,使得数据分布更平均,我们把这样的操作称为扰动。
ConcurrentHashMap底层的具体实现
在JDK1.7中,concurrenthashMap是由Segment数组结构和Hash Entry数据结构组成。segment继承了ReentrantLock,是一种可重入锁。Hash Entry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,一个Segment里包含一个HashEntry数组,每个HashEnrty是一个链表结构的元素,因此JDK1.7的concurrentHashMap是一种数组+链表结构。当对HashEnrty数组的数据进行修改时,必须首先获得与它对应的锁,这样才能保证线程安全,也就实现了全局的线程安全。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4z2vmVc2-1662292004425)(en-resource://database/1127:1)]
而在JDK1.8中采用Node + CAS+synchronized来保证并发安全进行实现,synchronized只锁定当前链表或红黑二叉树的首节点。如果相应位置上的Node没有初始化,则调用CAS插入相应的数据;如果相应位置的Node不为空,如果该节点的
如果该节点的first.hash!=-1,则对该节点加synchronized锁,更新节点或插入新节点; 如果该节点的first.hash=-1,则扩容。读操作无锁,Node节点的val和next使用volatile修饰,数组也用volatile修饰。
双亲委派机制
当一个类加载器收到了类加载请求的时候,他不会直接去加载指定的类,而是把这个请求委托给自己的父加载器去加载。只有父加载器无法加载这个类的时候,才会由当前这个加载器来负责类的加载。
Java中提供四种类型的加载器,具体如下:
Bootstrap ClassLoader类加载器:主要负责加载Java核心类库,
%JRE_HOME%\\lib下的rt.jar、resources.jar、charsets.jar和class等。
Extention CLassLoader扩展类加载器:主要负责加载目录%JRE_HOME%\\lib\\ext目录下的jar包和class文件。
Application ClassLoader(应用程序类加载器):主要负责加载当前应用的classpath下的所有类
User ClassLoader(用户自定义类加载器):用户自定义的类加载器可加载指定路径的class文件。
这四种类加载器存在如下关系,当进行类加载的时候,虽然用户自定义类不会由bootstrap classloader或是extension classloader加载(由类加载器的加载范围决定),但是代码实现还是会一直委托到bootstrap classloader, 上层无法加载,再由下层是否可以加载,如果都无法加载,就会触发findclass,抛出classNotFoundException.
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ophNl0Cp-1662292004427)(en-resource://database/1129:1)]
加载器之间的层级关系并不是以继承的方式存在的,而是以组合的方式处理的。
双亲委派机制的意义:
- 通过委派的方式,可以避免类的重复加载,当父加载器已经加载某一个类时,子加载器就不会再重新加载这个类。
- 通过双亲委派的方式,还保证了安全性。因为启动加载器在加载的时候,只会加载Java-home中的jar包里面的类,那么这个类是不会被随意替换的,除非有人跑到你的机器上,破坏你的jdk。那么就可以避免有人自定义一个有破坏功能的jdk里面的类被加载。这样可以有效防止核心Java API被篡改。
双亲委派机制有他存在的意义,不过也存在许多场景是需要破坏这个机制的,所以双亲委派机制也非必然。比如 tomcat web容器里面部署了很多的应用程序,但是这些应用程序对于第三方类库的依赖版本却不一样,但这些第三方类库的路径又是一样的,如果采用默认的双亲委派类加载机制,那么是无法加载多个相同的类。所以,Tomcat破坏双亲委派原则,提供隔离的机制,为每个web容器单独提供一个WebAppClassLoader加载器。
Tomcat的类加载机制:为了实现隔离性,优先加载 Web 应用自己定义的类,所以没有遵照双亲委派的约定,每一个应用自己的类加载器——WebAppClassLoader负责加载本身的目录下的class文件,加载不到时再交给CommonClassLoader加载,这和双亲委派刚好相反。
面试大礼包分享
下面再给大家分享一个超级棒的Java后端面试的大礼包,是某华为大师兄当时呕心沥血整理的,希望会对大家有用。
里面涵盖的内容非常的完整,且非常有用。
以上是关于秋招备战——Java基础知识的主要内容,如果未能解决你的问题,请参考以下文章