100道互联网大厂面试题+答案

Java基础

1、JDK 、JRE、JVM 有什么区别?

JDK(Java Development Kit),[Java开发工具包] JRE(Java Runtime Environment),Java运行时环境

JVM(Java Virtual Machine),运行 Java 字节码的虚拟机

JDK中包含JRE,JDK中有一个名为jre的目录,里面包含两个文件夹bin和lib,bin就是JVM,lib就是JVM工作所需要的类库。

2、== 和 equals 的区别是什么?

1、对于基本类型,== 比较的是值;

2、对于引用类型,== 比较的对象的内存地址;

3、equals不能用于基本类型的比较;

4、如果没有重写equals方法,equals就相当于==;

5、如果重写了equals方法,equals比较的是对象的属性是否相等;

3、final 在 java 中有什么作用?

(1)用来修饰一个引用

如果引用为基本数据类型,则该引用为常量,该值无法修改;

如果引用为引用数据类型,比如对象、数组,则该对象、数组本身可以修改,但指向该对象或数组的地址的引用不能修改。

如果引用时类的成员变量,则必须当场赋值,否则编译会报错。

(2)用来修饰一个方法

当使用final修饰方法时,这个方法将成为最终方法,无法被子类重写。但是,该方法仍然可以被继承。

(3)用来修饰类

当用final修改类时,该类成为最终类,无法被继承。比如常用的String类就是最终类。

4、java 中的 Math.round(-11.3) 等于多少?

-11

Math提供了三个与取整有关的方法:ceil、floor、round

(1)ceil:向上取整;

Math.ceil(11.3) = 12;

Math.ceil(-11.3) = -11;

(2)floor:向下取整;

Math.floor(11.3) = 11;

Math.floor(-11.3) = -12;

(3)round:四舍五入;加0.5然后向下取整。

Math.round(11.3) = 11;

Math.round(11.8) = 12;

Math.round(-11.3) = -11;

Math.round(-11.8) = -12;

5、String str="xyz"与 String str=new String("xyz")一样吗?

两个语句都会先去字符串常量池中检查是否已经存在 "xyz",如果有则直接使用,如果没有则会在常量池中创建 "xyz" 对象。

另外,String s = new String("xyz") 还会通过 new String() 在堆里创建一个内容与 "xyz" 相同的对象实例。

所以前者其实理解为被后者的所包含。

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记及答案【扫一扫】 即可免费获取**

6、new String("a") + new String("b") 会创建几个对象?

如果字符串常量池中没有"a"和"b",就是6个,否则就是4个

对象1:new StringBuilder()

对象2:new String("a")

对象3:常量池中的"a"

对象4:new String("b")

对象5:常量池中的"b"

深入剖析:StringBuilder中的toString():

对象6:new String("ab")

强调一下,toString()的调用,在字符串常量池中,没有生成"ab"

字符串常量池: JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。

7、如何将字符串反转?

将对象封装到stringBuilder中,调用reverse方法反转。

8、String 类的常用方法都有那些?

(1)常见String类的获取功能

length:获取字符串长度;

charAt(int index):获取指定索引位置的字符;

indexOf(int ch):返回指定字符在此字符串中第一次出现处的索引;

substring(int start):从指定位置开始截取字符串,默认到末尾;

substring(int start,int end):从指定位置开始到指定位置结束截取字符串;

(2)常见String类的判断功能

equals(Object obj): 比较字符串的内容是否相同,区分大小写;

contains(String str): 判断字符串中是否包含传递进来的字符串;

startsWith(String str): 判断字符串是否以传递进来的字符串开头;

endsWith(String str): 判断字符串是否以传递进来的字符串结尾;

isEmpty(): 判断字符串的内容是否为空串"";

(3)常见String类的转换功能

byte[] getBytes(): 把字符串转换为字节数组;

char[] toCharArray(): 把字符串转换为字符数组;

String valueOf(char[] chs): 把字符数组转成字符串。valueOf可以将任意类型转为字符串;

toLowerCase(): 把字符串转成小写;

toUpperCase(): 把字符串转成大写;

concat(String str): 把字符串拼接;

(4)常见String类的其他常用功能

