1、JDK 和 JRE 有什么区别?
• JDK:Java Development Kit 的简称,Java 开发工具包,提供了 Java 的开发环境和运行
环境。
• JRE:Java Runtime Environment 的简称,Java 运行环境,为 Java 的运行提供了所需环
境。
具体来说 JDK 其实包含了 JRE,同时还包含了编译 Java 源码的编译器 Javac,还包含了
很多 Java 程序调试和分析的工具。简单来说:如果你需要运行 Java 程序,只需安装
JRE 就可以了,如果你需要编写 Java 程序,需要安装 JDK。
2、 List 接口有什么特点?
顺序存储、可以有重复值。
3、&和&&的区别
&是位运算符。&&是布尔逻辑运算符,在进行逻辑判断时用&处理的前面为 false 后面的
内容仍需处理,用&&处的前面为 false 不再处理后面的内容。
4、重载和重写
1、Overload 为重载,Override 为重写方法的重写和重载是 Java 多态性的不同表现。重写
是父类与子类之间多态性的一种表现,重载是一个类中多态性的一种表现。
2、如果在子类中定义某方法与其父类有相同的名称和参数,我们说该方法被重写
(Override)。子类的对象使用这个方法时,将调用子类中的定义,对它而言, 父类中的定
义如同被“屏蔽“了
5、== 和 equals 的区别是什么?
== 解读 对于基本类型和引用类型 == 的作用效果是不同的,如下所示:
• 基本类型:比较的是值是否相同;
• 引用类型:比较的是引用是否相同; 代码示例:
代码示例:
String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); // true
System.out.println(x==z); // false
System.out.println(x.equals(y)); // true
System.out.println(x.equals(z)); // true
代码解读:因为 x 和 y 指向的是同一个引用,所以 == 也是 true,而 new String()方法
则重写开辟了内存空间,所以 == 结果为 false,而 equals 比较的一直是值,所以结果
都为 true。
equals 解读
equals 本质上就是 ==,只不过 String 和 Integer 等重写了 equals 方法,把它变成了
值比较。看下面的代码就明白了。
首先来看默认情况下 equals 比较一个有相同值的对象,代码如下:
class Cat {
private String name;
public Cat(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Cat c1 = new Cat("码农 code 之路");
Cat c2 = new Cat("码农 code 之路");
System.out.println(c1.equals(c2)); // false
输出结果出乎我们的意料,竟然是 false?这是怎么回事,看了 equals 源码就知道了,源
码如下:
public boolean equals(Object obj) {
return (this == obj);
}
原来 equals 本质上就是 ==。
那问题来了,两个相同值的 String 对象,为什么返回的是 true?代码如下:
String s1 = new String("老王");
String s2 = new String("老王");
System.out.println(s1.equals(s2)); // true
同样的,当我们进入 String 的 equals 方法,找到了答案,代码如下:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}
原来是 String 重写了 Object 的 equals 方法,把引用比较改成了值比较。
总结 :== 对于基本类型来说是值比较,对于引用类型来说是比较的是引用;而 equals
默认情况下是引用比较,只是很多类重新了 equals 方法,比如 String、Integer 等把它
变成了值比较,所以一般情况下 equals 比较的是值是否相等。
6、Set 接口有什么特点
无续存储、不能有重复值。
7、两个对象的 hashCode() 相同,则 equals() 也一定为 true,对
吗?
不对,两个对象的 hashCode() 相同,equals() 不一定 true。
代码示例:
String str1 = "通话";
String str2 = "重地";
System. out. println(String. format("str1:%d | str2:%d", str1.
hashCode(),str2. hashCode()));
System. out. println(str1. equals(str2));
执行的结果:
str1:1179395 | str2:1179395
false
代码解读:很显然“通话”和“重地”的 hashCode() 相同,然而 equals() 则为 false,因为在
散列表中,hashCode() 相等即两个键值对的哈希值相等,然而哈希值相等,并不一定能得
出键值对相等。
8、ArrayList 与 LinkedList 有什么区别?
1、ArrayList 与 LinkedList 都实现了 List 接口。
2、ArrayList 是线性表,底层是使用数组实现的,它在尾端插入和访问数据时效率较高。
3、 Linked 是双向链表,他在中间插入或者头部插入时效率较高,在访问数据时 效率较
低
9、 final 在 Java 中有什么作用?
• final 修饰的类叫最终类,该类不能被继承。
• final 修饰的方法不能被重写。
• final 修饰的变量叫常量,常量必须初始化,初始化之后值就不能被修改。
10、Array 与 ArrayList 有什么不一样?
Array 与 ArrayList 都是用来存储数据的集合。ArrayList 底层是使用数组实现的, 但是
arrayList 对数组进行了封装和功能扩展,拥有许多原生数组没有的一些功能。 我们可以理
解成 ArrayList 是 Array 的一个升级版。
11、 Java 中的 Math. round(-1. 5) 等于多少?
等于 -1,因为在数轴上取值时,中间值(0.5)向右取整,所以正 0.5 是往上取整,负
0.5 是直接舍弃。
12、 JDBC 操作的步骤
1、加载数据库驱动类
2、打开数据库连接
3、执行 sql 语句
4、处理返回结果
5、关闭资源
13、在使用 jdbc 的时候,如何防止出现 sql 注入
使用 CallableStatement
14、怎么在 JDBC 内调用一个存储过程
使用 PreparedStatement 类,而不是使用 Statement 类
15、 String 属于基础的数据类型吗?
String 不属于基础类型,基础类型有 8 种:byte、boolean、char、short、int、float、
long、double,而 String 属于对象。
16、Map 有什么特点?
1、以键值存储数据
2、元素存储循序是无序的
3、不允许岀现重复键
17、Java 的 IO 流分为哪几种?
按功能分:
输入流(input),输出流(output)
按类型分:
字节流,字符流
18、常用 io 类有那些?
FilelnputSteam, FileOutputStream
Bufferinputstream, BufferedOutputSream
PrintWrite
FileReader, FileWriter
BufferReader, BufferedWriter
Objectinputstream, ObjectOutputSream
19、字节流与字符流的区别
1、 以字节为单位输入输出数据,字节流按照 8 位传输
2、 以字符为单位输入输出数据,字符流按照 16 位传输
20、 Java 中操作字符串都有哪些类?它们之间有什么区别?
操作字符串的类有:String、StringBuffer、StringBuilder。
String 和 StringBuffer、StringBuilder 的区别在于 String 声明的是不可变的对象,每次
操作都会生成新的 String 对象,然后将指针指向新的 String 对象,而 StringBuffer、
StringBuilder 可以在原有对象的基础上进行操作,所以在经常改变字符串内容的情况下最
好不要使用 String。
StringBuffer 和 StringBuilder 最大的区别在于,StringBuffer 是线程安全的,而
StringBuilder 是非线程安全的,但 StringBuilder 的性能却高于 StringBuffer,所以在单
线程环境下推荐使用 StringBuilder,多线程环境下推荐使用 StringBuffer。
21、 String str=”i”与 String str=new String(“i”)一样吗?
不一样,因为内存的分配方式不一样。String str=”i”的方式,Java 虚拟机会将其分配到常
量池中;而 String str=new String(“i”) 则会被分到堆内存中。
22、是否了解连接池,使用连接池有什么好处?
数据库连接是非常消耗资源的,影响到程序的性能指标。连接池是用来分配、管 理、释放
数据库连接的,可以使应用程序重冥使用同一个数据库连接,而不是每 次都创建一个新的
数据库连接。通过释放空闲时间较长的数据库连接避免数据库 因为创建太多的连接而造成
的连接遗漏问题,提高了程序性能。
23、你所了解的数据源技术有那些?使用数据源有什么好处?
Dbcp,c3p0 等,用的最多还是 c3pO,因为 c3p0 比 dbcp 更加稳定,安全;通过 配置文件
的形式来维护数据库信息,而不是通过硬编码。当连接的数据库信息发 生改変时,不需要
再更改程序代码就实现了数据库信息的更新。
24、如何将字符串反转?
使用 StringBuilder 或者 stringBuffer 的 reverse() 方法。
示例代码:
// StringBuffer reverse
StringBuffer stringBuffer = new StringBuffer();
stringBuffer. append("abcdefg");
System. out. println(stringBuffer. reverse()); // gfedcba
// StringBuilder reverse
StringBuilder stringBuilder = new StringBuilder();
stringBuilder. append("abcdefg");
System. out. println(stringBuilder. reverse()); // gfedcba
25、String 类的常用方法都有那些?
• indexOf():返回指定字符的索引。
• charAt():返回指定索引处的字符。
• replace():字符串替换。
• trim():去除字符串两端空白。
• split():分割字符串,返回一个分割后的字符串数组。
• getBytes():返回字符串的 byte 类型数组。
• length():返回字符串长度。
• toLowerCase():将字符串转成小写字母。
• toUpperCase():将字符串转成大写字符。
• substring():截取字符串。
• equals():字符串比较。
26、final,finally,finalize 区别
1、final 为关键字
2、finalizeO 为方法
3、finally 为区块标志,用于 try 语句中
27、 final、finalize、finally,作用
1、final 为用于标识常量的关键字,final 标识的关键字存储在常量池中
2、 finalizeO 方法在 0bject 中进行了定义,用于在对象”消失”时,由 JVM 进行调用用于对
对象进行垃圾回收,类似于 C+ +中的析构函数;用户自定义时,用于释放对象占用的资源
(比如进行 I/O 操作)
3、finally{}用于表示代码块,与 try{}进行配合,不论 try 中的代码执行完或没有执行完,该
代码之中的程序必定会执行
28、抽象类必须要有抽象方法吗?
不需要,抽象类不一定非要有抽象方法。
示例代码:
abstract class Cat {
public static void sayHi() {
System. out. println("hi~");
}
}
上面代码,抽象类并没有抽象方法但完全可以正常运行。
29、普通类和抽象类有哪些区别?
• 普通类不能包含抽象方法,抽象类可以包含抽象方法。
• 抽象类不能直接实例化,普通类可以直接实例化。
30、抽象类能使用 final 修饰吗?
不能,定义抽象类就是让其他类继承的,如果定义为 final 该类就不能被继承,这样彼此
就会产生矛盾,所以 final 不能修饰抽象类,如下图所示,编辑器也会提示错误信息:
31、 接口和抽象类有什么区别?
• 实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。
• 构造函数:抽象类可以有构造函数;接口不能有。
• 实现数量:类可以实现很多个接口;但是只能继承一个抽象类。
• 访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方法可以是任意访问修
饰符。
32、如果对象的引用被置为 null,垃圾收集器是否会立即释放对象占用
的内存
不会,在下一个垃圾回收周期中,这个对象将是可被回收的。
33、串行(serial)收集器和吞吐量(throughput)收集器的区别是什么?
吞吐量收集器使用并行版本的新生代垃圾收集器,它用于中等规模和大规模数据的应用程
序。而串行收集器对大多数的小应用(在现代处理器上需要大概 100M 左右的内存)就足
够了。
34、 Java 中 IO 流分为几种?
按功能来分:输入流(input)、输出流(output)。
按类型来分:字节流和字符流。
字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16
位传输以字符为单位输入输出数据。
35、 BIO、NIO、AIO 有什么区别?
• BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用
方便,并发处理能力低。
• NIO:Non IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel
(通道)通讯,实现了多路复用。
• AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO
的操作基于事件和回调机制。
36、Files 的常用方法都有哪些?
• Files. exists():检测文件路径是否存在。
• Files. createFile():创建文件。
• Files. createDirectory():创建文件夹。
• Files. delete():删除一个文件或目录。
1.什么是 B/S 架构?什么是 C/S 架构
B/S(Browser/Server),浏览器/服务器程序;
C/S(Client/Server),客户端/服务端,桌面应用程序
2.Java 都有那些开发平台?
JAVA SE :主要用在客户端开发
JAVA EE :主要用在 web 应用程序开发
JAVA ME :主要用在嵌入式应用程序开发
3.什么是 JVM? java 虚拟机包括什么?
JVM : java 虚拟机,运用硬件或软件手段实现的虚拟的计算机
Java 虚拟机包括: 寄存器,堆栈,处理器
4.Java 是否需要开发人员回收内存垃圾吗?
大多情况下是不需要的。Java 提供了一个系统级的线程来跟踪内存分配,不再使用的内存
区将会自动回收
5.类与对象的关系?
类是对象的抽象,对象是类的具体,类是对象的模板,对象是类的实例
6.Super 与 this 表示什么?
Super 表示当前类的父类对象
This 表示当前类的对象
7.什么是隐式转换,什么是显式转换?
显示转换就是类型强转,把一个大类型的数据强制赋值给小类型的数据;隐式转換就是大
范围的变量能够接受小范围的数据;隐式转换和显式转换其实就是自动类型转换和强制类
型转换。
8.什么是拆装箱?
拆箱:把包装类型转成基本数据类型
装箱:把基本数据类型转成包装类型
9.java 中有没有指针?
有指针,但是隐藏了,开发人员无法直接操作指针,由 jvm 来操作指针。
10.java 中是值传递引用传递?
理论上说,java 都是引用传递,对于基本数据类型,传递是值的副本,而不是值 本身。对
于对象类型,传递是对象的引用,当在一个方法操作操作参数的时候, 其实操作的是引用
所指向的对象。
11.假设把实例化的数组的变量当成方法参数,当方法执行的时候改变
了数组内的元素,那么在方法外,数组元素有发生改变吗?
改变了,因为传递是对象的引用,操作的是引用所指向的对象
12.实例化数组后,能不能改变数组长度呢?
不能,数组一旦实例化,它的长度就是固定的。
13.Java 中操作字符串使用哪个类?
String, StringBuffer, StringBuilder
14.StringBuffer, Stringbuilder 有什么区别?
StringBuffer 与 StringBuilder 都继承了 AbstractStringBulder 类
在做字符串拼接修改删除昔换时,效率比 string 更高。
StringBuffer 是线程安全的,StringBuilder 是非线程安全的。所以 StringBuffer 比
StringBuilder 效率更高,StringBuffer 的方法大多都加了 synchronized 关键字
15.String str=” aaa”,与 String str=new String(“aaa”)一样吗?
1、 不一样的。因为内存分配的方式不一样。
2、 第一种,创建的”aaa”是常量,jvm 都将其分配在常量池中。
3、 第二种创建的是一个对象,jvm 将其值分配在堆内存中。
16.String str=” aa” ,String s=” bb” ,String aa=aa+s;—种创建了几个对象?
一共有两个引用,三个对象。因为”aa”与”bb”都是常量,常量的值不能改变,档执行字符
串拼接时候,会创建一个新的常量是”aabbb”,有将其存到常量池中。
17.什么事父类引用指向子类对象?
是 java 多态一种特殊的表现形式。创建父类引用,让该引用指向一个子类的对象。
18.JAVA 为什么需要接口?
接口弥补了 java 单继承的缺点。
19.接口有什么特点?
1、 接口中声明全是 public static final 修饰的常量;
2、 接口中所有方法都是抽象方法;
3、 接口是没有构造方法的;
4、 接口也不能直接实例化;
5、接口可以多继承。
20.Java 中异常分为哪两种?
1、编译时异常;
2、运行时异常。
21.说几个常见的编译时异常类?
1、NullPointerException:空指针异常
2、ArraylndexOutOfBoundsException:数组下标越界
3、NumberFormatException:数字转换异常
4、HlegalArgumentException:参数不匹配异常
5、InstantiationException:对象初始化导常
22.throw 与 throws 区别
1、throws:用来声明一个方法可能产生的所有异常,不做任何处理而是将异常往上传,谁
调用我我就抛给谁。
• 用在方法声明后面,跟的是异常类名
• 可以跟多个异常类名,用逗号隔开
• 表示抛出异常,由该方法的调用者来处理
• throws 表示出现异常的一种可能性,并不一定会发生这些异常
2、throw:则是用来抛出一个具体的异常类型。
• 用在方法体内,跟的是异常对象名
• 只能抛出一个异常对象名
• 表示抛出异常,由方法体内的语句处理
• throw 则是抛出了异常,执行 throw 则一定抛出了某种异常
23.Error 与 Exception 区别?
Error 和 Exception 都是 java 错误处理机制的一部分,都继承了 Throwable 类。
Exception 表示的异常,异常可以通过程序来捕捉,或者优化程序来避免。
24.java 容器都有哪些?
常用容器的图录:

25.Collection 和 Collections 有什么区别?
java.util.Collection 是一个集合接口(集合类的一个顶级接口)。它提供了对集合对象进行
基本操作的通用接口方法。Collection 接口在 Java 类库中有很多具体的实现。Collection
接口的意义是为各种具体的集合提供了最大化的统一操作方式,其直接继承接口有 List 与
Set。
Collections 则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中
元素进行排序、搜索以及线程安全等各种操作。
26.List、Set、Map 之间的区别是什么?
List、Set、Map 的区别主要体现在两个方面:元素是否有序、是否允许元素重复。
三者之间的区别,如下表:
| 比较 | List | Set | Map |
|---|---|---|---|
| 继承接口 | Collection | Collection | |
| 常见实现类 | AbstractList (其常用子类有ArrayList,LinkedList,Vector) | AbstractList (其常用子类有HashSet,LinkedHashSet,TreeSet)类常见 | HashMap、HashTable |
| 常见方法 | add(),remove(),clear(),get(), contains(),size()等 |
add(),remove(),clear(),add(), remove(),clear()等 |
put(),get(),remove(), clear(),containsKey() |
| 元素 | 可重复 | 不可重复 | 不可重复 |
| 顺序 | 无序 | 有序 | |
| 线程安全 | Vector线程安全 | HashTable线程安全 |
27.HashMap 和 Hashtable 有什么区别?
hashMap 去掉了 HashTable 的 contains 方法,但是加上了 containsValue()和
containsKey()方法。
hashTable 同步的,而 HashMap 是非同步的,效率上逼 hashTable 要高。
hashMap 允许空键值,而 hashTable 不允许。
28.如何决定使用 HashMap 还是 TreeMap?
对于在 Map 中插入、删除和定位元素这类操作,HashMap 是最好的选择。然而,假如你
需要对一个有序的 key 集合进行遍历,TreeMap 是更好的选择。基于你的 collection 的大
小,也许向 HashMap 中添加元素会更快,将 map 换为 TreeMap 进行有序 key 的遍历。
29.说一下 HashMap 的实现原理?
HashMap 概述:HashMap 是基于哈希表的 Map 接口的非同步实现。此实现提供所有可选
的映射操作,并允许使用 null 值和 null 键。此类不保证映射的顺序,特别是它不保证该顺
序恒久不变。
HashMap 的数据结构:在 java 编程语言中,最基本的结构就是两种,一个是数组,另外
一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap
也不例外。HashMap 实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
当我们往 Hashmap 中 put 元素时,首先根据 key 的 hashcode 重新计算 hash 值,根绝 hash
值得到这个元素在数组中的位置(下标),如果该数组在该位置上已经存放了其他元素,那么在
这个位置上的元素将以链表的形式存放,新加入的放在链头,最先加入的放入链尾.如果数组
中该位置没有元素,就直接将该元素放到数组的该位置上。
需要注意 Jdk 1.8 中对 HashMap 的实现做了优化,当链表中的节点数据超过八个之后,该链
表会转为红黑树来提高查询效率,从原来的 O(n)到 O(logn)
30.说一下 HashSet 的实现原理?
HashSet 底层由 HashMap 实现
HashSet 的值存放于 HashMap 的 key 上
HashMap 的 value 统一为 PRESENT
31.ArrayList 和 LinkedList 的区别是什么?
最明显的区别是 ArrrayList 底层的数据结构是数组,支持随机访问,而 LinkedList 的底层
数据结构是双向循环链表,不支持随机访问。使用下标访问一个元素,ArrayList 的时间复
杂度是 O(1),而 LinkedList 是 O(n)。
32.如何实现数组和 List 之间的转换?
数组转 List:使用 Arrays. asList(array) 进行转换。
List 转数组:使用 List 自带的 toArray() 方法。
代码示例:
// list to array
List
list. add(“码农 code 之路”);
list. add(“关注公众号:码农 code 之路”);
list. toArray();
// array to list
String[] array = new String[]{“码农 code 之路”,”关注公众号:码农 code 之路
“};
Arrays. asList(array);
33.ArrayList 和 Vector 的区别是什么?
Vector 是同步的,而 ArrayList 不是。然而,如果你寻求在迭代的时候对列表进行改变,你
应该使用 CopyOnWriteArrayList。
ArrayList 比 Vector 快,它因为有同步,不会过载。
ArrayList 更加通用,因为我们可以使用 Collections 工具类轻易地获取同步列表和只读列
表。
34.Array 和 ArrayList 有何区别?
Array 可以存储基本数据类型和对象,ArrayList 只能存储对象。
Array 是指定固定大小的,而 ArrayList 大小是自动扩展的。
Array 内置方法没有 ArrayList 多,比如 addAll、removeAll、iteration 等方法只有
ArrayList 有。
35.在 Queue 中 poll()和 remove()有什么区别?
相同点:都是返回第一个元素,并在队列中删除返回的对象。
不同点:如果没有元素 poll()会返回 null,而 remove()会直接抛出
NoSuchElementException 异常。
代码示例:
Queue
queue. offer(“string”); // add
System. out. println(queue. poll());
System. out. println(queue. remove());
System. out. println(queue. size());
36.哪些集合类是线程安全的?
vector:就比 arraylist 多了个同步化机制(线程安全),因为效率较低,现在已经不太建
议使用。在 web 应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
statck:堆栈类,先进后出。
hashtable:就比 hashmap 多了个线程安全。
enumeration:枚举,相当于迭代器。
37.迭代器 Iterator 是什么?
迭代器是一种设计模式,它是一个对象,它可以遍历并选择序列中的对象,而开发人员不
需要了解该序列的底层结构。迭代器通常被称为“轻量级”对象,因为创建它的代价小。
38.Iterator 怎么使用?有什么特点?
Java 中的 Iterator 功能比较简单,并且只能单向移动:
(1) 使用方法 iterator()要求容器返回一个 Iterator。第一次调用 Iterator 的 next()方法时,
它返回序列的第一个元素。注意:iterator()方法是 java.lang.Iterable 接口,被 Collection 继
承。
(2) 使用 next()获得序列中的下一个元素。
(3) 使用 hasNext()检查序列中是否还有元素。
(4) 使用 remove()将迭代器新返回的元素删除。
Iterator 是 Java 迭代器最简单的实现,为 List 设计的 ListIterator 具有更多的功能,它可以
从两个方向遍历 List,也可以从 List 中插入和删除元素。
39.Iterator 和 ListIterator 有什么区别?
Iterator 可用来遍历 Set 和 List 集合,但是 ListIterator 只能用来遍历 List。
Iterator 对集合只能是前向遍历,ListIterator 既可以前向也可以后向。
ListIterator 实现了 Iterator 接口,并包含其他的功能,比如:增加元素,替换元素,获取
前一个和后一个元素的索引,等等。
40.怎么确保一个集合不能被修改?
• 可以使用 Collections. unmodifiableCollection(Collection c) 方法来创建一个只读集合,
这样改变集合的任何操作都会抛出 Java. lang. UnsupportedOperationException 异常。
• 示例代码如下:
List<String> list = new ArrayList<>();
list. add("x");
Collection<String> clist = Collections. unmodifiableCollection(list);
clist. add("y"); // 运行时此行报错
System. out. println(list. size())
1、什么是Class 文件? Class 文件主要的信息结构有哪些?
Class 文件是一组以 8 位字节为基础单位的二进制流。各个数据项严格按顺序排列。
Class 文件格式采用一种类似于 C 语言结构体的伪结构来存储数据。这样的伪结构仅仅有
两种数据类型:无符号数和表。
无符号数:是基本数据类型。以 u1、u2、u4、u8 分别代表 1 个字节、2 个字节、4 个
字节、8 个字节的无符号数,能够用来描写叙述数字、索引引用、数量值或者依照 UTF-8
编码构成的字符串值。
表:由多个无符号数或者其它表作为数据项构成的复合数据类型。全部表都习惯性地以_info 结尾。
2、并发编程三个必要因素是什么?
原子性:
原子,即一个不可再被分割的颗粒。原子性指的是一个或多个操作要么全部执行成功要么全部执行失败。
可见性:
一个线程对共享变量的修改,另一个线程能够立刻看到。(synchronized,volatile)
有序性:
程序执行的顺序按照代码的先后顺序执行。(处理器可能会对指令进行重排序)
3、说下有哪些类加载器?
Bootstrap ClassLoader(启动类加载器) Extention ClassLoader(扩展类加载器) AppClassLoader(应用类加载器)
线程调度器选择优先级最高的线程运行,但是,如果发生以下情况,就会终止线程的运
行:
1、 线程体中调用了 yield 方法让出了对 cpu 的占用权利
2、 线程体中调用了 sleep 方法使线程进入睡眠状态
3、 线程由于 IO 操作受到阻塞
4、 另外一个更高优先级线程出现
5)在支持时间片的系统中,该线程的时间片用完
5、用Java写一个冒泡排序。
冒泡排序几乎是个程序员都写得出来,但是面试的时候如何写一个逼格高的冒泡排序却不
是每个人都能做到,下面提供一个参考代码:
public class BubbleSortTest {
public static void main(String[] args) {
int[] arr = new int[]{-12,3,2,34,5,8,1};
//冒泡排序
for(int i = 0;i < arr.length-1;i++){
for(int j = 0;j <arr.length-1-i;j++){
if(arr[j] >arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
//遍历
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]+"\t");
}
}
}
6、Java对象的布局了解过吗?
1、对象头区域此处存储的信息包括两部分:1、对象自身的运行时数据( MarkWord ),占 8 字节 存hashCode、GC 分代年龄、锁类型标记、偏向锁线程 ID 、 CAS 锁指向线程LockRecord 的指针等, synconized 锁的机制与这个部分( markwork )密切相关,用markword 中最低的三位代表锁的状态,其中一位是偏向锁位,另外两位是普通锁位。
2、对象类型指针( Class Pointer ),占 4 字节 对象指向它的类元数据的指针、 JVM 就是通过
它来确定是哪个 Class 的实例。
实例数据区域 此处存储的是对象真正有效的信息,比如对象中所有字段的内容
对齐填充区域 JVM的实现 HostSpot 规定对象的起始地址必须是 8 字节的整数倍,换句
话来说,现在 64 位的 OS 往外读取数据的时候一次性读取 64bit 整数倍的数据,也就
是 8 个字节,所以 HotSpot 为了高效读取对象,就做了”对齐”,如果一个对象实际占的
内存大小不是8byte的整数倍时,就”补位”到 8byte 的整数倍。所以对齐填充区域的大
小不是固定的。
7、解释什么是Tomcat Valve?
Tomcat Valve——Tomcat 4 引入的新技术,它允许您将 Java 类的实例链接到一个特定的
Catalina 容器。
8、同步方法和同步块,哪个是更好的选择?
同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。同
步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止
执行并需要等待获得这个对象上的锁。
同步块更要符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样从侧面来
说也可以避免死锁。
9、运行时常量池的作用是什么?
运行时常量池是方法区的一部分,Class 文件中除了有类的版本、字段、方法、接口等描
述信息外,还有一项信息是常量池表,用于存放编译器生成的各种字面量与符号引用,这
部分内容在类加载后存放到运行时常量池。一般除了保存 Class 文件中描述的符号引用
外,还会把符号引用翻译的直接引用也存储在运行时常量池。
运行时常量池相对于 Class 文件常量池的一个重要特征是动态性,Java 不要求常量只有
编译期才能产生,运行期间也可以将新的常量放入池中,这种特性利用较多的是 String
的 intern 方法。
运行时常量池是方法区的一部分,受到方法区内存的限制,当常量池无法再申请到内存时
会抛出 OutOfMemoryError。
10、如果你提交任务时,线程池队列已满,这时会发生什么
有俩种可能:
1、 如果使用的是无界队列 LinkedBlockingQueue,也就是无界队列的话,没关系,继续
添加任务到阻塞队列中等待执行,因为 LinkedBlockingQueue 可以近乎认为是一个无穷
大的队列,可以无限存放任务
2、 如果使用的是有界队列比如 ArrayBlockingQueue,任务首先会被添加到
ArrayBlockingQueue 中,ArrayBlockingQueue 满了,会根据 maximumPoolSize 的值增
加线程数量,如果增加了线程数量还是处理不过来,ArrayBlockingQueue 继续满,那么则
会使用拒绝策略 RejectedExecutionHandler 处理满了的任务,默认是 AbortPolicy
11、如何写一段简单的死锁代码?
这个笔试的话频率也挺高(遇见笔试的公司要三思啊),所以这里直接给出一个答案(有
很多版本的)。
12、栈帧都有哪些数据?
JVM 的运行是基于栈的,和 C 语言的栈类似,它的大多数数据都是在堆里面的,只有少部
分运行时的数据存在于栈上。
在 JVM 中,每个线程栈里面的元素,就叫栈帧。
栈帧包含:局部变量表、操作数栈、动态连接、返回地址等。
13、float f=3.4;是否正确?
答:不正确。3.4 是双精度数,将双精度型(double)赋值给浮点型(float)属于下转型
(down-casting,也称为窄化)会造成精度损失,因此需要强制类型转换 float f
=(float)3.4; 或者写成 float f =3.4F;。
14、形参与实参
形参:全称为“形式参数”,是在定义方法名和方法体的时候使用的参数,用于接收调用该
方法时传入的实际值;实参:全称为“实际参数”,是在调用方法时传递给该方法的实际
值。
15、BIO、NIO、AIO有什么区别?
BIO:Block IO 同步阻塞式 IO,就是我们平常使用的传统 IO,它的特点是模式简单使用
方便,并发处理能力低。
NIO:New IO 同步非阻塞 IO,是传统 IO 的升级,客户端和服务器端通过 Channel(通
道)通讯,实现了多路复用。
AIO:Asynchronous IO 是 NIO 的升级,也叫 NIO2,实现了异步非堵塞 IO ,异步 IO
的操作基于事件和回调机制。
16、什么是自旋
很多 synchronized 里面的代码只是一些很简单的代码,执行时间非常快,此时等待的线程
都加锁可能是一种不太值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既
然 synchronized 里面的代码执行得非常快,不妨让等待锁的线程不要被阻塞,而是在
synchronized 的边界做忙循环,这就是自旋。如果做了多次忙循环发现还没有获得锁,再
阻塞,这样可能是一种更好的策略。
17、我们可以在hashcode()中使用随机数字吗?
答案
http://javarevisited.blogspot.sg/2011/10/override-hashcode-in-java-example.html
不行,因为对象的 hashcode 值必须是相同的。参见答案获取更多关于 Java 中重写hashCode() 方法的知识。
18、你所知道网络协议有那些?
1、 HTTP:超文本传输协议
2、 FTP:文件传输协议
3、 SMPT:简单邮件协议
4、 TELNET:远程终端协议
5、 POP3:邮件读取协议
19、构造器Constructor 是否可被override
在讲继承的时候我们就知道父类的私有属性和构造方法并不能被继承,所以 Constructor
也就不能被 override,但是可以 overload,所以你可以看到一个类中有多个构造函数的情况。
20、什么是双亲委派机制?
双亲委派机制的意思是除了顶层的启动类加载器以外,其余的类加载器,在加载之前,都
会委派给它的父加载器进行加载。这样一层层向上传递,直到祖先们都无法胜任,它才会
真正的加载。
21、讲讲什么情况下会出现内存溢出,内存泄漏?
内存泄漏的原因很简单:
1、 对象是可达的(一直被引用)
2、 但是对象不会被使用
常见的内存泄漏例子:
解决这个内存泄漏问题也很简单,将 set 设置为 null,那就可以避免上述内存泄漏问题
了。其他内存泄漏得一步一步分析了。
内存溢出的原因:
1、 内存泄露导致堆栈内存不断增大,从而引发内存溢出。
2、 大量的 jar,class 文件加载,装载类的空间不够,溢出
3、 操作大量的对象导致堆内存空间已经用满了,溢出
4、 nio 直接操作内存,内存过大导致溢出
解决:
1、 查看程序是否存在内存泄漏的问题
2、 设置参数加大空间
3、 代码中是否存在死循环或循环产生过多重复的对象实体、
4、 查看是否使用了 nio 直接操作内存。
22、乐观锁和悲观锁的理解及如何实现,有哪些实现方式?
悲观锁:
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候
都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用
到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再
比如 Java 里面的同步原语 synchronized 关键字的实现也是悲观锁。
乐观锁:
顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是
在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。
乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于
write_condition 机制,其实都是提供的乐观锁。在 Java 中 java.util.concurrent.atomic 包
下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的。
23、线程与进程的区别?
进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元。
一个程序至少有一个进程,一个进程至少有一个线程。
24、Session 的 save()、update()、merge()、lock()、saveOrUpdate()和 persist()方法分别是做什么的?有什么区别?
瞬时态的实例可以通过调用 save()、persist()或者 saveOrUpdate()方法变成持久态;游离态
的实例可以通过调用 update()、saveOrUpdate()、lock()或者 replicate()变成持久态。
save()和 persist()将会引发 SQL 的 INSERT 语句,而 update()或 merge()会引发 UPDATE 语
句。save()和 update()的区别在于一个是将瞬时态对象变成持久态,一个是将游离态对象变
为持久态。merge()方法可以完成 save()和 update()方法的功能,它的意图是将新的状态合
并到已有的持久化对象上或创建新的持久化对象。
对于 persist()方法,
persist()方法把一个瞬时态的实例持久化,但是并不保证标识符被立刻填入到持久化实例
中,标识符的填入可能被推迟到 flush 的时间;
persist()方法保证当它在一个事务外部被调用的时候并不触发一个 INSERT 语句,当需要封
装一个长会话流程的时候,persist()方法是很有必要的;
save()方法不保证第②条,它要返回标识符,所以它会立即执行 INSERT 语句,不管是在事
务内部还是外部。至于 lock()方法和 update()方法的区别,update()方法是把一个已经更改
过的脱管状态的对象变成持久状态;lock()方法是把一个没有更改过的脱管状态的对象变成
持久状态。
25、用代码演示三种代理
静态代理:
什么是静态代理
由程序员创建或工具生成代理类的源码,再编译代理类。所谓静态也就是在程序运行前就
已经存在代理类的字节码文件,代理类和委托类的关系在运行前就确定了。
缺点:
每个需要代理的对象都需要自己重复编写代理,很不舒服,
优点:
但是可以面相实际对象或者是接口的方式实现代理
动态代理:
什么是动态代理
动态代理也叫做,JDK 代理、接口代理。
动态代理的对象,是利用 JDK 的 API,动态的在内存中构建代理对象(是根据被代理的接
口来动态生成代理类的 class 文件,并加载运行的过程),这就叫动态代理
代码演示
package com.lijie; //接口 public interface UserDao { void save(); }
package com.lijie; //接口实现类
public class UserDaoImpl implements UserDao {
public void save() {
System.out.println("保存数据方法");
}
}
下面是代理类,可重复使用,不像静态代理那样要自己重复编写代理
必须是面向接口,目标业务类必须实现接口
优点:
不用关心代理类,只需要在运行阶段才指定代理哪一个对象
CGLIB 动态代理
CGLIB 动态代理原理:
利用 asm 开源包,对代理对象类的 class 文件加载进来,通过修改其字节码生成子类来处
理。
什么是 CGLIB 动态代理
CGLIB 动态代理和 jdk 代理一样,使用反射完成代理,不同的是他可以直接代理类(jdk 动
态代理不行,他必须目标业务类必须实现接口),CGLIB 动态代理底层使用字节码技术,
CGLIB 动态代理不能对 final 类进行继承。(CGLIB 动态代理需要导入 jar 包)
stackoverflow 错误主要出现:
在虚拟机栈中(线程请求的栈深度大于虚拟机栈锁允许的最大深度)
permgen space 错误(针对 jdk 之前 1.7 版本):
1、 大量加载 class 文件
2、 常量池内存溢出
27、分代收集算法
当前主流 VM 垃圾收集都采用”分代收集” (Generational Collection)算法, 这种算法会根据
对象存活周期的不同将内存划分为几块, 如 JVM 中的新生代、老年代、永久代, 这样就
可以根据各年代特点分别采用最适当的 GC 算法
28、同步方法和同步块,哪个是更好的选择?
同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。同
步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止
执行并需要等待获得这个对象上的锁。
同步块更要符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样从侧面来
说也可以避免死锁。
请知道一条原则:同步的范围越小越好。
29、Java 中的编译期常量是什么?使用它又什么风险?
公共静态不可变(public static final )变量也就是我们所说的编译期常量,这里的 public
可选的。实际上这些变量在编译时会被替换掉,因为编译器知道这些变量的值,并且知道
这些变量在运行时不能改变。这种方式存在的一个问题是你使用了一个内部的或第三方库
中的公有编译时常量,但是这个值后面被其他人改变了,但是你的客户端仍然在使用老的
值,甚至你已经部署了一个新的 jar。为了避免这种情况,当你在更新依赖 JAR 文件时,
确保重新编译你的程序。
30、Java 死锁以及如何避免?
Java 中的死锁是一种编程情况,其中两个或多个线程被永久阻塞,Java 死锁情况出现至少
两个线程和两个或更多资源。
Java 发生死锁的根本原因是:在申请锁时发生了交叉闭环申请
31、什么是“依赖注入”和“控制反转”?为什么有人使用?
控制反转(IOC)是 Spring 框架的核心思想,用我自己的话说,就是你要做一件事,别自
己可劲 new 了,你就说你要干啥,然后外包出去就好~
依赖注入(DI) 在我浅薄的想法中,就是通过接口的引用和构造方法的表达,将一些事情
整好了反过来传给需要用到的地方~
32、ArrayList 和 LinkedList 的区别是什么?
1、 数据结构实现:ArrayList 是动态数组的数据结构实现,而 LinkedList 是双向链表的
数据结构实现。
2、 随机访问效率:ArrayList 比 LinkedList 在随机访问的时候效率要高,因为
LinkedList 是线性的数据存储方式,所以需要移动指针从前往后依次查找。
3、 增加和删除效率:在非首尾的增加和删除操作,LinkedList 要比 ArrayList 效率要
高,因为 ArrayList 增删操作要影响数组内的其他数据的下标。
4、 内存空间占用:LinkedList 比 ArrayList 更占内存,因为 LinkedList 的节点除了存储
数据,还存储了两个引用,一个指向前一个元素,一个指向后一个元素。
5、 线程安全:ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
6、 综合来说,在需要频繁读取集合中的元素时,更推荐使用 ArrayList,而在插入和删除
操作较多时,更推荐使用 LinkedList。
7、 LinkedList 的双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指
针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很
方便地访问它的前驱结点和后继结点。
33、观察者模式应用场景
1、 关联行为场景,需要注意的是,关联行为是可拆分的,而不是“组合”关系。事件多级
触发场景。
2、 跨系统的消息交换场景,如消息队列、事件总线的处理机制。
代码演示
1、 定义抽象观察者,每一个实现该接口的实现类都是具体观察者。
package com.lijie;
//观察者的接口,用来存放观察者共有方法
public interface Observer {
// 观察者方法
void update(int state);
}
2、 定义具体观察者
package com.lijie;
// 具体观察者
public class ObserverImpl implements Observer {
// 具体观察者的属性
private int myState;
public void update(int state) {
myState=state;
System.out.println("收到消息,myState 值改为:"+state);
}
public int getMyState() {
return myState;
}
}
3、 定义主题。主题定义观察者数组,并实现增、删及通知操作。
package com.lijie;
import java.util.Vector;
//定义主题,以及定义观察者数组,并实现增、删及通知操作。
public class Subjecct {
//观察者的存储集合,不推荐 ArrayList,线程不安全,
private Vector<Observer> list = new Vector<>();
// 注册观察者方法
public void registerObserver(Observer obs) {
list.add(obs);
}
// 删除观察者方法
public void removeObserver(Observer obs) {
list.remove(obs);
}
// 通知所有的观察者更新
public void notifyAllObserver(int state) {
for (Observer observer : list) {
observer.update(state);
}
}
}
4、 定义具体的,他继承继承 Subject 类,在这里实现具体业务,在具体项目中,该类会
有很多。
package com.lijie;
//具体主题
public class RealObserver extends Subjecct {
//被观察对象的属性
private int state;
public int getState(){
return state;
}
public void setState(int state){
this.state=state;
//主题对象(目标对象)值发生改变
this.notifyAllObserver(state);
}
}
5、 运行测试
package com.lijie;
public class Client {
public static void main(String[] args) {
// 目标对象
RealObserver subject = new RealObserver();
// 创建多个观察者
ObserverImpl obs1 = new ObserverImpl();
ObserverImpl obs2 = new ObserverImpl();
ObserverImpl obs3 = new ObserverImpl();
// 注册到观察队列中
subject.registerObserver(obs1);
subject.registerObserver(obs2);
subject.registerObserver(obs3);
// 改变 State 状态
subject.setState(300);
System.out.println("obs1 观察者的 MyState 状态值为:
"+obs1.getMyState());
System.out.println("obs2 观察者的 MyState 状态值为:
"+obs2.getMyState());
System.out.println("obs3 观察者的 MyState 状态值为:
"+obs3.getMyState());
// 改变 State 状态
subject.setState(400);
System.out.println("obs1 观察者的 MyState 状态值为:
"+obs1.getMyState());
System.out.println("obs2 观察者的 MyState 状态值为:
"+obs2.getMyState());
System.out.println("obs3 观察者的 MyState 状态值为:
"+obs3.getMyState());
}
}
34、Array 与 ArrayList 有什么不一样?
Array 与 ArrayList 都是用来存储数据的集合。ArrayList 底层是使用数组实现的,但是
arrayList 对数组进行了封装和功能扩展,拥有许多原生数组没有的一些功能。我们可以理
解成 ArrayList 是 Array 的一个升级版。
35、实例化数组后,能不能改变数组长度呢?
不能,数组一旦实例化,它的长度就是固定的
36、Java 中,Maven 和 ANT 有什么区别?
虽然两者都是构建工具,都用于创建 Java 应用,但是 Maven 做的事情更多,在基于“约
定优于配置”的概念下,提供标准的 Java 项目结构,同时能为应用自动管理依赖(应用中
所依赖的 JAR 文件),Maven 与 ANT 工具更多的不同之处请参见。
1、 这就是所有的面试题,如此之多,是不是?我可以保证,如果你能回答列表中的所有
问题,你就可以很轻松的应付任何核心 Java 或者高级 Java 面试。虽然,这里没有
涵盖 Servlet、JSP、JSF、JPA,JMS,EJB 及其它 Java EE 技术,也没有包含主流的
框架如 Spring MVC,Struts 2.0,Hibernate,也没有包含 SOAP 和 RESTful web
service,但是这份列表对做 Java 开发的、准备应聘 Java web 开发职位的人还是同
样有用的,因为所有的 Java 面试,开始的问题都是 Java 基础和 JDK API 相关的。
如果你认为我这里有任何应该在这份列表中而被我遗漏了的 Java 流行的问题,你可
以自由的给我建议。我的目的是从最近的面试中创建一份最新的、最优的 Java 面试
问题列表。
37、方法区的作用是什么?
方法区用于存储被虚拟机加载的类型信息、常量、静态变量、即时编译器编译后的代码缓
存等数据。
JDK8 之前使用永久代实现方法区,容易内存溢出,因为永久代有 -XX:MaxPermSize 上
限,即使不设置也有默认大小。JDK7 把放在永久代的字符串常量池、静态变量等移出,
JDK8 中永久代完全废弃,改用在本地内存中实现的元空间代替,把 JDK 7 中永久代剩余
内容(主要是类型信息)全部移到元空间。
虚拟机规范对方法区的约束宽松,除和堆一样不需要连续内存和可选择固定大小/可扩展
外,还可以不实现垃圾回收。垃圾回收在方法区出现较少,主要目标针对常量池和类型卸
载。如果方法区无法满足新的内存分配需求,将抛出 OutOfMemoryError。
38、接口和抽象类有什么区别?
实现:抽象类的子类使用 extends 来继承;接口必须使用 implements 来实现接口。 构
造函数:抽象类可以有构造函数;接口不能有。 main 方法:抽象类可以有 main 方法,
并且我们能运行它;接口不能有 main 方法。 实现数量:类可以实现很多个接口;但是
只能继承一个抽象类。 访问修饰符:接口中的方法默认使用 public 修饰;抽象类中的方
法可以是任意访问修饰符。
39、原型模式的应用场景
1、 类初始化需要消化非常多的资源,这个资源包括数据、硬件资源等。这时我们就可以
通过原型拷贝避免这些消耗。
2、 通过 new 产生的一个对象需要非常繁琐的数据准备或者权限,这时可以使用原型模
式。
3、 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考
虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。
我们 Spring 框架中的多例就是使用原型
40、ConcurrentHashMap 和 Hashtable 的区别?
ConcurrentHashMap 和 Hashtable 的区别主要体现在实现线程安全的方式上不同。
底层数据结构:
JDK1.7 的 ConcurrentHashMap 底层采用 分段的数组+链表 实现,JDK1.8 采用的数据结
构跟 HashMap1.8 的结构一样,数组+链表/红黑二叉树。Hashtable 和 JDK1.8 之前的
HashMap 的底层数据结构类似都是采用 数组+链表的形式,数组是 HashMap 的主体,
链表则是主要为了解决哈希冲突而存在的;
实现线程安全的方式:
1、 在 JDK1.7 的时候,ConcurrentHashMap(分段锁对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配 16 个 Segment,比 Hashtable 效率提高16 倍。) 到了 JDK1.8 的时候已经摒弃了 Segment 的概念,而是直接用 Node 数组+链表+红黑树的数据结构来实现,并发控制使用 synchronized 和 CAS 来操作。(JDK1.6 以后 对 synchronized 锁做了很多优化) 整个看起来就像是优化过且线程安全的HashMap,虽然在 JDK1.8 中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;
2、 Hashtable(同一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线
程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put
添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率
越低。