JavaSE面试题整理


JavaSE面试题整理

  • Object类
    • - Object类自带哪些方法?
  • String类
    • - 构造方法
    • - 方法
    • - String、StringBuffer、StringBuilder的区别
  • Collection集合
    • - collection和collections的区别
    • - Set里的元素用什么方法来区分重复与否呢?
    • - Java泛型底层源码解析ArrayList,LinkedList,HashSet和HashMap
      • ArrayList
      • LinkedList
      • HashSet
      • HashMap
    • - HashMap和Hashtable的区别
    • - HashMap、LinkedHashMap、ConcurrentHashMap的异同
  • IO流
    • - Serialization序列化
  • Thread线程
    • - 开启线程的三种方式
    • - 进程,线程,协程之间的区别
    • - 线程之间是如何通信的
    • - 在Java中wait和seelp方法的不同
    • - 谈谈ThreadLocal关键字
    • - run()和start()方法区别
    • - 什么是线程池,为什么要用线程池,说出几种常见的线程池
    • - 描述一下线程的生命周期(描述5个状态)
    • - 为什么会发生死锁,如何避免死锁
    • - 多线程中的锁有哪些种类,说下区别
      • 可重入锁
      • 可中断锁
      • 公平锁
      • 读写锁
      • 可重入锁
    • - Synchronized和Lock锁的区别
    • - Synchronized有什么缺陷
    • - 并行与并发的异同
    • - Java如何实现并发


Object类

- Object类自带哪些方法?

  • hashCode()
    hashCode相等,不一定equals
    equals时,hashCode一定相等
    如果不重写hashcode(),在HashSet中添加两个equals的对象,会将两个对象都加入进去。
  • toString()
  • equals(Object obj)
  • clone()
    实现对象的浅复制
  • finalize()
    垃圾回收机制
  • getClass()
    getClass()经常用于java反射机制
  • 5个线程方法
    notify() notifyAll() 3个wait()

String类

- 构造方法

String()
String(String value)
String(byte[] bytes, int offset, int length)
String(byte[] bytes, String charsetName)
String(byte[] bytes, int offset, int length, String charsetName) //指定字符集
String(char[] value)
String(char[] value, int offset, int count)
String(StringBuffer buffer)
String(StringBuilder builder)

- 方法

  • isEmpty()
  • length()
  • charAt(int index)
    返回指定索引的字符值
  • indexOf()

indexOf(int ch)
indexOf(String str)
indexOf(int ch, int fromIndex)
indexOf(String str, int fromIndex)

  • lastIndexOf()

lastIndexOf(int ch)
lastIndexOf(String str)
lastIndexOf(int ch, int fromIndex)
lastIndexOf(String str, int fromIndex)

字符串加工

  • concat(String str)
    将指定字符串连接到字符串结尾
  • subString(int beginIndex)
    -subString(int beginIndex, int endIndex)
    字符串切割
  • toUpperCase()
    全大写
    -全小写 LowerCase()
  • compareTo(String anotherString)
  • equalsIgnoreCase(String anotherString)
  • trim()
  • valueOf()
    int,long,float,double,char,char数组,Object转换成String的方法
  • getBytes()
    把String字符串转换为字节数组
    -byte[] getBytes(String charsetName)
  • replace(char oldChar, char newChar)
    与正则表达式相关
  • matches(String regex)
  • split(String regex)
    切割得到的字符串数组不包含作为分割的字符

- String、StringBuffer、StringBuilder的区别

  • String 是不可变的对象, 因此在每次对 String 类型进行改变的时候其实都等同于生成了一个新的String 对象,然后将指针指向新的 String 对象,所以经常改变内容的字符串最好不要用 String类型 ,因为每次生成对象都会对系统性能产生影响。
  • StringBuffer 和 StringBuilder 二者都继承了 AbstractStringBuilder ,底层都是利用可修改的char数组(JDK 9 以后是 byte数组)。
String StringBuffer StringBuilder
可以赋空值 不可以赋空值 不可以赋空值
不可变 可变 可变
线程安全 线程安全 线程不安全
速度●○○ 速度●●○ 速度 ●●●
  • StringBuffer、StringBuilder 类只对字符串对象本身进行操作,而不是生成新的对象,所以速度快。且StringBuffer方法synchronized修饰(方法加锁),所以执行较慢。

Collection集合

JavaSE面试题整理_第1张图片

- collection和collections的区别

  • java.util.Collection 是一个集合接口(集合类的一个顶级接口)。
  • Collections则是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。

- Set里的元素用什么方法来区分重复与否呢?

  • 用 equals

==:基本类型:比较的是值是否相同
引用类型:比较的是地址值是否相同
equals():
引用类型:默认情况下,比较的是地址值,可进行重写,比较的是对象的成员变量值是否相同

- Java泛型底层源码解析ArrayList,LinkedList,HashSet和HashMap

ArrayList