replace(char old,char new) 将指定字符进行互换

replace(String old,String new) 将指定字符串进行互换

trim() 去除两端空格

int compareTo(String str) 会对照ASCII 码表 从第一个字母进行减法运算 返回的就是这个减法的结果,如果前面几个字母一样会根据两个字符串的长度进行减法运算返回的就是这个减法的结果,如果连个字符串一摸一样 返回的就是0。

9、String、StringBuffer和StringBuilder区别?

String:字符串对象是不可变对象,虽然可以共享常量对象,但是对于频繁字符串的修改和拼接操作,效率极低

后面两者都是可变字符串对象

StringBuffer:线程安全的(因为它的方法有synchronized修饰)效率低

StringBuilder:线程不安全的 性能高

小结:

(1)如果要操作少量的数据用 String;

(2)多线程操作字符串缓冲区下操作大量数据用 StringBuffer;

(3)单线程操作字符串缓冲区下操作大量数据用 StringBuilder。
String不可变的真正原因

1、字符串的本质是char数组(jdk9以后是byte[]),被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。

2、String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变。

10、 字符串拼接问题

(1)常量+常量:结果是常量池(常量优化,因为编译期间就可以确定结果)

(2)常量与变量 或 变量与变量:结果是堆

(3)拼接后调用intern方法:结果在常量池

ini 复制代码
String str1 = "str";
String str2 = "ing";
String str3 = "str" + "ing";
String str4 = str1 + str2;
String str5 = "string";
System.out.println(str3 == str4);//false
System.out.println(str3 == str5);//true
System.out.println(str4 == str5);//false

AI生成项目java
运行
12345678

11、什么是自动拆装箱? int和Integer有什么区别?以及以下程序运行结果。

基本数据类型,如int,float,double,boolean,char,byte,不具备对象的特征,不能调用方法。

装箱:将基本类型转换成包装类对象

拆箱:将包装类对象转换成基本类型的值

java为什么要引入自动装箱和拆箱的功能?主要是用于java集合中,List list=new ArrayList();

list集合如果要放整数的话,只能放对象,不能放基本类型,因此需要将整数自动装箱成对象。

实现原理:javac编译器的语法糖,底层是通过Integer.valueOf()和Integer.intValue()方法实现。

区别:

(1)Integer是int的包装类,int则是java的一种基本数据类型

(2)Integer变量必须实例化后才能使用,而int变量不需要

(3)Integer实际是对象的引用,当new一个Integer时,实际上是生成一个指针指向此对象;而int则是直接存储数据值

(4)Integer的默认值是null,int的默认值是0

包装类型的缓存机制

Java 基本数据类型的包装类型的大部分都用到了缓存机制来提升性能。

Byte,Short,Integer,Long 这 4 种包装类默认创建了数值 [-128,127] 的相应类型的缓存数据,Character 创建了数值在 [0,127] 范围的缓存数据,Boolean 直接返回 True or False。

如果超出对应范围仍然会去创建新的对象,缓存的范围区间的大小只是在性能和资源之间的权衡。

两种浮点数类型的包装类 Float,Double 并没有实现缓存机制。

ini 复制代码
public class Test01 {  
	public static void main(String[] args){  
		Integer a = 127;  
		Integer b = 127;  
		Integer c = 128;  
		Integer d = 128;  
		System.out.println(a==b); //true  
		System.out.println(c==d); //false 
		
		Float i11 = 333f;
		Float i22 = 333f;
		System.out.println(i11 == i22);//false
		
		Double i3 = 1.2;
		Double i4 = 1.2;
		System.out.println(i3 == i4);//false
	}  
} 

AI生成项目java
运行
123456789101112131415161718

建议:所有整型包装类对象之间值的比较,建议使用 equals 方法比较。

12、类和对象的关系

类是对同一类事物的描述,是抽象的

对象是一类事物的实现,是具体的

类是模板,对象是类的实例

13、怎么理解面向对象?

继承:描述的是事物之间的所属关系,is-a,子类继承父类的特征和行为,复用性 扩展性

封装:内部属性私有化,对外提供公共的访问方式,高内聚,低耦合

多态:同一个行为有不同的表现形式,多态存在3个条件:①继承②重写③父类引用指向子类实例 如List list = new ArrayList();

