Java面试题总结记录(1)—— 基础篇

1、Java语言的特点

(1)简单易学,有丰富的类库。
(2)面向对象 (Java最重要的特性,让程序耦合度更低,内聚性更高)
(3)与平台无关性 (JVM是java跨平台使用的根本)
(4)可靠安全
(5)支持多线程


2、面向对象和面向过程的区别

  • 面向过程: 是分析解决问题的步骤,然后用函数将这些步骤一个一个地实现,然后在使用的时候一一调用即可,性能高,所以单片机、嵌入式开发等一般采用面向过程开发。
  • 面向对象: 是把构成问题的事务分解成各个对象,而建立对象的目的也不是为了完成一个个步骤,而是为了描述一个事物在解决整个问题的过程中所发生的行为。面向对象有封装、继承、多态的特性,所以易维护,易复用,易扩展。可以设计出低耦合的系统,但性能较面向过程差。

3、八种基础数据类型、大小、封装类

基本数据类型 大小(字节) 默认值 封装类
byte 1 0 Byte
short 2 0 Short
int 4 0 Integer
long 8 0 Long
float 4 0.0f Float
double 8 0.0 Double
char 2 \u0000(null) Character
boolean - false Boolean

注意:

(1)基本数据类型与引用数据类型的差异:

int基础数据类型Integer 是 int 的封装类,属于引用数据类型。int 的默认值为 0 , Integer的默认值为 null,所以Integer能够区分出 0 和 null,当Java检测到null 时,就知道这个引用没有指向某个对象,在引用前必须指定一个对象,否则报错。

(2)关于数组对象赋值给另一个数组对象后,发生引用传递现象的原因:

基本数据类型在声明时系统会自动分配空间,而引用数据类型声明时只是分配了引用空间,必须通过实例化开辟数据空间之后可以赋值。数组对象赋值,也是将自己的地址,复制给另一个数组对象到引用空间,所以才会产生引用传递现象。

(3)boolean数据类型在Java中的特点

Java虽然定义了boolean这种数据类型,但是只对它提供了很有限的支持,在JVM中没有任何供boolean专用的字节码指令,Java语言表达式所操作的boolean值,在编译后使用JVM的 int 值代替,而 boolean 数组会被编码成JVM的 byte 数组,每个 boolean 元素占 8 位(1字节),这样我们能得出boolean 类型单独使用是 4个字节(int),在数组中使用则为 1 个字节(byte)。使用int的原因是对于 32 位CPU来说,一次处理数据是 32 位,具有高效存取的特点。


4、标识符的命名规则

  • 标识符的含义: 是指程序中,自定义内容,例如类名,方法名,变量名等等都是标识符。
  • 命名规则(必须): ① 标识符可以包含英文、数组、下划线以及美元符号。② 不能以数字作为开头。 ③ 标识符不能是关键字。
  • 命名规范(非必须): 类名规范: 每个单词的首字符都大写(大驼峰模式);变量名规范: 首字符小写,其它单词首字符大写(小驼峰模式);方法名规范: 同变量名规范相同。

5、instanceof关键字的作用

  • instanceof是什么?
    interfaceof 严格来说是Java的一个双目运算符,用来测试一个对象是否为一个类的实例。
  • 如何使用interfaceof?
boolean reasult = obj instanceofClass;

其中 obj 是对象,Class表示一个类或接口,当 obj 为 Class 的一个对象,或者是直接或间接子类,或者是接口的实现类,结果 reasult 都会返回为 true,否则返回 false。

注意: 编译器会检查obj是否能转换成右边的class类型,如果不能转换则直接报错,如果不能确定类型,则通过编译,具体看运行而定。

int i = 0;
//编译不通过,说明int不能直接转换为Integer
i instanceof Integer; 
//编译不通过,因为int是基本数据类型,并不是引用数据类型
i instanceof Object; 
Integer integer = new Integer(1);
//编译通过,说明integer能直接转换为Integer
integer instanceof Integer; //true
//编译通过,因为integer是引用数据类型,引用数据类型就是一个对象,是Object类的子类。
integer instanceof Object; //true

返回值为false的情景: JaveSE规范中对 intstanceof 运算符的规定就是:如果obj 为null,则返回false。

null instanceof Object; //false

