> 文章列表 > 【巨人的肩膀】JAVA面试总结(八)

【巨人的肩膀】JAVA面试总结(八)

【巨人的肩膀】JAVA面试总结(八)

💪

此篇主要记录一些校招杂题,春招结束前会一直更新

  • 💪
    • 1、请列举出在JDK中几个常用的设计模式
    • 2、什么是设计模式
    • 3、单例模式
    • 4、工厂模式
    • 5、装饰器模式
    • 6、观察者模式
    • 7、Java中IO流的分类?说出几个你熟悉的实现类
      • 7.1、字节输入流和字节缓冲输入流
      • 7.2、字节输出流和字节缓冲输出流
      • 7.3、字符输入流和字节缓冲输入流
      • 7.4、打印流
    • 8、了解内部类吗
    • 9、ArrayList的扩容机制
    • 10、HashMap初始化长度设置大小
    • 11、switch后面可以跟什么
    • 12、线程池的经典应用场景?
    • 13、什么是回表查询
    • 14、线程通信的方式
    • 15、Spring Bean的生命周期?
    • 16、springboot中yml、yaml、properties加载顺序
    • 17、项目部署在linux上或者服务器上 如何远程debug?
    • 18、假设要从A文件读取内容,写入到B文件,这段Java代码怎么写?读写过程中可能会出现什么异常?
    • 19、什么是CAS,使用这种机制的类有哪些

1、请列举出在JDK中几个常用的设计模式

  • 单例模式(Singleton pattern)用于Runtime,Calendar和其他的一些类中
  • 工厂模式(Factory pattern)被用于各种不可变的类如 Boolean
  • 观察者模式(Observer pattern)被用于 Swing 和很多的事件监听中
  • 装饰器设计模式(Decorator design pattern)被用于多个 Java IO 类中

2、什么是设计模式

设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。设计模式是代码可用性的延伸

设计模式分类:创建型模式,结构型模式,行为型模式

  • 创建型模式,是对对象创建过程的各种问题和解决方案的总结,常见的创建型模式有工厂模式(Factor)、单例模式(Singleton)等
  • 结构型模式,关注于类和对象的继承、组合方式的实践经验。常见的结构性模式有装饰者模式(Decorator)、代理模式(Proxy)等
  • 行为型模式,是从类或对象之间交互、职责划分等角度总结的模式。常见的行为型模式有观察者模式(Observer)、模板方法模式(Template Method)等

3、单例模式

单例的意思是一个类永远只存在一个对象,不能创建多个对象,实际开发中很多类的对象我们只需要一个,对象越多占内存越多。

  • 意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点
  • 主要解决:一个全局使用的类频繁地创建与销毁

懒汉式:通过类获取单例对象的时候发现没有对象才会去创建一个对象**

  1. 定义一个单例类
  2. 把类的构造器私有
  3. 定义一个静态成员变量用于存储一个对象(懒汉单例不能直接创建对象,必须需要的时候才去创建)
  4. 定义一个方法返回单例对象,判断对象不存在才创建一次,存在直接返回
// 定义一个单例类
class SingleInstance2{//定义一个静态成员变量用于存储一个对象!public static SingleInstance2 ins;// 1.把类的构造器私有private SingleInstance2(){}// 3.通过方法返回一个对象,第一次不存在对象才创建一个返回public static SingleInstance2 getInstance(){if(ins == null){//第一次来取对象,创建一个对象ins = new SingleInstance2();}return ins;}
}

饿汉式:通过类获取单例对象的时候,对象已经提前做好了

  1. 定义一个单例类
  2. 把类的构造器私有
  3. 定义一个静态成员变量用于存储一个对象!(饿汉单例在返回对象的时候,对象要已经做好)
  4. 定义一个方法返回单例对象
// 定义一个单例类
class SingleInstance01{// 2.定义一个静态成员变量用于存储一个对象public static SingleInstance01 ins = new SingleInstance01();// 1.把类的构造器私有,构造器只能在本类中访问// 私有的无参构造器private SingleInstance01(){}// 3.提供一个方法返回单例对象public static SingleInstance01 getInstance(){return ins;}
}