14、接口与抽象类的区别?

csharp 复制代码
  1、成员变量
     	抽象类:既可以是常量也可以是变量
     	接口:一定是常量
  2、构造
      	抽象类:有构造
      	接口:没有构造
  3、成员方法
    	抽象类:既可以是普通方法,也可以是抽象方法
    	接口:JDK1.8之前,必须是抽象方法(JDK8之后出现了默认方法和静态方法,JDK9出现了私有方法)
  4、设计理念
      	抽象类:作为一个继承体系顶层,将共性行为和属性被继承下去,体现is-a的关系 单继承
      	接口:作为一个功能进行扩展 多实现(子类可以实现多个接口)

AI生成项目java
运行
123456789101112

15、重载与重写有什么区别?

1、重载发生在本类,重写发生在父类与子类之间;

2、重载的方法名必须相同,重写的方法名相同且返回值类型必须相同;

3、重载的参数列表不同,重写的参数列表必须相同。

4、重写的访问权限不能比父类中被重写的方法的访问权限更低。

5、构造方法不能被重写

16、为什么要使用克隆?如何实现对象克隆?深拷贝和浅拷贝区别是什么?

(1)为什么要使用克隆?

想对一个对象进行复制,又想保留原有的对象进行接下来的操作,这个时候就需要克隆了。

(2)如何实现对象克隆?

1、实现Cloneable接口,重写clone方法;

2、实现Serializable接口,通过对象的序列化和反序列化实现克隆,可以实现真正的深克隆。

BeanUtils,apache和Spring都提供了bean工具,只是这都是浅克隆。

(3)深拷贝和浅拷贝区别是什么?

浅拷贝:浅拷贝会在堆上创建一个新的对象(区别于引用拷贝的一点),不过,如果原对象内部的属性是引用类型的话,浅拷贝会直接复制内部对象的引用地址,也就是说拷贝对象和原对象共用同一个内部对象。

深拷贝:深拷贝会完全复制整个对象,包括这个对象所包含的内部对象。

17、java 中 IO 流分为几种?

所谓的IO就是实现数据从磁盘的读取和写入。

实际上,除了磁盘以外,内存、网络都可以作为 I/O 流的数据来源和目的地。

在 Java 里面,提供了字符流和字节流两种方式来实现数据流的操作。

Java 里面提供了 Socket的方式来实现数据的网络传输。

18、 常见的IO模型:BIO、NIO、AIO

BIO

定义:同步阻塞IO,传统的IO模型,实现数据从磁盘中的读取以及写入。

特点:简单使用方便,但并发处理能力低。

NIO

定义:同步非阻塞 IO,是传统 IO 的升级,它是支持面向缓冲的,基于通道的 I/O 操作方法。了

特点:多路复用,减少CPU的小号,适用于高并发。

AIO

定义:异步非阻塞IO,是 NIO 的升级,也叫 NIO2,是基于事件和回调机制实现的,也就是应用操作之后会直接返回,不会堵塞在那里,当后台处理完成,操作系统会通知相应的线程进行后续的操作。

特点:异步非阻塞

19、什么是 java 序列化?怎么实现序列化

序列化:把内存中的java对象转换为二进制字节流,用来实现存储或传输

反序列化:将从文件或者网络上获取到的对象的字节流转化为对象

序 列 化 的 实 现 :

只有实现了Serializable和Externalizeble的接口的类才能实现序列化。

Java.IO.ObjectOutputStream代表对象输出流。writeObject(Object obj)方法可对参数指定的obj对象进行序列化。

Java.IO.ObjectInputStream代表对象输入流,它的readObject()方法可以反序列化。

如果类中每个成员变量不想被序列化,可以用transient关键字修饰。

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafka 面试专题

需要全套面试笔记及答案【扫一扫】 即可免费获取**

20、final、finally、finalize的区别?

final:修饰符(关键字)有三种用法:修饰类、变量和方法。修饰类时,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。修饰变量时,该变量使用中不被改变,必须在声明时给定初值,在引用中只能读取不可修改,即为常量。修饰方法时,也同样只能使用,不能在子类中被重写。