6、Java的自动封箱与拆箱

  • 装箱: 自动将基本数据类型转换为包装类型(int -> Integer);调用Integer.valueOf(int)实现。
  • 拆箱: 自动将包装类型转换为基本数据类型(Integer -> int);调用Integer.intValue()实现。

在Java SE5 之前,如果要生成一个数值为 10 的 Integer 对象,必须使用 new 关键字实例化 Integer 对象。

Integer i = new Integer(10);

从Java SE5 之后,提供了自动装箱的特性,如果要生成一个数值为 10 的 Integer 对象,直接使用赋值进行生成。

Integer i = 10;

相关笔试题:

(1)以下代码会输出什么?

public class Main{
	public static void main(String[] args){
		Integer i1 = 100;
		Integer i2 = 100;
		Integer i3 = 200;
		Integer i4 = 200;
		
		System.out.println(i1 == i2);
		System.out.println(i3 == i4);
	}
}

运行结果

true
false

产生的原因: i1 与 i2 引用的对象是同一个,i3 与 i4 引用的对象是不同的。

Integer.valueOf 源码实现

当参数 i 的值在 [-128 , 127 ]内,则会返回一个指向IntegerCache.cache中已经存在的对象引用;否则创建一个新的Integer对象。

public static Integer valueOf(int i) {
    
    
	if (i >= IntegerCache.low && i <= IntegerCache.high)
		return IntegerCache.cache[i + (-IntegerCache.low)];
	return new Integer(i);
}

(2)以下代码会输出什么?

public class Main{
	public static void main(String[] args){
		Double i1 = 100.0;
		Double i2 = 100.0;
		Double i3 = 200.0;
		Double i4 = 200.0;
		
		System.out.println(i1 == i2);
		System.out.println(i3 == i4);
	}
}

运行结果

false
false

Double.valueOf 源码实现

无论参数是多少,Double都会实例化一个Double对象。

    public static Double valueOf(double d) {
    
    
        return new Double(d);
    }

7、重载与重写的区别

(1)重写(Override)

从字面上看,重写就是“ 重新写一遍 ”的意思。其实就是子类将父类的方法重新写一遍。

子类继承了父类原有的方法,但有时子类想要修改父类的方法,所以在方法名,参数列表,返回类型(除子类方法的返回值是父类方法返回值的子类)都相同的情况下,对方法进行修改或重写。

在重写时,应当注意子类函数的访问权限不能低于父类。

public class Father {
    
    
    public static void main(String[] args) {
    
    
        Son son = new Son();
        son.sayHello();
    }

    public void sayHello(){
    
    
        System.out.println("Hello");
    }
}

class Son extends Father{
    
    
    @Override
    public void sayHello() {
    
    
        System.out.println("hello by son");
    }
}

总结:

  • 重载发生在父类与子类之间
  • 方法、参数列表、返回类型(父类方法与子类方法的返回值必须是相同或有继承关系)必须相同
  • 子类方法访问权限必须相同或大于父类
  • 重写方法一定不能抛出新的Exception或使用更宽泛的Exception(例如直接抛出Exception)

(2)重载(Overload)

在一个类中,同名方法有不同的参数列表(类型不同、个数不同、顺序不同)则被视为重载。

public class Father {
    
    
	 public static void main(String[] args) {
    
    
		 Father f = new Father();
		 f.sayHello();
		 f.sayHello("wintershii");
	 }
	 
	 //同名方法1
	 public void sayHello() {
    
    
	 	System.out.println("Hello");
	 }

	 //同名方法2
	 public void sayHello(String name) {
    
    
	 	System.out.println("Hello" + " " + name);
	 }
}

总结:

  • 重载Overload是一个类中多态表现
  • 重载要求同名方法参数列表不同
  • 重载方法可以改变返回值,不以返回值判断是否为重载

8、equals与==

(1)“==”

“==” 比较的是变量(栈)内存中的对象内存地址,用来判断是否是同一个对象。

比较的对象:

  • 比较的是操作符两端是否为同一个对象
  • 两边的对象必须是同一类型才能编译通过
  • 比较的是地址,若以具体的数字进行比较,则以内容进行比较(数字是放置在静态常量池)

(2)equals

equals比较的是两个对象的内容是否相等。

由于所有类继承Object类的,若子类不重写equals方法,则调用Object.equals(),Object内部使用的是“==”进行比较。

总结: 在所有比较是否相等时,都使用 equals 并且对常量相比较时,以 常量.equals(xxx) 规范进行比较(因为使用Object的equals时,object可能会空指针)。