-ArrayList底层采用数组实现,当使用不带参数的构造方法生成ArrayList对象时,实际上会在底层生成一个长度为10的Object类型数组。
-扩容规则:
新增的时候发现容量不够用了,才去扩容
扩容后的大小= 原始大小+原始大小/2 + 1【JDK1.6之前】
扩容后的大小= 原始大小+原始大小/2【JDK1.7之后】
直接add是将元素赋值给数组顺序的最后一位。
指定插入位置的add方法,参考以下:【arraycopy】

public void add(int index, E element) {
	rangeCheckForAdd(index);
	ensureCapacityInternal(size + 1);
	System.arraycopy(elementData, index, elementData, index + 1, size - index);
	elementData[index] = element;
	size++;
}

LinkedList

-LinkedList底层采用双向链表实现
-没有初始长度,也没有扩容规则

HashSet

-HashSet底层是使用HashMap来实现的
-扩容规则:
当前大小 和 当前容量 的比例超过了扩容因子,就会扩容
对应HashMap默认初始长度为16,扩容因子默认0.75。
add是将该对象作为底层所维护的Map对象的key,而value则是同一个Object对象
扩容2倍

HashMap

-HashMap底层是使用数组和单向链表结合来实现的
-扩容规则:
存放新值的时候当前已有元素的个数必须大于等于阈值
存放新值的时候当前存放数据发生hash碰撞(当前key计算的hash值换算出来的数组下标位置已经存在值)两者同时满足才发生扩容

因为上面这两个条件,所以存在下面这些情况
(1)就是hashMap在存值的时候(默认大小为16,负载因子0.75,阈值12),可能达到最后存满16个值的时候,再存入第17个值才会发生扩容现象,因为前16个值,每个值在底层数组中分别占据一个位置,并没有发生hash碰撞。
(2)当然也有可能存储更多值(超多16个值,最多可以存26个值)都还没有扩容。原理:前11个值全部hash碰撞,存到数组的同一个位置(这时元素个数小于阈值12,不会扩容),后面所有存入的15个值全部分散到数组剩下的15个位置(这时元素个数大于等于阈值,但是每次存入的元素并没有发生hash碰撞,所以不会扩容),前面11+15=26,所以在存入第27个值的时候才同时满足上面两个条件,这时候才会发生扩容现象。

扩容2倍

- HashMap和Hashtable的区别

HashMap Hashtable
继承AbstractMap类 继承Dictionary类
key、value可以为空值(但null值的key只能有一个) key、value都不可以为空值
Iterator遍历 Enumeration遍历
HashMap和Hashtable不同补充
hash值的计算方式不同
数组初始化和扩容方式不同

- HashMap、LinkedHashMap、ConcurrentHashMap的异同

HashMap LinkedHashMap ConcurrentHashMap
无序 有序 无序
不安全 不安全 安全(锁一个桶)
有序的HashMap 线程安全的HashMap
  • ConcurrentHashMap是使用了锁分段技术技术来保证线程安全的,锁分段技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问
  • LinkedHashMap维护一个双链表,可以将里面的数据按写入的顺序读出

IO流

按照传输的单位分成字节流,字符流
字节流的父类:InputStream OutputStream
字符流的父类:Reader Writer

- Serialization序列化

  • 序列化是将内存中的对象以二进制的方式存储在硬盘文件中。

  • 反序列化是将硬盘文件中表示对象的二进制转化为对象。

  • 序列化实现
    需要让一个类实现Serializable接口,该接口是一个标识性接口,标注该类对象是可被序列化的,然后使用一个输出流来构造一个对象输出流并通过writeObject(Object)方法就可以将实现对象写出(即保存其状态)

  • 序列化除了能够实现对象的持久化之外,还能够用于对象的深度克隆【clone浅克隆】

深度克隆/浅度克隆:对某个对象进行克隆,对象的的成员变量如果包括引用类型或者数组,那么克隆的时候其实是不会把这些对象也带着复制到克隆出来的对象里面的,只是复制一个引用,这个引用指向被克隆对象的成员对象,但是基本数据类型是会跟着被带到克隆对象里面去的。而深度可能就是把对象的所有属性都统统复制一份新的到目标对象里面去。

Thread线程

- 开启线程的三种方式

1.继承Thread类创建线程

//继承Thread类
public class MyThread extends Thread{
	//重写run方法
  public void run(){}
}
public class Main {
	public static void main(String[] args){
		new MyThread().start();	//创建并启动线程
	}
}

2.实现Runnable接口创建线程

//实现Runnable接口
public class MyThread2 extends Runnable{
	//重写run方法
  public void run(){}
}
public class Main {
	public static void main(String[] args){
		new Thread(new MyThread()).start();	//创建并启动线程
	}
}
//直接在函数体内使用
Thread t = new Thread(new Runnable(){
	public void run(){
		System.out.println("内部类开启线程");
	}
});

3.使用Callable和Future创建线程