finally:通常放在try...catch的后面构造最终执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。

finalize:Object类中定义的方法,Java中允许使用finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize() 方法可以整理系统资源或者执行其他清理工作。

21、Object中有哪些方法?

scss 复制代码
(1)protected Object clone()--->创建并返回此对象的一个副本。 
(2)boolean equals(Object obj)--->指示某个其他对象是否与此对象"相等"。 
(3)protected void finalize()--->当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。 
(4)Class<? extendsObject> getClass()--->返回一个对象的运行时类。 
(5)int hashCode()--->返回该对象的哈希码值。 
(6)void notify()--->唤醒在此对象监视器上等待的单个线程。 
(7)void notifyAll()--->唤醒在此对象监视器上等待的所有线程。 
(8)String toString()--->返回该对象的字符串表示。 
(9)void wait()--->导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。 
	void wait(long timeout)--->导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll()方法,或者超过指定的时间量。 
	void wait(long timeout, int nanos)--->导致当前的线程等待,直到其他线程调用此对象的 notify()

AI生成项目java
运行
1234567891011

22、异常相关

在 Java 中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类有两个重要的子类:
Exception :程序本身可以处理的异常,可以通过 catch 来进行捕获。Exception 又可以分为 Checked Exception (受检查异常,必须处理) 和 Unchecked Exception (非受检查异常,可以不处理)。
Error:Error 属于程序无法处理的错误 ,我们没办法通过 catch 来进行捕获。例如 [Java 虚拟机]运行错误(Virtual MachineError)、虚拟机内存不够错误(OutOfMemoryError)、类定义错误(NoClassDefFoundError)等 。这些异常发生时,Java 虚拟机(JVM)一般会选择线程终止。

1、编译时异常:

IOException 输入输出流异常

FileNotFoundException 文件找不到的异常

ClassNotFoundException 类找不到异常

DataFormatException 数据格式化异常

NoSuchFieldException 没有匹配的属性异常

NoSuchMethodException 没有匹配的方法异常

SQLException 数据库操作异常

TimeoutException 执行超时异常

2、运行时异常(RuntimeException 及其子类都统称为非受检查异常)

ArrayIndexOutofBoundsException 数组越界异常

ClassCastException 类型转换异常

NullPointerException 空指针异常

IllegalAccessException 非法的参数异常

NumberFormatException 字符串转换为数字异常;出现原因:字符型数据中包含非数字型字符。

ArithmeticException 算术异常

23、hashcode是什么?有什么作用?

hashcode是一种编码方式,在Java中,每个对象都会有一个hashcode,Java可以通过这个hashcode来识别一个对象。hashcode方法返回该对象的哈希码值,该方法为哈希表提供一些优点,就是通过这个哈希实现快速查找键对象

1、如果equals相等,hashcode一定相同

2、但hashcode相等,equals不一定相等(hash碰撞)

3、重写equals方法一定要重写hashcode方法(两个相等的对象hashcode一定相同)

HashCode的存在主要是为了查找的快捷性,HashCode是用来在散列存储结构中确定对象的存储地址的。如hashmap、hashtable

24、谈谈你对反射的理解?

(1)反射机制:

所谓的反射机制就是java语言在运行时拥有一项自观的能力。通过这种能力可以彻底的了解自身的情况为下一步的动作做准备。

Java的反射机制的实现要借助于4个类:class,Constructor,Field,Method;

其中class代表的时类对 象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象。通过这四个对象我们可以粗略的看到一个类的各个组 成部分。

(2)Java反射的作用:

在Java运行时环境中,对于任意一个类,可以知道这个类有哪些属性和方法。对于任意一个对象,可以调用它的任意一个方法。这种动态获取类的信息以及动态调用对象的方法 的功能来自于Java 语言的反射(Reflection)机制。

(3)Java 反射机制提供功能

在运行时判断任意一个对象所属的类。

在运行时构造任意一个类的对象。

在运行时判断任意一个类所具有的成员变量和方法。

在运行时调用任意一个对象的方法

优点

  • 增加程序的灵活性,可以在运行的过程中动态对类进行修改和操作
  • 提高代码的复用率,比如动态代理,就是用到了反射来实现