9、Hashcode的作用

Java的集合有两类

  • List: 有序可重复
  • Set: 无序不重复

(1)Set集合是可以如何查重的?

通过equals方法,但不适用于高数据量的Set集合,所以使用了哈希算法提高集合中查找元素的效率。

(2)哈希算法

这种方式将集合分成若干个存储区域,每个对象可以计算出一个哈希码,并将哈希码分组,每组分别对应某个存储区域,根据一个对象的哈希码就可以确定该对象应该存储的那个区域。

(3)hashCode方法

hashCode是根据对象的内存地址换算出的一个值。

当集合要添加新的元素时,先调用这个元素的hashCode方法,获取哈希码,再查找Hash存储区域中是否存在元素,若这个位置上没有元素,就直接存储在这个位置上,不用再进行任何比较了;若存在元素则调用equals方法进行比较,不相同则散列其它地址。


10、String、String StringBuffer 和 StringBuilder 的区别

(1)String

String是一种 只读字符串,是一种引用数据类型。

从底层源码来看,是一个 final 类型的字符数组,引用的字符串不能被改变(因为final锁定了),一经定义则无法进行修改。

每次对String进行操作,都会生产新的String对象。

private final char[] value;

(2)String 的 “+” 操作符

每一次的 “+” 操作在堆上都会 new 一个跟原字符串相同的 StringBuilder 对象,再调用 append() 方法拼接新字符。

(3)StringBuffer和StringBuilder

两者继承了 AbstractStringBuilder 抽象类,该抽象类的底层是可变字符组。

操作建议

  • 在进行频繁的字符串操作时,建议使用 StringBufferStringBuilder 进行操作。
  • StringBuffer是线程安全的: 对方法加 同步锁 或者 对调用的方法加 同步锁
  • StringBuilder是线程非安全的: 没有对方法加同步锁

11、ArrayList和linkedList的区别


(1)Array:基于索引的数据结构

  • 优点: 搜索和读取数据快,Array 获取数据的时间复杂度是 O(1)
  • 缺点:
    • 数组初始化必须指定长度,否则编译不通过
    • 删除的开销极大(后续数据需要迁移)

(2)List:是一个有序的集合,可包含重复元素,提供索引访问,继承于Collection

两种实现类

  • ArrayList: 一种可以自动扩容的数组(底层是Array,通过数组扩容实现)
    • toArray(): 返回一个数组(Array)
    • asList(): 返回一个列表(List)
  • LinkedList: 一种双向链表,在大量数据的操作上,在添加和删除元素操作上比 ArrayList 更适合(链表的特点),但在Get/Set上弱于ArrayList(数组的特点)。

12、HashMap和HashTable的区别

1、两者父类不同

(1)不同点

  • HashMap: 继承AbstractMap类
  • HashTable: 继承Dictionary类

(2)相同点: 实现了MapCloneable(可复制)Serializable(可序列化) 这三个接口。

2、对外提供的接口不同