4、工厂模式

工厂模式属于创建型模式,它提供了一种创建对象的最佳方式。

  • 意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。
  • 主要解决:主要解决接口选择的问题

应用实例:您需要一辆汽车,可以直接从工厂里面提货,而不用去管这辆汽车是怎么做出来的,以及这个汽车里面的具体实现。

  • 优点:一个调用者想创建一个对象,只要知道其名称就可以了。扩展性高,如果想增加一个产品,只要扩展一个工厂类就可以
  • 缺点:每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中类的个数成倍增加

使用场景:数据库访问,当用户不知道最后系统采用哪一类数据库,以及数据库可能有变化时

5、装饰器模式

装饰器模式允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。

  • 意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活
  • 主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀

所以我们一般是在不想增加很多子类的情况下扩展类,装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

6、观察者模式

当对象间存在一对多关系时,则使用观察者模式。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式

  • 意图:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新

应用实例:卖的时候,拍卖师观察最高标价,然后通知给其他竞价者竞价。

7、Java中IO流的分类?说出几个你熟悉的实现类

7.1、字节输入流和字节缓冲输入流

按功能来分:输入流(input)、输出流(output),按类型来分:字节流 和 字符流

字节流:InputStream/OutputStream

字符流:Reader/Writer

InputStream 常用方法 :InputStream用于从源头(通常是文件)读取数据(字节信息)到内存中

  • read() :返回输入流中下一个字节的数据。返回的值介于 0 到 255 之间。如果未读取任何字节,则代码返回 -1 ,表示文件结束
  • read(byte b[ ]) : 从输入流中读取一些字节存储到数组 b 中。如果数组 b 的长度为零,则不读取。如果没有可用字节读取,返回 -1。如果有可用字节读取,则最多读取的字节数最多等于 b.length , 返回读取的字节数。这个方法等价于 read(b, 0, b.length)
  • read(byte b[], int off, int len) :在read(byte b[ ]) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字节数)。
  • skip(long n) :忽略输入流中的 n 个字节 ,返回实际忽略的字节数。
  • available() :返回输入流中可以读取的字节数
  • close() :关闭输入流释放相关的系统资源

从 Java 9 开始,InputStream 新增加了多个实用的方法

  • readAllBytes() :读取输入流中的所有字节,返回字节数组
  • readNBytes(byte[] b, int off, int len) :阻塞直到读取 len 个字节
  • transferTo(OutputStream out) : 将所有字节从一个输入流传递到一个输出流

FileInputStream 是一个比较常用的字节输入流对象,一般我们是不会直接单独使用 FileInputStream ,通常会配合 BufferedInputStream字节缓冲输入流)

// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));

DataInputStream 用于读取指定类型数据,不能单独使用,必须结合 FileInputStream

DataInputStream dataInputStream = new DataInputStream(new FileInputStream("input.txt"));
//可以读取任意具体的类型数据
dataInputStream.readBoolean();
dataInputStream.readInt();
dataInputStream.readUTF();

ObjectInputStream 用于从输入流中读取 Java 对象(反序列化),ObjectOutputStream 用于将对象写入到输出流(序列化)。

ObjectInputStream input = new ObjectInputStream(new FileInputStream("object.data"));
MyClass object = (MyClass) input.readObject();
input.close();

另外,用于序列化和反序列化的类必须实现 Serializable 接口,对象中如果有属性不想被序列化,使用 transient 修饰

7.2、字节输出流和字节缓冲输出流

OutputStream 常用方法 :OutputStream用于将数据(字节信息)写入到目的地(通常是文件)

OutputStream 常用方法 :

  • write(int b) :将特定字节写入输出流
  • write(byte b[ ]) : 将数组b 写入到输出流,等价于 write(b, 0, b.length)
  • write(byte[] b, int off, int len) : 在write(byte b[ ]) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字节数)
  • flush() :刷新此输出流并强制写出所有缓冲的输出字节。
  • close() :关闭输出流释放相关的系统资源