缺点:

  • 破坏了代码本身的抽象性
  • 反射会涉及到动态类型的解析,所以 JVM 无法对这些代码进行优化,导致性能要比非反射调用更低。

25、linux常用命令

三个简单

ls:列出目录中的目录

cd:切换目录

mkdir :创建文件夹

cp:复制文件

mv:移动文件

cat、more:查看文件

三个复杂

ifconfig:查询当前网卡配置信息

tail -f xx.out 动态查看日志

netstat -anp | grep 端口号:查看端口号

top:查看内存

ps aux:查看进程

kill -9 进程号:杀死进程

26、docker常用命令

systemctr start/stop/restart docker 开启/停止/重启docker

docker images 查看所有镜像

docker pull 拉取镜像

docker rmi -f 删除镜像

docker ps 查看正在运行的容器

docker ps -a 查看所有的容器

docker run 启动容器

docker stop 停止容器

docker exec -it '容器名称或容器ID' bash

docker logs -f 容器id/容器名称 实时查看日志

docker logs --tail=500 [容器id] 查看最后500行日志

docker logs -f --since "2022-06-22" [容器id或服务名称] 查看某个时间至今的日志

集合

1、数组和集合的区别

  • 相同点:都是容器,可以存储数据
  • 不同点:
    1、数组长度是不可变的,集合长度可变
    2、数组可以存储基本数据类型和引用数据类型,集合只能存储引用数据类型,如果要存基本数据类型,要转换为其对应的包装类
    3、数组转集合:Arrasys.asList()
    4、集合转数组:list.toArray(new String[list.size()])

2、说一下集合体系?

3、说一下ArrarList和LinkedList区别?

ArrayList

  • ArrayList:底层是动态数组,与Java中的数组相比,它的容量能动态增长。查询效率高(有索引)
  • 时间复杂度:查询时O(1),增删是O(n)

LinkedList

  • linkedList:底层是双向链表,增删效率高,也要看实际情况,对于单条数据的增删,ArrayList的效率反而优于linkedList。对于批量增删,linkedList大大优于ArrayList
  • 时间复杂度:查询O(n),增删O(1)
  • 都不是线程安全的

4、ArrayList和Vector的底层原理和扩容机制

  • ArrayList:是线程不安全的动态数组,JDK1.7后初始化为空数组,在添加第一个元素时初始化为长度为10的数组,如果容量满了,按照1.5倍扩容。支持foreach和Iterator遍历。
  • Vector:是线程安全的动态数组,初始化为长度为10的数组,如果容量满了,按照2.0倍扩容。除了支持foreach和Iterator遍历,还支持Enumeration迭代。

5、HashMap底层原理?

1、当我们往HashMap中put元素时,利用key的hashCode重新hash计算出当前对象的元素在数组中的下标 。

2、存储时,如果出现hash相同的key,有可能会先两种情况,

  • 如果key之前是存在的,则覆盖原始值;
  • 如果key不同则将当前的key-value放入链表中 。(jdk1.7头插法,jdk1.8尾插法)

3、获取时,通过hash直接找到对应的index,在进一步判断key是否相同,从而找到对应的值

JDK1.8之前

拉链法:将链表和数组相结合。也就是说创建一个链表数组,数组中每一格就是一个链表。若遇到哈希冲突,则将冲突的值加到链表中即可。

JDK1.8

jdk1.8是数组+链表+红黑树。当链表长度大于阈值(默认为8) 时且数组长度大于64时,将链表转化为红黑树,以减少搜索时间。当链表长度大于8且数组长度小于64时开始扩容 ,红黑树拆分成的树的结点数小于等于临界值6个,则退化成链表。

6、HashMap中put方法具体流程?

HashMap的底层结构在jdk1.7中由数组+链表实现,在jdk1.8中由数组+链表+红黑树实现,以数组+链表的结构为例。

JDK1.8之前Put方法:

(1)当第一次put时,数组初始化为一个长度为16的Entry数组

(2)特殊考虑:如果key为null,index直接是[0],hash也是0

(3)如果key不为null,会对key的hashCode()值做一个hash(key)再次哈希的运算,这样可以使得Entry对象更加散列的存储到table中