(1)HashTable 多提供了 elements()contains() 两个方法。

  • elements(): 继承自 Dictionnary,用于返回此HashTable 中的 value 的枚举。
  • contains(): 判断该 HashTable 是否含有指定value。(containsValue() 修饰了 contains()

3、对null的支持不同

  • Hashtable: key和value都不能为null。
  • HashMap:
    • key 可为 null ,但唯一(保持 key 的唯一性原则)
    • value 可为 null 并不受限制。

4、安全性不同

  • HashMap线程不安全 的,在多线程并发的环境下,可能会产生死锁等问题。
  • HashTable线程安全 的,每个方法上都有 synchronized(同步锁) 关键字,因此可直接用于多线程中。

虽然 HashMap 是线程不安全的,但是它的效率远远高于 HashTable,这样设计是合理的,因为大部分的使用场景都是单线程。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap ,效率高于 HashTable(因为 ConcurrentHashMap 使用了 分段锁,并不对整个数据进行锁定)。

5、初始容量大小和每次扩充容量大小不同

HashTable 初始长度为 11,每次扩增原来的 2n+1
HashMap 初始长度为 16,每次扩增 2n

6、计算hash值的方法不同

HashTable 直接使用对象的hash值。
HashMap 为提高计算效率,将哈希表的大小固定为 2 的幂次,这样在取模计算时,不需要做除法,只需要做位运算。(更加高效)


13、Collection包结构,与Collections的区别

(1)Collection 是集合类的上级接口,子接口有 Set、List、LinkedList、ArrayList、Vector(动态数组)、Stack(栈)。

(2)Collections 是集合类的一个工具类,它包含有各种有关集合操作的静态多态方法,用于实现对各种集合的搜索、排序、线程安全化等操作,服务于Java的Collection框架,此类不能实例化。


14、 Java的四种引用,强弱软虚

(1)强引用: 最常用的引用方式之一,在程序内存(OOM)不足时,不被回收。

String str = new String("str");
System.out.println(str);

(2)弱引用: JVM发现就回收。

WeakReference<String> wrf = new WeakReference<String>(str);

可用场景: Java源码中的 java.util.WeakHashMap 中的 key 就是使用弱引用,一旦不需要某个引用,JVM会自动处理,用户不需要做其它操作。

(3)软引用: 在程序内存(OOM)不足时,会被回收。

// 注意:wrf这个引用也是强引用,它是指向SoftReference这个对象的,
// 这里的软引用指的是指向new String("str")的引用,也就是SoftReference类中T
SoftReference<String> wrf = new SoftReference<String>(new String("str"));

可用场景: 创建缓存时,创建的对象放进缓存中,当内存不足时,JVM回收该对象。

(4)虚引用:

虚引用的回收机制跟弱引用相似,回收前放入 ReferenceQueue 中,而其它引用是被 JVM 回收后才被传入 ReferenceQueue 中的。

由于这个机制,所以虚引用大多被用于引用 销毁前 的处理工作,虚引用创建的时候,必须带有 ReferenceQueue

PhantomReference<String> prf = new PhantomReference<String>(new String("str"),
new ReferenceQueue<>());

可用场景: 对象销毁前的一些操作,比如说资源释放等。 Object.finalize() 虽然也可以做这
类动作,但是这个方式即不安全又低效。

注意: 这几类引用都是针对对象本身进行引用,而不是指Reference的四个子类的引用(SoftReference等)。


15、 泛型常用特点

(1)泛型的概念

泛型 是Java SE 1.5之后的特性。在《Java 核心技术》中对泛型的定义是 “泛型” 意味着编写的代码可以被不同类型的对象所重用。

(2)泛型的特点

我们提供了泛指的概念,但可以有具体的规则来约束。

ArrayList<Integer> 创建为例

List<Integer> list = new ArrayList<>();

(3)泛型的优点

  • 不需要固定未来参数的数值类型
  • 通过规则自由约束需要的类型
  • 泛型同属于Object的子类

16、Java对象创建的几种方式

(1)new创建新对象(通过构造器)
(2)通过反射机制(通过 Class.newInstance())
(3)采用clone机制(通过实现 Cloneable 接口,重写clone方法,通过已有的对象进行克隆一个新对象并且方法与属性相同)
(4)通过序列化机制(经过反序列化可以得到序列化后的对象)


17、有没有可能两个不相等的对象有相同的hashcode

有可能,在产生hash冲突时,两个不相等的对象就会有相同的 hashcode 值。

解决方案:

(1)拉链法: 每个 哈希节点 都有一个 next 指针,多个 哈希节点 都可以通过 next指针构建一条单向链表,hashcode 相同的对象,就会被挂在到这条链表上。(Set的查重机制)

(2)开放定址法: 一旦发生冲突,就散列到下一个空的地址上,只要散列表够大,总能找到并存储。(数据结构课上的hash表解决方法)

(3)再哈希: 又叫双哈希法,又多个hash函数,当发生冲突时,使用不同的计算hashcode,直至不冲突。


18、深拷贝和浅拷贝的区别是什么?

  • 浅拷贝: 指新对象与原对象的变量相同,但新对象与原对象的引用型对象是相同的,只是增加了一个指针指向已存在的内存地址。
  • 深拷贝: 指新对象与原对象的变量相同,但新对象与原对象的引用型对象是不同的,增加了一个指针并且申请了一个新的内存并指向新的内存。

19、final有哪些用法?

final的含义就是 最终 的意思,那么意味着它所修饰的所有东西都不能被修改。

常见回答:

(1)final修饰的类不能继承
(2)final修饰的方法不能被重写
(3)final修饰的变量不能修改,若修饰的是对象,则引用地址不变,引用内容可变
(4)final修饰的方法,JVM会尝试内联,以提高效率
(5)final修饰的常量,在编译阶段存入常量池

额外回答:

编辑器对final域要遵守的两个重排序规则(重新排列执行顺序)

  • 构造函数内final的写入对象引用赋值,这两个操作不能重排序
  • 初次读入一个包含了final的对象引用随后初次读入这个final 这两个操作不能重排序

20、static都有哪些用法?

  • 静态变量: 共享变量
  • 静态方法: 通用方法
  • 静态代码块: 多用于初始化操作
  • 静态内部类: 修饰静态类
  • 静态导包: 使用 import static ,是在JDK1.5引入的特性,用来指定导入某个类中的静态资源。
import static java.lang.Math.*

public class Main{
    
    
	public static void main(String[] args]{
    
    
		System.out.println(random());
	}
}

21、3*0.1 == 0.3返回值是什么

程序执行结果

false

结果分析

“==” 操作符是用来比较左右两边对象是否相同,很显然浮点数是会有精度不足的情况,3*0.1 = 0.30000000000000004,这是CPU执行的结果,由于数值是直接比较是否相等,所以是false。


22、a=a+b与a+=b有什么区别吗?

执行过程是一致的,但最终输出的结果可能会发生编译异常。

a = a + b: 执行过程是 a + b 的结果赋值给 a,当发生结果超出赋值类型范围则会发生变异错误java: 不兼容的类型: 从int转换到byte可能会有损失
a += b: 执行过程是 a+b 后赋值给 a,但会进行隐式自动类型转换(计算结果超出类型范围会转换到更大的类型上)。

衍生问题

//下列代码是否有错
short s = 1;
s = s + 1;

有错误。short 在计算时,会自动提升为 int 类型,则 s+1 的结果是 int 类型,而 s 是short类型,所以编译不通过,若使用 s += 1,则正确。


23、try catch finally,try里有return,finally还执行么?

执行,并且执行finally早于return。

结论:

  • finally代码块无论try中发生什么,都会得到执行(return的表达式计算后,会被存储,在finaly代码块执行之后,再进行返回计算结果)
  • finally代码块中最好不要放return,否则程序会提前退出

24、Excption与Error包结构

Java可抛出的结构分为三种类型:被检查异常运行时异常错误

(1)CheckedException

定义: Exception类本身以及其子类,除RuntimeException之外的所有异常都为被检查异常。

特点: Java编译器会检查它,并且能通过throws进行声明抛出,要么通过try-catch进行捕获处理,否则编译不过。

(2)RuntimeException

定义: RuntimeException及其子类都被称为运行时异常。

特点: Java编译器并不会捕获该异常。例如除数为零异常,数组越界异常,fail-fast机制产生的异常(fail-fast是一种快速失败机制,它是一种Java集合的一种错误检测机制,当多个线程对集合进行结构上的改变,那么就有可能产生fail-fast机制)。

常见五种RuntimeException

  • ClassCastException(类转换异常)
  • IndexOutOfBoundsException(数组越界)
  • NullPointerException(空指针异常)
  • ArrayStoreException(数据存储异常,操作数组是类型不一致)
  • BufferOverflowException(缓冲区域溢出异常)

(3)Error

定义: Error类及其子类。

特点: 和运行时异常一样,编译器也不会对错误进行检查。

当资源不足、约束失败、或是其它程序无法继续运行的条件发生时,就产生错误。

程序本身无法修复这些错误的。例如,VirtualMachineError、OutOfMemoryError、ThreadDeath就属于错误,出现这种错误会导致程序终止运行。

Java虚拟机规范:JVM的内存分为了好几块,比如堆,栈,程序计数器,方法区等。


25、OOM你遇到过哪些情况,SOF你遇到过哪些情况

(1)OOM

OOM,是OutOfMemoryError的简称,除了程序计数器外,虚拟机内存的其它几个运行时区域都有发生OOM异常的可能。

  • Java Heap 溢出:java.lang.OutOfMemoryError:Java heap spacess

Java堆用于存储对象实例,只要不断创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,当对象数量达到最大堆容量限制后产生OOM异常。

解决这种异常:

通过内存映像分析工具(如Eclipse Memory Analyzer)对dump出来的堆转存快照进行分析,重点是确认内存中的对象是否必要,先分清是因为内存泄漏(Memory Leak)还是内存溢出(Memory Overflow),如果是内存泄漏则进一步通过工具查看泄漏对象到GCRoots的引用链。如果不存在泄漏,则检查虚拟机参数(-Xmx与-Xms)的设置是否适当。

  • 虚拟机栈和本地方法栈溢出

如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverflowError异常。

如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常

这里需要注意当栈越大可分配的线程数越少。

  • 运行时常量池溢出

异常信息:java.lang.OutOfMemoryError:PermGenspace

如果要向运行时常量池中添加内容,最简单的做法就是使用String.intern()这个Native方法。

该方法的作用是:如果池中已经包含一个等于此String的字符串,则返回代表池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。

由于常量池分配在方法区内,我们可以通过-XX:PermSize和-XX:MaxPermSize限制方法区的大小,从而间接限制其中常量池的容量。

  • 方法区溢出
    方法区用于存放Class的相关信息,如类名、访问修饰符、常量池、字段描述、方法描述等或是方法区中保存的class对象没有被及时回收掉或者class信息占用的内存超过了我们配置。

异常信息:java.lang.OutOfMemoryError:PermGenspace

方法区溢出也是一种常见的内存溢出异常,一个类如果要被垃圾收集器回收,判定条件是很苛刻
的。在经常动态生成大量Class的应用中,要特别注意这点。

(2)SOF

StackOverflowError 的定义:当应用程序递归太深而发生堆栈溢出时,抛出该错误。

因为栈一般默认为1-2mb,一旦出现死循环或者是大量的递归调用,在不断的压栈过程中,造成栈容
量超过1mb而导致溢出。

栈溢出的原因:递归调用大量循环或死循环全局变量是否过多,数组、List、map数据过大


26、简述线程、程序、进程的基本概念。以及他们之间关系是什么?

(1)线程:

  • 一个比进程更小的执行单位。
  • 一个进程在执行过程中可以产生多个线程。
  • 同类的多个线程共享同一个内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程要小,线程也被称为轻量进程。

(2)程序

  • 含有指令和数据的文件,被存储在磁盘或其它的数据存储设备中,也就是说程序是静态的代码。

(3)进程

  • 是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。
  • 系统运行一个程序的步骤是一个进程是从创建,运行,消亡的过程。一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行,同时,每个进程还占用系统资源如CPU时间,内存空间,文件,IO设备的使用权等等。

线程、程序、进程的关系

  • 进程创建线程
  • 线程服务程序
  • 程序创建进程

27、Java 序列化中如果有些字段不想进行序列化,怎么办?

关键字:transient

作用: 阻止实例中那些用此关键字修饰的变量序列化;当对象被反序列化时,被transient修饰的变量不会被持久化和恢复。

限制: 只能修饰变量。


28、说说Java 中 IO 流

(1)IO流的分类

  • 按照流向划分: 输入流和输出流
  • 按照操作单元划分: 字节流和字符流
  • 按照流的角色划分: 节点流和处理流

(2)IO流的4中基类

  • InputStream/Reader: 所有输入流的基类,前者是字节输入流,后者是字符输入流
  • OutputStream/Writer: 所有输出流的基类,前者是字节输出流,后者是字符输出流

(3)按照操作方式分类:

  • Reader
    • 节点流: FileReader,PipedReader,CharArrayReader
    • 处理流: BufferedReader,InputStreamReader
  • Writer
    • 节点流: FileWriter,PipedWriter,CharArrayWriter
    • 处理流: BufferedWriter,InputStreamWriter,PointWriter
  • InputStream
    • 节点流: FileInputStream,PipedInputStream,ByteArrayInputStream
    • 处理流: BufferedInputStream,ObjectInputStream,SequenceInputStream
  • OutputStream
    • 节点流: FileOutputStream,PipedOutputStream,ByteArrayOutputStream
    • 处理流: BufferedOutputStream,DataOutputStream,ObjectOutputStream,PrintStream

(4)按照操作对象分类:

  • 文件操作: FileInputStream,FileOutputStream,FileReader,FileWriter
  • 缓冲操作: BufferedInputStream,BufferedOutputStream,BufferedReader,BufferedWriter
  • 管道操作: PipedInputStream,PipedOutputStream,PipedReader,PipedWriter
  • 基本数据类型操作: DataInputStream,DataOutputStream
  • 对象序列化: ObjectInputStream,ObjectOutputStream
  • 转化控制: InputStreamReader,OutputStreamWriter
  • 打印控制: PrintStream,PrintWriter
  • 数组操作: ByteInputStream,ByteOutputStream,CharArrayReader,CharArrayWriter

29、Java IO与 NIO的区别

NIO,即New IO,这个库是JDK1.4引入。NIO与IO的作用和目的相同,但实现方法不同。

NIO主要用到块,所以NIO的效率高于IO。

在Java API中提供了两套NIO

  • 标准输出: IO
  • 网络输出: NIO

30、java反射的作用原理

(1)定义: 反射机制是在运行时,对于任意一个类,都能知道这个类的所有属性和方法,对于任意个对象,都能调用它的任意一个方法。在Java中,只要给定类的名字,就可以通过反射机制来获取类的所有信息。

(2)Java 反射机制使用场景

Class.forName("com.mysql.jdbc.Driver.class"); #加载MySQL驱动类

其实Hibernate、struts等框架也都是通过反射机制实现的。

(3)反射的实现方式

第一步: 获取Class对象

  • Class.forName(“类的路径名”)
  • 类名.class(Object的方法)
  • 对象.getClass()
  • 基本类型的包装类,可以调用Type属性获取Class对象

(4)实现java反射的类

  • Class: 表示正在运行的Java应用程序中的类和接口。需要注意的是,所有对象的信息都通过Class类实现。
  • Filed: 提供有关类和接口的属性信息,以及对它的动态访问权限。
  • Constructor: 提供类的单个构造器信息以及它的访问权限
  • Method: 提供类或接口中的某个方法的信息

(5)反射机制的优缺点

  • 优点
    • 能够运行时动态获取类的实例,提高灵活性
    • 与动态编译结合
  • 缺点
    • 使用反射性能较低,需要解析字节码,将内存中的对象进行解析。
    • 相对不安全,破坏了封装性。

(6)反射机制缺点的解决方案

  • 通过setAccessible(true),关闭JDK的安全检查来提升反射速度
  • 多次常见一个类的实例时,有缓存
  • 使用ReflectASM工具类,通过字节码生成的方式加快反射速度

31、说说List,Set,Map三者的区别?

  • List(对付顺序的好帮手): List接口存储一组有序可重复的对象
  • Set(注重独一无二的性质): Set接口存储一组无序不重复的对象。
  • Map(用Key来搜索的专家): 使用键值对存储。Map会维护与Key有关联的值,且key唯一,value可重复。

32、Object 有哪些常用方法?大致说一下每个方法的含义

  • clone方法:
    一种浅拷贝方式,只有实现了 Cloneable 接口才可以调用该方法,否则抛出克隆不支持异常,深拷贝也需要实现克隆接口吗,同时引用变量的类实现克隆接口,重写clone方法。
  • finalize方法
    该方法和垃圾收集器有关系,JVM判断一个对象是否可以被回收的最后一步就是判断是否重写了此方法。
  • equals方法
    该方法使用频率高,一般在子类中重写自定义的比较方法。
  • hashCode方法
    该方法用于计算哈希值,重写 equals 方法一般都需要重写hashCode方法,这个方法在一些具有hash功能的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)设定一个时间间隔,如果在规定时间内没有获得锁就返回。
    调用后,当前线程进入睡眠状态,直至时间发生:
    • 其它线程调用notify()
    • 其它线程调用notifyAll()
    • 其它线程调用 interrupt 中断线程,抛出 interruptedException 异常
    • 时间间隔到了
  • notify方法
    配合 synchronized 使用,唤醒在该对象上等待队列中的某个线程(同步队列中的线程是给抢占CPU的线程,等待队列中的线程指的是等待唤醒的线程)
  • notifyAll方法
    配合 synchronized 使用,该方法唤醒在该对象上等待队列中的所有线程。