FileOutputStream 是最常用的字节输出流对象,通常也会配合 BufferedOutputStream(字节缓冲输出流)

// 创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("bos.txt"));

DataOutputStream 用于写入指定类型数据,不能单独使用,必须结合 FileOutputStream

// 输出流
FileOutputStream fileOutputStream = new FileOutputStream("out.txt");
DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream);
// 输出任意数据类型
dataOutputStream.writeBoolean(true);
dataOutputStream.writeByte(1);

ObjectInputStream 用于从输入流中读取 Java 对象(ObjectInputStream,反序列化),ObjectOutputStream将对象写入到输出流(ObjectOutputStream,序列化)。

ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream("file.txt")
Person person = new Person("Guide哥", "JavaGuide作者");
output.writeObject(person);

7.3、字符输入流和字节缓冲输入流

字符输入流:Reader

字符输出流:Writer

Reader 用于读取文本, InputStream 用于读取原始字节。

Reader 常用方法 :

  • read() : 从输入流读取一个字符
  • read(char[] cbuf) : 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中,等价于 read(cbuf, 0, cbuf.length)
  • read(char[] cbuf, int off, int len) :在read(char[] cbuf) 方法的基础上增加了 off 参数(偏移量)和 len 参数(要读取的最大字符数)。
  • skip(long n) :忽略输入流中的 n 个字符 ,返回实际忽略的字符数。
  • close() : 关闭输入流并释放相关的系统资源。
// 创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
// 创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));

7.4、打印流

public PrintStream(String fileName) : 使用指定的文件名创建一个新的打印流。

public class PrintDemo {public static void main(String[] args) throws IOException {// 调用系统的打印流,控制台直接输出97System.out.println(97);// 创建打印流,指定文件的名称PrintStream ps = new PrintStream("ps.txt");// 设置系统的打印流流向,输出到ps.txtSystem.setOut(ps);// 调用系统的打印流,ps.txt中输出97System.out.println(97);}
}
System.out.print("Hello!");
System.out.println("Hello!");

System.out 实际是用于获取一个 PrintStream 对象,print方法实际调用的是 PrintStream 对象的 write 方法。PrintStream 属于字节打印流,与之对应的是 PrintWriter (字符打印流)

8、了解内部类吗

定义在一个类里面的类就是内部类,内部类的好处:可以提供更好的封装性,内部类有更多的权限修饰符,封装性有了更多的控制。

内部类的分类:

  • 静态内部类
  • 实例内部类
  • 局部内部类
  • 匿名内部类(重点):匿名内部类是一个没有名字的内部类
new 类名|抽象类|接口(形参){方法重写
}

9、ArrayList的扩容机制

ArrayList是List接口的实现类,它是支持根据需要而动态增长的数组。java中标准数组是定长的,在数组被创建之后,它们不能被加长或缩短。这就意味着在创建数组时需要知道数组的所需长度,但有时我们需要动态程序中获取数组长度。ArrayList就是为此而生的,但是它不是线程安全的
ArrayList扩容的本质就是计算出新的扩容数组的size后实例化,并将原有数组内容复制到新数组中去。一般是扩容至原来的1.5倍

10、HashMap初始化长度设置大小

我们可以自定义初始map的长度,若果我们传的参数是5,那么map的长度就是5吗?答案是NO:默认初始容量-必须是2的幂。如果传的参数是5,hashmap底层会自动帮我们优化成比5大的2的倍数的最小值,也就是2的3次方8。

11、switch后面可以跟什么

整型,字符型,枚举型。但绝对不可以是实数,float 型变量、double 型变量、小数常量通通不行,全部都是语法错误。

12、线程池的经典应用场景?

13、什么是回表查询

回表查询可以理解为非聚集索引(普通索引)的查询,先定位主键值,再定位行记录,它的性能较扫一遍索引树更低。