(4)计算当前对象的元素在数组中的下标index = table.length-1 & hash;

(5)如果table[index]为空,创建Entry对象,把value存到数组中,size++

(6)如果table[index]不为空,判断是否出现hash值相同的key

  • 如果key相同,则覆盖原始值;
  • 如果key不同(出现冲突),则将当前的key-value放入链表中 (头插法)

JDK1.8之后Put方法:

(1)当第一次put时,数组初始化为一个长度为16的table数组

(2)特殊考虑:如果key为null,index直接是[0],hash也是0

(3)如果key不为null,会对key的hashCode()值做一个hash(key)再次哈希的运算

(4)计算当前对象的元素在数组中的下标index = table.length-1 & hash;

(5) 如果table[index]==null,那么直接创建一个Node结点存储到table[index]中即可,size++

(6)如果table[index]不为空,判断是否出现hash值相同的key

  • 如果key相同,则覆盖原始值;
  • 如果key不同(出现冲突),则将当前的key-value放入链表中 (尾插法)
    与jdk1.7不同的是,当链表的长度达到8并且数组长度达到64时,此时将链表转化为红黑树去存储,可以提高性能和减少搜索时间。
    如果链表长度大于8,并且数组长度小于64时,并不会树化,而是数组扩容,因为红黑树涉及到左旋、右旋、变色等操作。

7、HashMap扩容机制?

jdk1.7:

css 复制代码
if(size >= threshold  &&  table[index]!=null){
	①会扩容
	②会重新计算key的hash
	③会重新计算index
}

AI生成项目java
运行
12345

jdk1.8:

scss 复制代码
if(size > threshold ){
     ①会扩容
     ②会重新计算key的hash
     ③会重新计算index
 }

AI生成项目java
运行
12345

扩容之后会重新计算key的hash,会重新计算index

rehash: 扩容时在转移元素的过程中,

如 rehash为true,那么该元素转移之后,就有可能被放在新数组任意一个位置。

若 rehash为false,那么该元素转移之后,就会和老数组在同一个位置或者会被转移到元素在老数组位置所在索引 +老数组的长度

8、HashMap的寻址算法?

1、计算出key的hashCode赋值给h

2、hash = key.hashCode() ^ (h >>> 16) 按位异或

3、index = hash & (table.length-1) 按位与

​&(按位与运算):相同的二进制数位上,都是1的时候,结果为1,否则为0。

​4)^(按位异或运算):相同的二进制数位上,数字相同,结果为0,不同为1。

9、HashMap 多线程操作导致死循环问题

JDK1.7 及之前版本的 HashMap 在多线程环境下扩容操作可能存在死循环问题,这是由于当一个桶位中有多个元素需要进行扩容时,多个线程同时对链表进行操作,头插法可能会导致链表中的节点指向错误的位置,从而形成一个环形链表,进而使得查询元素的操作陷入死循环无法结束。

为了解决这个问题,JDK1.8 版本的 HashMap 采用了尾插法而不是头插法来避免链表倒置,使得插入的节点永远都是放在链表的末尾,避免了链表中的环形结构。

但是还是不建议在多线程下使用 HashMap,因为多线程下使用 HashMap 还是会存在数据覆盖的问题。并发环境下,推荐使用 ConcurrentHashMap 。

10、哈希表的底层数组长度为什么是 2 的 n 次方?

因为 2 的 n 次方-1 的二进制值是前面都 0,后面几位都是 1,这样的话,与 hash 进行 &运算的结果就能保证在[0,table.length-1]范围内,而且是均匀的。
保证index可以更散列均匀分布[0,table.length-1],减少hash冲突。

11、HashMap和HashTable区别?

1、HashMap是线程不安全的,HashTable是线程安全的(synchronized修饰的);

2、HashMap底层是数组+链表(jdk1.7)数组+链表+红黑树(jdk1.8),HashTable底层是数组+链表

3、HashMap中允许键和值为null,HashTable不允许;

4、HashMap的默认容器是16,为2倍扩容,HashTable默认是11,为2N+1扩容;

HashMap的线程安全问题可以使用Collections的synchronizedMap(Map<K,V> m) 方法解决。