总结:

其它的方法,还有toString() 和 getClass 方法。


33、Java 创建对象有几种方式?

  • 关键字 new
User user = new User();
  • 反射方式
User user = User.class.newInstance();
Object obj = (Object)Class.forName(User.class).newInstance();
  • clone方式
#默认User 实现了Cloneable且重写了clone方法
User user = new User();
User clone = user.clone();
  • 使用反序列化创建对象
#默认对象实现了Serializeable接口
Object obj = ObjectInputStream.readObject();

34、获取一个类Class对象的方式有哪些?

  • getClass()
User user = new User();
user.getClass();
  • 静态成员class
User.class
  • forName()
Class.forName(User.class)

35、用过 ArrayList 吗?说一下它有什么特点?

ArrayList除了基本的可扩容特点以及数组特点外的一些特点。

  • add(o),添加到数组尾部,若增加的数据量大,应该使用 ensureCapacity(),该方法的作用预先设置大小,可以大大提高初始化速度。
  • add(int, o),添加到某个为止,可能会挪动大量的数组元素,并且可能会触发扩容机制。

高并发的情况下,线程不安全,当多个线程同时操作 ArrayList,会引发不可预知的异常或错误。

ArrayList 实现了 Cloneable接口,标识着它可以被复制。注意:该克隆是浅克隆。