//实现Callable接口
public class MyThread2 implements Callable {
	//重写call()方法
  public Object call(){	
  	return null;	
  }
}
public class Main {
	public static void main(String[] args){
		//使用Lambda类表达式创建Callable对象
		//使用futureTask类来包装Callable对象
		Callable callable = new MyThread3();
		FutureTask futureTask = new FutureTask(callable);
		Thread thread = new Thread(futureTask);
		thread.start();
	}
}

- 进程,线程,协程之间的区别

进程 线程 协程
不共享堆 共享堆 共享堆
不共享栈 不共享栈 不共享栈
由操作系统调度 由操作系统调度 在协程的代码里显示调度
  • 进程是系统分配资源的最小单位
  • 线程是CPU调度的最小单位
  • 如果操作系统是双核的,而且线程是标准线程,那么线程 A 和 B 可以达到真的并行
  • 协程和线程区别:协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。

JavaSE面试题整理_第2张图片

- 线程之间是如何通信的

共享内存机制和消息通信机制。
1.同步。访问权限【锁】。
2.notify/wait机制

- 在Java中wait和seelp方法的不同

wait seelp
Object Thread
会释放锁 不会释放锁

- 谈谈ThreadLocal关键字

  • ThreadLocal是共享变量的一份拷贝。
  • ThreadLocal采用了“以空间换时间”的方式。为每一个线程都提供了一份变量,因此可以同时访问而互不影响。

- run()和start()方法区别

  • start():该方法是在当前线程中启动一个新的线程,而新启动的线程会调用run()方法,同时该方法不能重复调用;
  • run() :该方法和普通的方法一样,可以重复执行,不会创建新的线程。

- 什么是线程池,为什么要用线程池,说出几种常见的线程池

  • 线程池:Java中开辟出了一种管理线程的概念,这个概念叫做线程池
  • 使用线程池的好处主要有以下几点:
    (1)因为在请求时,线程已经存在,从而消除了线程创建所带来的延迟
    (2)通过调整线程池中的线程数目,可以有效防止资源不足
  • 四种常见的线程池:
    CachedThreadPool:可缓存的线程池,该线程池中没有核心线程,非核心线程的数量为Integer.max_value,就是无限大,当有需要时创建线程来执行任务,没有需要时回收线程,适用于耗时少,任务量大的情况。
    SecudleThreadPool:周期性执行任务的线程池,按照某种特定的计划执行线程中的任务,有核心线程,但也有非核心线程,非核心线程的大小也为无限大。适用于执行周期性的任务。
    SingleThreadPool:只有一条线程来执行任务,适用于有顺序的任务的应用场景。
    FixedThreadPool:定长的线程池,有核心线程,核心线程的即为最大的线程数量,没有非核心线程

- 描述一下线程的生命周期(描述5个状态)

  • 新建New 可运行Runnable 运行Running 阻塞Blocked 死亡Dead
    JavaSE面试题整理_第3张图片

New:当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
Runnable:当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
Running:当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
Blocked:处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
2.同步阻塞 – 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3.其他阻塞 – 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

Dead:线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

- 为什么会发生死锁,如何避免死锁

  • java 死锁产生的四个必要条件:
    (1)互斥使用,即当资源被一个线程使用(占有)时,别的线程不能使用
    (2)不可抢占,资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放。
    (3)请求和保持,即当资源请求者在请求其他的资源的同时保持对原有资源的占有。
    (4)循环等待,即存在一个等待队列:P1占有P2的资源,P2占有P3的资源,P3占有P1的资源。这样就形成了一个等待环路。
  • 如何避免死锁

- 多线程中的锁有哪些种类,说下区别

可重入锁

可中断锁

公平锁

读写锁

可重入锁

- Synchronized和Lock锁的区别

synchronized Lock
Java的关键字,在jvm层面上 在finally中必须释放锁,不然容易造成线程死锁
假设A线程获得锁,B线程等待。如果A线程阻塞,B线程会一直等待 分情况而定,Lock有多个锁获取的方式,具体下面会说道,大致就是可以尝试获得锁,线程可以不用一直等待
无法判断锁状态 可以判断锁状态
锁类型可重入 不可中断 非公平 可重入 可判断 可公平(两者皆可)
性能少量同步 大量同步

- Synchronized有什么缺陷

(1)不能够跨越多个对象。
(2)当在等待锁对象的时候,不能中途放弃,直到成功。
(3)等待没有超时限制。
(4)不能中断阻塞。

- 并行与并发的异同

并发就是指代码逻辑上可以并行,有并行的潜力,但是不一定当前是真的以物理并行的方式运行。并发指的是代码的性质,并行指的是物理运行状态。

- Java如何实现并发

  • Synchronized是Java中解决并发问题的一种最常用的方法。Synchronized的作用主要有三个:
    (1)确保线程互斥的访问同步代码
    (2)保证共享变量的修改能够及时可见
    (3)有效解决重排序问题。
  • Java并发编程:Synchronized及其实现原理

你可能感兴趣的:(JavaSE)