目录
23、try catch fifinally,try里有return,fifinally还执行么?
26、 简述线程、程序、进程的基本概念。以及他们之间关系是什 么?
27、Java 序列化中如果有些字段不想进行序列化,怎么办?
32.、Object 有哪些常用方法?大致说一下每个方法的含义
35、ArrayList 和 LinkedList 的区别有哪些?
40、HashMap 中的 key 我们可以使用任何类作为 key 吗?
42、HashMap 与 ConcurrentHashMap 的异同
22、a=a+b与a+=b有什么区别吗?
+=
操作符会进行隐式自动类型转换
,
此处
a+=b
隐式的将加操作的结果类型强制转换为持有结果的类
型
,
而
a=a+b
则不会自动进行类型转换
.
如:
byte a = 127 ;byte b = 127 ;b = a + b ; // 报编译错误 :cannot convert from int to byteb += a ;
以下代码是否有错,有的话怎么改?
short s1 = 1 ;s1 = s1 + 1 ;
有错误
.short
类型在进行运算时会自动提升为
int
类型
,
也就是说
s1+1
的运算结果是
int
类型
,
而
s1
是
short
类型
,
此时编译器会报错
.
正确写法:
short s1 = 1 ;s1 += 1 ;
23、try catch fifinally,try里有return,fifinally还执行么?
执行,并且
fifinally
的执行早于
try
里面的
return
import static
java
.
lang
.
Math
.
*
;
public class
Test
{
public static
void
main
(
String
[]
args
){
//System.out.println(Math.sin(20));
传统做法
System
.
out
.
println
(
sin
(
20
));
}
}
byte
a
=
127
;
byte
b
=
127
;
b
=
a
+
b
;
//
报编译错误
:cannot convert from int to byte
b
+=
a
;
short
s1
=
1
;
s1
=
s1
+
1
;
short
s1
=
1
;
s1
+=
1
;
阿里内部资料
结论:
1
、不管有木有出现异常,
fifinally
块中代码都会执行;
2
、当
try
和
catch
中有
return
时,
fifinally
仍然会执行;
3
、
fifinally
是在
return
后面的表达式运算后执行的(此时并没有返回运算后的值,而是先把要返回的
值保存起来,管
fifinally
中的代码怎么样,返回的值都不会改变,任然是之前保存的值),所以函数
返回值是在
fifinally
执行前确定的;
4
、
fifinally
中最好不要包含
return
,否则程序会提前退出,返回值不是
try
或
catch
中保存的返回值。
24、 Excption与Error包结构
Java
可抛出
(Throwable)
的结构分为三种类型:被检查的异常
(CheckedException)
,运行时异常
(RuntimeException)
,错误
(Error)
。
1
、运行时异常
定义
:RuntimeException
及其子类都被称为运行时异常。
特点
:Java
编译器不会检查它。也就是说,当程序中可能出现这类异常时,倘若既
"
没有通过
throws
声明抛出它
"
,也
"
没有用
try-catch
语句捕获它
"
,还是会编译通过。例如,除数为零时产生的
ArithmeticException
异常,数组越界时产生的
IndexOutOfBoundsException
异常,
fail-fast
机制产
生的
ConcurrentModifificationException
异常(
java.util
包下面的所有的集合类都是快速失败
的,
“
快速失败
”
也就是
fail-fast
,它是
Java
集合的一种错误检测机制。当多个线程对集合进行结构上
的改变的操作时,有可能会产生
fail-fast
机制。记住是有可能,而不是一定。例如:假设存在两个线
程(线程
1
、线程
2
),线程
1
通过
Iterator
在遍历集合
A
中的元素,在某个时候线程
2
修改了集合
A
的
结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出
ConcurrentModifificationException
异常,从而产生
fail-fast
机制,这个错叫并发修改异常。
Fail
safe
,
java.util.concurrent
包下面的所有的类都是安全失败的,在遍历过程中,如果已经遍历的数
组上的内容变化了,迭代器不会抛出
ConcurrentModifificationException
异常。如果未遍历的数组
上的内容发生了变化,则有可能反映到迭代过程中。这就是
ConcurrentHashMap
迭代器弱一致的
表现。
ConcurrentHashMap
的弱一致性主要是为了提升效率,是一致性与效率之间的一种权衡。
要成为强一致性,就得到处使用锁,甚至是全局锁,这就与
Hashtable
和同步的
HashMap
一样
了。)等,都属于运行时异常。
常见的五种运行时异常:
ClassCastException
(类转换异常)
IndexOutOfBoundsException
(数组越界)
NullPointerException
(空指针异常)
ArrayStoreException
(数据存储异常,操作数组是类型不一致)
阿里内部资料
BufffferOverflflowException
2
、被检查异常
定义
:Exception
类本身,以及
Exception
的子类中除了
"
运行时异常
"
之外的其它子类都属于被检查异
常。
特点
: Java
编译器会检查它。 此类异常,要么通过
throws
进行声明抛出,要么通过
try-catch
进行捕
获处理,否则不能通过编译。例如,
CloneNotSupportedException
就属于被检查异常。当通过
clone()
接口去克隆一个对象,而该对象对应的类没有实现
Cloneable
接口,就会抛出
CloneNotSupportedException
异常。被检查异常通常都是可以恢复的。 如:
IOException
FileNotFoundException
SQLException
被检查的异常适用于那些不是因程序引起的错误情况,比如:读取文件时文件不存在引发的
FileNotFoundException
。然而,不被检查的异常通常都是由于糟糕的编程引起的,比如:在对象
引用时没有确保对象非空而引起的
NullPointerException
。
3
、错误
定义
: Error
类及其子类。
特点
:
和运行时异常一样,编译器也不会对错误进行检查。
当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。程序本身无法修
复这些错误的。例如,
VirtualMachineError
就属于错误。出现这种错误会导致程序终止运行。
OutOfMemoryError
、
ThreadDeath
。
Java
虚拟机规范规定
JVM
的内存分为了好几块,比如堆,栈,程序计数器,方法区等
25、OOM你遇到过哪些情况,SOF你遇到过哪些情况
OOM
:
1
,
OutOfMemoryError
异常
除了程序计数器外,虚拟机内存的其他几个运行时区域都有发生
OutOfMemoryError(OOM)
异常的
可能。
Java Heap
溢出:
一般的异常信息:
java.lang.OutOfMemoryError:Java heap spacess
。
java
堆用于存储对象实例,我们只要不断的创建对象,并且保证
GC Roots
到对象之间有可达路径来
避免垃圾回收机制清除这些对象,就会在对象数量达到最大堆容量限制后产生内存溢出异常。
阿里内部资料
出现这种异常,一般手段是先通过内存映像分析工具
(
如
Eclipse Memory Analyzer)
对
dump
出来的
堆转存快照进行分析,重点是确认内存中的对象是否是必要的,先分清是因为内存泄漏
(Memory
Leak)
还是内存溢出
(Memory Overflflow)
。
如果是内存泄漏,可进一步通过工具查看泄漏对象到
GCRoots
的引用链。于是就能找到泄漏对象是
通过怎样的路径与
GC Roots
相关联并导致垃圾收集器无法自动回收。
如果不存在泄漏,那就应该检查虚拟机的参数
(-Xmx
与
-Xms)
的设置是否适当。
2
,虚拟机栈和本地方法栈溢出
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出
StackOverflflowError
异常。
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出
OutOfMemoryError
异常
这里需要注意当栈的大小越大可分配的线程数就越少。
3
,运行时常量池溢出
异常信息:
java.lang.OutOfMemoryError:PermGenspace
如果要向运行时常量池中添加内容,最简单的做法就是使用
String.intern()
这个
Native
方法。该方法
的作用是:如果池中已经包含一个等于此
String
的字符串,则返回代表池中这个字符串的
String
对
象;否则,将此
String
对象包含的字符串添加到常量池中,并且返回此
String
对象的引用。由于常量
池分配在方法区内,我们可以通过
-XX:PermSize
和
-XX:MaxPermSize
限制方法区的大小,从而间接
限制其中常量池的容量。
4
,方法区溢出
方法区用于存放
Class
的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等。也有可
能是方法区中保存的
class
对象没有被及时回收掉或者
class
信息占用的内存超过了我们配置。
异常信息:
java.lang.OutOfMemoryError:PermGenspace
方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻
的。在经常动态生成大量
Class
的应用中,要特别注意这点。
SOF
(堆栈溢出
StackOverflflow
):
StackOverflflowError
的定义:当应用程序递归太深而发生堆栈溢出时,抛出该错误。
因为栈一般默认为
1-2m
,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容
量超过
1m
而导致溢出。
栈溢出的原因:递归调用,大量循环或死循环,全局变量是否过多,数组、
List
、
map
数据过大。
26、 简述线程、程序、进程的基本概念。以及他们之间关系是什 么?
阿里内部资料
线程
与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个
线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个
线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻
量级进程。
程序
是含有指令和数据的文件,被存储在磁盘或其他的数据存储设备中,也就是说程序是静态的代
码。
进程
是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序
即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算
机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如
CPU
时间,内存空
间,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存
中。 线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而 各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系
统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同
时执行一个以上的程序段。
27、Java 序列化中如果有些字段不想进行序列化,怎么办?
对于不想进行序列化的变量,使用
transient
关键字修饰。
transient
关键字的作用是:阻止实例中那些用此关键字修饰的的变量序列化;当对象被反序列化
时,被
transient
修饰的变量值不会被持久化和恢复。
transient
只能修饰变量,不能修饰类和方
法。
28、说说Java 中 IO 流
Java 中 IO 流分为几种?
按照流的流向分,可以分为输入流和输出流;
按照操作单元划分,可以划分为字节流和字符流;
按照流的角色划分为节点流和处理流。
Java Io
流共涉及
40
多个类,这些类看上去很杂乱,但实际上很有规则,而且彼此之间存在非常紧
密的联系,
Java I0
流的
40
多个类都是从如下
4
个抽象类基类中派生出来的。
InputStream/Reader:
所有的输入流的基类,前者是字节输入流,后者是字符输入流。
OutputStream/Writer:
所有输出流的基类,前者是字节输出流,后者是字符输出流。
按操作方式分类结构图:
按操作对象分类结构图:
29、 Java IO与 NIO的区别(补充)
NIO
即
New IO
,这个库是在
JDK1.4
中才引入的。
NIO
和
IO
有相同的作用和目的,但实现方式不同,
NIO
主要用到的是块,所以
NIO
的效率要比
IO
高很多。在
Java API
中提供了两套
NIO
,一套是针对标
准输入输出
NIO
,另一套就是网络编程
NIO
。
30、java反射的作用于原理
1
、定义:
反射机制是在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意个对象,
都能够调用它的任意一个方法。在
java
中,只要给定类的名字,就可以通过反射机制来获得类的所
有信息。
这种动态获取的信息以及动态调用对象的方法的功能称为
Java
语言的反射机制。
2
、哪里会用到反射机制?
jdbc
就是典型的反射
Class . forName ( 'com.mysql.jdbc.Driver.class' ); // 加载 MySQL 的驱动类这就是反射。如 hibernate , struts 等框架使用反射实现的。
3
、反射的实现方式:
第一步:获取
Class
对象,有
4
中方法:
1
)
Class.forName(“
类的路径
”)
;
2
)类名
.class 3
)对象
名
.getClass() 4
)基本类型的包装类,可以调用包装类的
Type
属性来获得该包装类的
Class
对象
4
、实现
Java
反射的类:
1
)
Class
:表示正在运行的
Java
应用程序中的类和接口 注意:
所有获取对象的信息都需要
Class
类
来实现。
2
)
Field
:提供有关类和接口的属性信息,以及对它的动态访问权限。
3
)
Constructor
:
提供关于类的单个构造方法的信息以及它的访问权限
4
)
Method
:提供类或接口中某个方法的信息
5
、反射机制的优缺点:
优点:
1
)能够运行时动态获取类的实例,提高灵活性;
2
)与动态编译结合
缺点:
1
)使用反射
性能较低,需要解析字节码,将内存中的对象进行解析。 解决方案:
1
、通过
setAccessible(true)
关闭
JDK
的安全检查来提升反射速度;
2
、多次创建一个类的实例时,有缓存会快很多
3
、
ReflflectASM
工具类,通过字节码生成的方式加快反射速度
2
)相对不安全,破坏了封装性(因为通
过反射可以获得私有方法和属性)
31、说说List,Set,Map三者的区别?
List(
对付顺序的好帮手
)
:
List
接口存储一组不唯一(可以有多个元素引用相同的对象),有序
的对象
Set(
注重独一无二的性质
):
不允许重复的集合。不会有多个元素引用相同的对象。
Map(
用
Key
来搜索的专家
):
使用键值对存储。
Map
会维护与
Key
有关联的值。两个
Key
可以引
用相同的对象,但
Key
不能重复,典型的
Key
是
String
类型,但也可以是任何对象。
32.、Object 有哪些常用方法?大致说一下每个方法的含义
java.lang.Object
下面是对应方法的含义。
clone
方法
保护方法,实现对象的浅复制,只有实现了
Cloneable
接口才可以调用该方法,否则抛出
CloneNotSupportedException
异常,深拷贝也需要实现
Cloneable
,同时其成员变量为引用类型
的也需要实现
Cloneable
,然后重写
clone
方法。
fifinalize
方法
该方法和垃圾收集器有关系,判断一个对象是否可以被回收的最后一步就是判断是否重写了此方
法。
equals
方法
该方法使用频率非常高。一般
equals
和
==
是不一样的,但是在
Object
中两者是一样的。子类一
般都要重写这个方法。
hashCode
方法
该方法用于哈希查找,重写了
equals
方法一般都要重写
hashCode
方法,这个方法在一些具有哈
希功能的
Collection
中用到。
一般必须满足
obj1.equals(obj2)==true
。可以推出
obj1.hashCode()==obj2.hashCode()
,但是
hashCode
相等不一定就满足
equals
。不过为了提高效率,应该尽量使上面两个条件接近等价。
JDK 1.6
、
1.7
默认是返回随机数;
JDK 1.8
默认是通过和当前线程有关的一个随机数
+
三个确定值,运用
Marsaglia’s xorshift
scheme
随机数算法得到的一个随机数。
wait
方法
阿里内部资料
配合
synchronized
使用,
wait
方法就是使当前线程等待该对象的锁,当前线程必须是该对象的拥
有者,也就是具有该对象的锁。
wait()
方法一直等待,直到获得锁或者被中断。
wait(long timeout)
设定一个超时间隔,如果在规定时间内没有获得锁就返回。
调用该方法后当前线程进入睡眠状态,直到以下事件发生。
1.
其他线程调用了该对象的
notify
方法;
2.
其他线程调用了该对象的
notifyAll
方法;
3.
其他线程调用了
interrupt
中断该线程;
4.
时间间隔到了。
此时该线程就可以被调度了,如果是被中断的话就抛出一个
InterruptedException
异常。
notify
方法
配合
synchronized
使用,该方法唤醒在该对象上
等待队列
中的某个线程(同步队列中的线程是给
抢占
CPU
的线程,等待队列中的线程指的是等待唤醒的线程)。
notifyAll
方法
配合
synchronized
使用,该方法唤醒在该对象上等待队列中的所有线程。
总结
只要把上面几个方法熟悉就可以了,
toString
和
getClass
方法可以不用去讨论它们。该题目考察的
是对
Object
的熟悉程度,平时用的很多方法并没看其定义但是也在用,比如说:
wait()
方法,
equals()
方法等
Class Object is the root of the class hierarchy.Every class has Object as asuperclass. All objects, including arrays, implement the methods of this class.
大致意思:Object 是所有类的根,是所有类的父类,所有对象包括数组都实现了 Object 的方法。
33、Java 创建对象有几种方式?
这题目看似简单,要好好回答起来还是有点小复杂的,我们来看看,到底有哪些方式可以创建对
象?
使用
new
关键字
,这也是我们平时使用的最多的创建对象的方式,示例:
User user=new User();
使用反射方式创建对象
,使用
newInstance()
,但是得处理两个异常
InstantiationException
、
IllegalAccessException
:
User user=User.class.newInstance();Object object=(Object)Class.forName("java.lang.Object").newInstance()
使用
clone
方法
,前面题目中
clone
是
Object
的方法,所以所有对象都有这个方法。
使用反序列化创建对象
,调用
ObjectInputStream
类的
readObject()
方法。
我们反序列化一个对象,
JVM
会给我们创建一个单独的对象。
JVM
创建对象并不会调用任何构造函
数。一个对象实现了
Serializable
接口,就可以把对象写入到文件中,并通过读取文件来创建对
象。
总结
创建对象的方式关键字:
new
、反射、
clone
拷贝、反序列化。
34、获取一个类Class对象的方式有哪些?
搞清楚类对象和实例对象,但都是对象。
第一种:通过类对象的
getClass()
方法获取,细心点的都知道,这个
getClass
是
Object
类里面的
方法。
Useruser = new User ();//clazz 就是一个 User 的类对象Class clazz = user . getClass ();
第二种:通过类的静态成员表示,每个类都有隐含的静态成员 class。
//clazz 就是一个 User 的类对象Class clazz = User . class ;
第三种:通过 Class 类的静态方法 forName() 方法获取。
Class clazz = Class . forName ( "com.tian.User" );
35、ArrayList 和 LinkedList 的区别有哪些?
ArrayList
优点
:
ArrayList
是实现了基于动态数组的数据结构,因为地址连续,一旦数据存储好了,查询
操作效率会比较高(在内存里是连着放的)。
缺点
:因为地址连续,
ArrayList
要移动数据,所以插入和删除操作效率比较低。
LinkedList
User user
=
new
User
();
//clazz
就是一个
User
的类对象
Class
<?>
clazz
=
user
.
getClass
();
//clazz
就是一个
User
的类对象
Class
<?>
clazz
=
User
.
class
;
Class
<?>
clazz
=
Class
.
forName
(
"com.tian.User"
);
阿里内部资料
优点
:
LinkedList
基于链表的数据结构,地址是任意的,所以在开辟内存空间的时候不需要等
一个连续的地址。对于新增和删除操作,
LinkedList
比较占优势。
LinkedList
适用于要头尾操
作或插入指定位置的场景。
缺点
:因为
LinkedList
要移动指针,所以查询操作性能比较低。
适用场景分析
当需要对数据进行对随机访问的时候,选用
ArrayList
。
当需要对数据进行多次增加删除修改时,采用
LinkedList
。
如果容量固定,并且只会添加到尾部,不会引起扩容,优先采用
ArrayList
。
当然,绝大数业务的场景下,使用
ArrayList
就够了,但需要注意避免
ArrayList
的扩容,以及非顺
序的插入。
36、用过 ArrayList 吗?说一下它有什么特点?
只要是搞
Java
的肯定都会回答
“
用过
”
。所以,回答题目的后半部分
——ArrayList
的特点。可以从这
几个方面去回答:
Java
集合框架中的一种存放相同类型的元素数据,是一种变长的集合类,基于定长数组实现,当加
入数据达到一定程度后,会实行自动扩容,即扩大数组大小。
底层是使用数组实现,添加元素。
如果
add(o)
,添加到的是数组的尾部,如果要增加的数据量很大,应该使用
ensureCapacity()
方法,该方法的作用是预先设置
ArrayList
的大小,这样可以大大提高初始化速度。
如果使用
add(int,o)
,添加到某个位置,那么可能会挪动大量的数组元素,并且可能会触发扩
容机制。
高并发的情况下,线程不安全。多个线程同时操作
ArrayList
,会引发不可预知的异常或错误。
ArrayList
实现了
Cloneable
接口,标识着它可以被复制。注意:
ArrayList
里面的
clone()
复制其实
是浅复制。
37、有数组了为什么还要搞个 ArrayList 呢?
通常我们在使用的时候,如果在不明确要插入多少数据的情况下,普通数组就很尴尬了,因为你不
知道需要初始化数组大小为多少,而
ArrayList
可以使用默认的大小,当元素个数到达一定程度
后,会自动扩容。
可以这么来理解:我们常说的数组是定死的数组,
ArrayList
却是动态数组。
38、说说什么是 fail-fast?
阿里内部资料
fail-fast
机制是
Java
集合(
Collection
)中的一种错误机制。当多个线程对同一个集合的内容进行
操作时,就可能会产生
fail-fast
事件。
例如:当某一个线程
A
通过
iterator
去遍历某集合的过程中,若该集合的内容被其他线程所改变
了,那么线程
A
访问集合时,就会抛出
ConcurrentModifificationException
异常,产生
fail-fast
事
件。这里的操作主要是指
add
、
remove
和
clear
,对集合元素个数进行修改。
解决办法:建议使用
“java.util.concurrent
包下的类
”
去取代
“java.util
包下的类
”
。
可以这么理解:在遍历之前,把
modCount
记下来
expectModCount
,后面
expectModCount
去
和
modCount
进行比较,如果不相等了,证明已并发了,被修改了,于是抛出
ConcurrentModifificationException
异常。
39、说说Hashtable 与 HashMap 的区别
本来不想这么写标题的,但是无奈,面试官都喜欢这么问
HashMap
。
1.
出生的版本不一样,
Hashtable
出生于
Java
发布的第一版本
JDK 1.0
,
HashMap
出生于
JDK
1.2
。
2.
都实现了
Map
、
Cloneable
、
Serializable
(当前
JDK
版本
1.8
)。
3. HashMap
继承的是
AbstractMap
,并且
AbstractMap
也实现了
Map
接口。
Hashtable
继承
Dictionary
。
4. Hashtable
中大部分
public
修饰普通方法都是
synchronized
字段修饰的,是线程安全的,
HashMap
是非线程安全的。
5. Hashtable
的
key
不能为
null
,
value
也不能为
null
,这个可以从
Hashtable
源码中的
put
方
法看到,判断如果
value
为
null
就直接抛出空指针异常,在
put
方法中计算
key
的
hash
值之
前并没有判断
key
为
null
的情况,那说明,这时候如果
key
为空,照样会抛出空指针异常。
6. HashMap
的
key
和
value
都可以为
null
。在计算
hash
值的时候,有判断,如果
key==null
,则其
hash=0
;至于
value
是否为
null
,根本没有判断过。
7. Hashtable
直接使用对象的
hash
值。
hash
值是
JDK
根据对象的地址或者字符串或者数字算出
来的
int
类型的数值。然后再使用除留余数法来获得最终的位置。然而除法运算是非常耗费时
间的,效率很低。
HashMap
为了提高计算效率,将哈希表的大小固定为了
2
的幂,这样在取
模预算时,不需要做除法,只需要做位运算。位运算比除法的效率要高很多。
8. Hashtable
、
HashMap
都使用了
Iterator
。而由于历史原因,
Hashtable
还使用了
Enumeration
的方式。
9.
默认情况下,初始容量不同,
Hashtable
的初始长度是
11
,之后每次扩充容量变为之前的
2n+1
(
n
为上一次的长度)而
HashMap
的初始长度为
16
,之后每次扩充变为原来的两倍。
另外在
Hashtable
源码注释中有这么一句话:
Hashtable is synchronized. If a thread-safe implementation is not needed, it isrecommended to use HashMap in place of Hashtable . If a thread-safe highlyconcurrent implementation is desired, then it is recommended to useConcurrentHashMap in place of Hashtable.
大致意思:
Hashtable
是线程安全,推荐使用
HashMap
代替
Hashtable
;如果需要线程安全高并
发的话,推荐使用
ConcurrentHashMap
代替
Hashtable
。
这个回答完了,面试官可能会继续问:
HashMap
是线程不安全的,那么在需要线程安全的情况下
还要考虑性能,有什么解决方式?
这里最好的选择就是
ConcurrentHashMap
了,但面试官肯定会叫你继续说一下
ConcurrentHashMap
数据结构以及底层原理等。
40、HashMap 中的 key 我们可以使用任何类作为 key 吗?
平时可能大家使用的最多的就是使用
String
作为
HashMap
的
key
,但是现在我们想使用某个自定
义类作为
HashMap
的
key
,那就需要注意以下几点:
如果类重写了
equals
方法,它也应该重写
hashCode
方法。
类的所有实例需要遵循与
equals
和
hashCode
相关的规则。
如果一个类没有使用
equals
,你不应该在
hashCode
中使用它。
咱们自定义
key
类的最佳实践是使之为不可变的,这样,
hashCode
值可以被缓存起来,拥有
更好的性能。不可变的类也可以确保
hashCode
和
equals
在未来不会改变,这样就会解决与
可变相关的问题了。
41、HashMap 的长度为什么是 2 的 N 次方呢?
为了能让
HashMap
存数据和取数据的效率高,尽可能地减少
hash
值的碰撞,也就是说尽量把数
据能均匀的分配,每个链表或者红黑树长度尽量相等。
我们首先可能会想到
%
取模的操作来实现。
下面是回答的重点哟
取余( % )操作中如果除数是 2 的幂次,则等价于与其除数减一的与( & )操作(也就是说hash % length == hash &(length - 1) 的前提是 length 是 2 的 n 次方)。并且,采用二进制位操作 & ,相对于 % 能够提高运算效率。这就是为什么 HashMap 的长度需要 2 的 N 次方了。
42、HashMap 与 ConcurrentHashMap 的异同
1.
都是
key-value
形式的存储数据;
2. HashMap
是线程不安全的,
ConcurrentHashMap
是
JUC
下的线程安全的;
阿里内部资料
3. HashMap
底层数据结构是数组
+
链表(
JDK 1.8
之前)。
JDK 1.8
之后是数组
+
链表
+
红黑
树。当链表中元素个数达到
8
的时候,链表的查询速度不如红黑树快,链表会转为红黑树,红
黑树查询速度快;
4. HashMap
初始数组大小为
16
(默认),当出现扩容的时候,以
0.75 *
数组大小的方式进行扩
容;
5. ConcurrentHashMap
在
JDK 1.8
之前是采用分段锁来现实的
Segment + HashEntry
,
Segment
数组大小默认是
16
,
2
的
n
次方;
JDK 1.8
之后,采用
Node + CAS + Synchronized
来保证并发安全进行实现。
43、红黑树有哪几个特征?
紧接上个问题,面试官很有可能会问红黑树,下面把红黑树的几个特征列出来
44、说说你平时是怎么处理 Java 异常的
try-catch-fifinally
try
块负责监控可能出现异常的代码
catch
块负责捕获可能出现的异常,并进行处理
fifinally
块负责清理各种资源,不管是否出现异常都会执行
其中
try
块是必须的,
catch
和
fifinally
至少存在一个标准异常处理流程
抛出异常 → 捕获异常 → 捕获成功(当 catch 的异常类型与抛出的异常类型匹配时,捕获成功)→ 异常被处理,程序继续运行抛出异常 → 捕获异常 → 捕获失败(当 catch 的异常类型与抛出异常类型不匹配时,捕获失败) → 异常未被处理,程序中断运行
在开发过程中会使用到自定义异常,在通常情况下,程序很少会自己抛出异常,因为异常的类名通
常也包含了该异常的有用信息,所以在选择抛出异常的时候,应该选择合适的异常类,从而可以明
确地描述该异常情况,所以这时候往往都是自定义异常。
自定义异常通常是通过继承
java.lang.Exception
类,如果想自定义
Runtime
异常的话,可以继承
java.lang.RuntimeException
类,实现一个无参构造和一个带字符串参数的有参构造方法。
在业务代码里,可以针对性的使用自定义异常。比如说:该用户不具备某某权限、余额不足等。
45、说说深拷贝和浅拷贝?
浅拷贝(
shallowCopy
)只是增加了一个指针指向已存在的内存地址,
深拷贝(
deepCopy
)是增加了一个指针并且申请了一个新的内存,使这个增加的指针指向这个新
的内存,
使用深拷贝的情况下,释放内存的时候不会因为出现浅拷贝时释放同一个内存的错误。
最好是结合克隆已经原型模式联系在一起哈,记得复习的时候,把这几个联系起来的。