36、有数组了为什么还要搞个 ArrayList 呢?

ArrayList既有数组的高效性,又有List的扩容机制,在不明确数据量的情况下,不需要初始化空间就可以进行存储数据。


37、说说什么是 fail-fast?

fail-fast机制是Java集合的一种错误机制,当多个线程多个线程对同个集合的内容进行操作时,就可能产生fail-fast 事件。

例如: 某个线程A 通过迭代器遍历集合的过程中,若该集合的内容被其它线程改变,则线程A 访问集合时,就会抛出 ConcurrentModificationException 异常,产生 fails-fast 事件。这里的操作主要是指 add、remove 和 clear,对集合元素个数进行修改。

解决方法: 使用 java.util.concurrent 包下的类 去代替 java.utils 包下的类。

可以这么理解:在遍历之前,把 modCount 记录下 expectModCount,后面 expectModCount 去和 modCount 做对比,如果不相等,证明已并发修改了,则抛出ConcurrentModificationException 异常


38、HashMap 中的 key 我们可以使用任何类作为 key 吗?

HashMao的key最多使用的是String类型,当我们想要使用某个自定义类作为 key 时,需要注意以下几点:

  • 如果类重写了equals(),则也要重写hashCode()
  • 类的所有实例需要遵循与equals 和 hashCode 相关规则
  • 如果一个类没有使用equals,则不应该在hashCode中使用
  • 自定义key最好能够使之不可变,这样HashCode能被缓存起来,拥有更好的性能。