红黑树:左旋、右旋、变色

12、哪些集合类是线程安全的?

Vector:就比Arraylist多了个同步化机制(线程安全)。

Stack:栈,也是线程安全的,继承于Vector。

Hashtable:就比Hashmap多了个线程安全。

CopyAndWriteList:写时复制,采用ReentrantLock

ConcurrentHashMap:是一种高效且线程安全的集合。

13、Hashmap树化链表长度为什么是8?负载因子为什么是0.75?

链表长度符合泊松分布,各个长度的命中概率逐渐递减,当长度为8时,hash碰撞的概率为千万分之6.

负载因子是计算扩容的阈值,作用就是节省时间和空间,当加载因子为0.75的时候,空间利用率很高,而且避免了相当多的hash冲突,使得底层的链表和红黑树的长度更低,提升了空间利用率。

14、JDK7与JDK8中HashMap的不同点?

1、数据结构

jdk7是数组+链表,jdk8是数组+链表+红黑树

2、链表插入方式

jdk7:头插法,扩容转移元素的时候也是使用的头插法,头插法速度更快,无需遍历链表,但是在多线程扩容的情况下使用头插法会出现循环链表的问题,导致CPU飙升

jdk8:尾插法,反正要去计算链表当前结点的个数,反正要遍历的链表的,所以直接使用尾插法

3、hash算法

jdk7hash算法更复杂,这样hash值越散列,通过二次hash,jdk8没有这个逻辑,jdk8还可以通过红黑树降低hash冲突,提升查询效率

4、扩容机制

jdk7:size >= threshold && table[index]!=null&&老数组的容量没有达到integer最大值。JDK7是每次转移一个元素

jdk8:size >= threshold ,JDK8是先算出来当前位置上哪些元素在新数组的低位上,哪些在新数组的高位上,然后在一次性转移

15、ConcurrentHashMap是怎么保证并发安全的?

jdk1.7 :采用Segments 分段锁,底层是由Segments + HashEntry 链表实现,Segment 继承了 ReentrantLock,所以 Segment 是一种可重入锁,扮演锁的角色。HashEntry 用于存储键值对数据。

jdk1.8:数据结构和hashmap类似。取消了 Segment 分段锁的设计,采用 Node + CAS + synchronized 保证线程安全,锁粒度更细,synchronized 只锁定当前链表或红黑二叉树的首节点,这样只要 hash 不冲突,就不会产生并发,就不会影响其他 Node 的读写,效率大幅提升

16、fail-safe和fail-fast有了解吗?

是多线程并发操作集合时的一种失败处理机制

fail-fast:表示快速失败,在集合遍历中,一旦发现容器中数据修改了,就会抛出异常

java.util 包下的集合类都是快速失败机制的,常见的的使用 fail-fast 方式遍历的容器有 HashMap 和 ArrayList 等。

fail-safe:表示失败安全,在集合遍历中,集合中元素被修改,也不会抛出异常。是因为采用这种机制的集合在遍历时,是先复制原有的集合,在拷贝上的集合进行遍历。

java.util.concurrent 包下的容器都是安全失败的,可以在多线程下并发使用,并发修改。

使用 fail-safe方式有ConcurrentHashMap 和CopyOnWriteArrayList

相关推荐
用户8356290780512 小时前
使用Python自动化移除Excel公式,保留纯净数值
后端·python
nlog3n2 小时前
分布式计数器系统完整解决方案
java·分布式
Java水解2 小时前
SpringBoot 线程池 配置使用详解
spring boot·后端
ytadpole2 小时前
Java并发编程:从源码分析ThreadPoolExecutor 的三大核心机制
java·面试
Aevget2 小时前
「Java EE开发指南」用MyEclipse开发的EJB开发工具(一)
java·ide·java-ee·myeclipse
TanYYF2 小时前
Spring Boot 异步处理框架核心源码解析及实现原理
java·spring boot·spring
karry_k2 小时前
生产者-消费者问题
后端
QZQ541883 小时前
go中channel通信的底层实现
后端
百锦再3 小时前
从 .NET 到 Java 的转型指南:详细学习路线与实践建议
android·java·前端·数据库·学习·.net·数据库架构