  • 一般我们自己建的索引不管是单列索引还是联合索引,其实一个索引就对应着一颗独立的索引B+树,索引B+树的节点仅仅包含了索引里的几个常见的字段的值以及主键值。即使我们根据索引树按照条件找到了需要的数据,那也仅仅是索引里的几个字段的值和主键值,万一你要查询select * 还需要很多其他的字段,那就还需要一个回表查询:根据主键跑到主键的聚簇索引里去找,聚簇索引的叶子节点是数据页,找到数据页才能把一行数据的所有字段值提取出来。

如何减少回表查询:

  1. 索引覆盖:即将查询sql中的字段添加到联合索引里面,只要保证查询语句里面的字段都在索引文件中,就无需进行回表查询

非聚集索引一定会回表查询吗

不一定。这涉及到查询语句所要求的字段是否全部命中了索引,如果是,那么就不需要回表查询

14、线程通信的方式

线程通信就是当多个线程共同操作共享的资源时,互相告知自己的状态以避免资源争夺。

线程通信主要可以分为三种方式,分别为共享内存消息传递管道流。每种方式有不同的方法来实现

  1. 共享内存:通过 volatile 共享内存,共享程序的公共状态,线程之间通过读-写内存中的公共状态来隐式通信
  2. 消息传递:通过 wait、notify、notyfyAll、join 方式进行线程通信
  3. 管道流:管道输入/输出流的形式

15、Spring Bean的生命周期?

在传统的Java应用中,bean的生命周期很简单。使用Java关键字new进行bean实例化,然后该bean就可以使用了。一旦该bean不再被使用,则由Java自动进行垃圾回收。相比之下,Spring容器中的bean的生命周期就显得相对复杂多了。bean在Spring容器中从创建到销毁经历了若干阶段,每一阶段都可以针对Spring如何管理bean进行个性化定制。

16、springboot中yml、yaml、properties加载顺序

可以查看查看 spring-boot-starter-parent 中中的源码,加载顺序为:yml --> yaml --> properties。在只有yml和yaml情况下,以yml为准。

17、项目部署在linux上或者服务器上 如何远程debug?

  1. tomcat服务器下远程debug的配置
  2. springboot项目jar包的形式远程debug配置:springboot内嵌了tomcat,项目默认打成jar包,只需要在启动的时候加上如下参数,设置远程debug端口为8001
  3. 开发工具IDEA上进行配置

18、假设要从A文件读取内容,写入到B文件,这段Java代码怎么写?读写过程中可能会出现什么异常?

编程思路:将A文件中的内容复制到B文件中

复制原理:其实就是将A文件中文件数据存储到B文件中,在自己想要存储的地方创建一个B文件,用于存储A文件中的数据,定义读取流和A文件关联,通过不断的读写完成数据存储,最终关闭流资源。

  1. 方法一:从A文件中读一个字符,就往B文件中写一个字符
//方法一:从A文件中读一个字符,就往B文件中写一个字符
public static void copy_1() throws IOException{//创建目的地FileWriter fw= new FileWriter("DemoCope1.txt");FileReader fr = new FileReader("E:\\\\JAVA\\\\TestMap\\\\src\\\\com\\\\diaobao\\\\map\\\\TastMap.java");int num = 0;while ((num = fr.read()) !=-1) {fw.write(num);}fw.close();fr.close();
}
  1. 方法二:先把A文件中的内容全部读出来后,在往B文件中写
  2. 方法三:使用缓存区的方法进行

19、什么是CAS,使用这种机制的类有哪些

CAS 全称compareAndSet,比较并交换的意思,CAS为无锁操作(其实就是CPU级别的锁,跟操作系统没关系,而且CPU级别的锁比较快)。

顾名思义CAS操作分为两步,先比较后交换。既然要进行比较然后在进行交换,那肯定是涉及到了三个参数,自己V、和谁进行比较 E、比较完之后需要修改成谁 U。

目前CAS在jdk中主要应用在J.U.C相关包下的Atomic相关类中,主要有AtomicInteger、AtomicLong、AtomicBoolean、AtomicDouble、AtomicReference、AtomicReferenceFieldUpdater等。