39、HashMap 的长度为什么是 2 的 N 次方呢?

为了能让HashMap存取数据效率增加,尽可能地减少 hash 值冲突,尽量将数据均匀分配,每个链表或红黑树长度尽量相等。

问题的重要原因: 取余(%)操作符,如果是2的幂次,则等价于与其除数减一的与(&)操作符(即 hash % length == hash &(length - 1)的前提是length 是 2 的 n 次方),并且采用二进制位操作&,相对于%能提高运算效率。


40、HashMap 与 ConcurrentHashMap 的异同

  • (1)都是key-value形式存储
  • (2)HashMap 是线程不安全的,ConcurrentHashMap 是JUC下的线程安全的
  • (3)HashMap 底层是数组+链表(JDK 1.8之前),JDK1.8之后,则为 数组 + 链表 + 红黑树。当链表中元素到达8个之后,链表查询速度慢于红黑树,链表会转为红黑树。
  • (4)HashMap 初始数组大小为 16(默认),当出现扩容的时候,以 0.75 的扩容因子进行扩容。
  • (5)ConcurrentHashMap 在JDK1.8之前,采用分段锁实现 Segment(分段锁) + HashEntry,Segment 数组大小默认为 16,2 的 n 次方;JDK1.8之后,用 Node + CAS(无锁策略) + Synchronized 来保证并发安全。

41、红黑树有哪几个特征?

红黑树

  • 每个节点是 黑色红色
  • 根节点黑色
  • 每个 叶子结点 都是 黑色
  • 如果一个叶子结点是红色,则其子节点必须是黑色
  • 从一个结点到该节点的子孙节点的所有路径上包含相同数目的黑节点

42、说说你平时是怎么处理 Java 异常的

try-catch-finally

  • try:负责监控代码
  • catche:负责捕获异常,并进行处理
  • finally:负责释放资源,无论发生任何异常或return结果

自定义异常

通过继承 java.lang.Exception类,若想自定义 RuntimeException则通过继承 java.lang.RuntimeException类,实现一个无参构造和一个带有字符串参数的有参构造方法。

在业务代码中,可以通过自定义针对性的异常进行捕获,比如 用户授权验证等等…

猜你喜欢

转载自blog.csdn.net/Zain_horse/article/details/131819246
今日推荐