2021 Java面试汇总(未分类,按照大厂面试题进行解答)

2021 Java面试汇总(未分类,按照大厂面试题进行解答)

包含Java、数据结构、操作系统、计算机网络、算法等相关知识,未分类,按照看到的面经进行解答,可以评论转发喔。

1. 数组和链表的区别

数组和链表都是数据结构中存储数据的容器,数组底层使用连续的地址空间进行数据的存取,链表使用指针(java中使用引用)的方式进行存取。数组的分配需要大量的连续空间,如果大量的使用数组,可能会造成内存碎片严重。链表可以把碎片化的空间通过指针进行连接,从而提高内存的利用率。其次,数组可以随机存取,他的读操作快(直接根据下标),写操作慢(可能涉及到数组的移位)。链表插入数据块,但是读的时候需要遍历,速度较慢。

2. 重写和重载

重写一般是指重写父类已经定义好的方法,而重载呢,是指在一个类中,存在函数名相同,但是参数不同(包括参数列表,参数个数)的函数。通常用来完成相同的功能。重写一般是用于对父类的方法做一个扩充,而我们的重载一般是为了适应不同的参数,比如说max函数,sum函数等等。但是在设计模式中,我们一般不建议重写父类已经写好的方法,这样回违反里氏替换原则。

3. final、finally、finalize的应用场景

final:一般修饰类或者不变的量,一般一个类如果不想被继承,通常用final修饰,比如说String,final修饰类的话,其所有的方法都会被隐试的添加final的修饰符。
finally:异常捕获体系中一定会执行的方法,我们在关闭数据库连接,redis连接,socket等连接。
finalize:在垃圾回收的时候需要调用的方法,类似于C++中的析构函数,一般我们可以在这里写一些对类中一些变量的处理,比如DBHelper中关闭连接等操作。还有一个神奇的用法就是在我们垃圾回收的时候,如果在finalize中重新给自身添加引用,可以逃脱垃圾回收。

4. HashMap的结构、扩容、头插尾插。

在jdk1.7 的时候,hashmap使用数组 + 链表的结构,在jdk 1.8 以后,采用的是数组 + 链表 + 红黑树的结构。初始的桶数组的长度为16,当我们的桶数组占用了8个,并且hashmap存储元素超过64个的时候,会从链表转换为红黑树。如果超过了扩容因子,但是元素并未超过64的时候,优选应该是扩容。在桶数组小于6个且存储元素小于64个的时候,会转变成你那表。
在jdk8之前,采用的是头插法,之后是尾插。jdk8之前之所以使用头插,是为了凸显一个热点效应,即后插入的,一般来说先用到,但是这是个伪命题,在进行rehash操作的时候,会颠倒我们的链表。在并发条件下,头插还容易产生死锁。

5. IO流

在java中,流一般分为二进制的字节流,input/outputStrem,和字符流InputStreamReader/OutSteamWriter。

6. sychornized和volatile

都是在Java并发编程中提到的重要的知识点。volatile可以保证有序性和可见性,而我们的sychornized可以保证原子性。
sychornized可以在方法,代码块上进行一个加锁,在方法上加锁,说明这个方法是一个同步方法。对一段代码加锁,需要传入一个锁对象,我们一般传入一个类或者对象,对象的内存结构会记录锁的状态,只允许一个线程进入。在jdk1.6之前,sychornized使用,会向操作系统申请一个mutex互斥量,但是这样效率很低,所以我们在以后就引入了无锁、偏向锁(只有一个线程)、轻量级锁(自旋锁)、重量级锁。 在不同的情况下,sychornized会在不同的情况使用不同的锁。提高了sychornized的效率,已经达到了和lock锁相当的效率。同时sychornized是非公平锁,由虚拟机决定加锁和解锁,是可重入锁。 一个线程如果尝试获取锁失败之后,会进入一个同步队列中(SynchronizedQueue)阻塞住。
volatile是保持缓存一致性协议(MESI)的重要组成部分,保证我们的一个线程对volatile变量进行修改,马上就可以让别的线程得知,同时加入了内存屏障(读屏障、写屏障)保证了指令的有序性。但是无法保证原子性。

7. 垃圾回收算法

标记清除算法:标记无引用的对象,直接清除,会产生大量的内存碎片。
标记整理算法:标记之后进行整理。
复制算法:每次只用一半的内存空间,然后每次收集,把存活的对象移动到另外的一半内存中。
分代收集算法:把内存划分为新生代和老年代,然后在不同的区域使用不同的垃圾回收算法。

8. 索引的底层数据结构

索引底层采用了B+树实现了索引结构。非叶子节点存放索引的值,叶子节点存放索引 + 数据。聚集索引(Innodb),非聚集索引(MyISAM)。在叶子节点,有指向其兄弟节点的指针,又因为有序,树的高度低,可以达到很高的查询效率。

9.TCP三次握手

tcp三次握手是tcp连接管理里面的一个重要的知识点,用于在双方发送之前建立链接。首先客户端往服务端发送请求连接的数据(syn = 1, seq = x,ack = 0),发送之后进入“同步以发送”状态,服务端收到消息,返回愿意接受连接的报文(syn = 1, seq = y,ack = x + 1),(服务端进入同步已接收状态),客户端收到消息给服务端返回确认报文。(seq = x + 1 ,(ACK = 1) ack = y + 1),这里传输的x,y就是以后传输的基准。(客户端和服务端进入连接已建立状态)。因为TCP是全双工通信,所以我们必须保证客户端和服务端都有发送数据和接收数据的能力,所以必须要三次握手。

10. 进程和线程

进程是CPU分配资源的最小单位,线程是CPU执行和调度的最小单位。
从并发度来讲: 线程比进程有更高的并发度,因为CPU的大量的任务都是(朝花夕拾的),特别是针对服务端程序来说,处理一个请求开辟一个进程无意义。
从资源方面说: 进程是CPU分配资源的最小单位,线程是利用进程的资源,没有自己独立的资源,但是会有线程隔离的方法栈,程序计数器等。
从调度来说: 进程调度很消耗cpu的资源,但是线程很轻,不存在资源的分配和消耗。
对CPU的利用率上说: 线程对CPU的利用率更高。
从属关系上来说: 一个进程可以有多个线程,一个线程必须从属于一个进程。

11 . a线程在b线程之前执行如何操作

  1. 设置互斥量(Semaphore),a线程执行完了,只能让b线程执行。
  2. 使用CountDownLatch阻塞a线程,当a线程执行完成之后,在执行b线程。
  3. 使用join方法,join方法表示优先执行一个线程(相当与高优先级)
  4. 使用单线程池(Executors.newSingleThreadExecutor),提供一个单线程,按照循序执行任务。(注意一定使用完成一定要关闭,singlePool.shutdown());

12. 类加载机制

Java中的类加载是通过类加载器进行加载的。一般分为四种类加载器。(启动类加载器,扩展类加载器,应用程序类加载器,自定义类加载器),并且采用双亲委派机制进行加载,即委托给父加载器加载,如果父加载器加载了,就不会在加载,如果父加载器不能加载,那么就自身加载。
好处: 沙箱安全机制,可以防止java的核心类库被破坏。
防止类的重复加载。
类加载一般分为:加载、验证、准备、解析、初始化等几个步骤。
加载: 把字节码加载到内存中。
验证: 验证字节码的格式是否符合要求。
准备: 给类变量,静态常量等分配内存,并赋予默认值。
解析: 将符号引用替换为直接引用,相当与页表机制里面产生一个页表,指向实际的内存空间。
初始化: 对类的静态变量初始化为指定的值,执行静态代码块。

13. 事务的特征

(ACID)
原子性: 事务是一组操作,要么成功,要么全部回滚。
一致性: 在执行事务的前后(包括回滚),数据库必须处于数据一致的状态(也可以理解成不会有脏数据)。
隔离性: 事务与事务之间不应该相互影响,是独立的。
持久性: 当事务执行完毕,会直接把修改的信息持久化到数据库。

14. 事务的隔离级别

  1. 读未提交,可能出现,脏读、幻读、不可重复读的问题。
  2. 读已提交,可能出现幻读、不可重复读的问题。
  3. 可重复读,可能出现幻读。
  4. 可串行化。
    可重复读可以解决幻读吗?
    不能,可重复读在mysql使用mvcc机制保证,但是在可重复读的隔离级别下, 虽然读不到别的事务添加的数据,但是在修改(特别是无条件的修改的时候),会修改到别的事务提交的数据,说白了,并没有真正的解决幻读的问题。
    MVCC是在每条数据后面添加了两个字段,一个是创建事务的id,一个是删除事务的id,并没有对更新做一些操作,所以我们的可重复读出现幻读的方式就是通过更新进行一个体现。当我们查询的时候,必须要 创建事务的id <= 当前事务的id ,删除事务id为空或者删除事务的id >= 当前事务的id。 才能够查询出来。
    当插入数据的时候,创建事务的id = 当前事务的id,删除的时候,删除事务的id = 当前事务的id。事务的id是递增的,由数据库系统进行维护。
  • 脏读:读到了别的事务修改了当时还没有提交的数据。
  • 幻读:读到了别的事务提交的新增的数据。一个事务按照相同的查询条件重新读取以前检索过的数据,却发现其他事务插入满足其查询条件的新数据,这种现象称之为:幻读。

15. 唯一索引和主键的区别

创建主键的同时会创建一个唯一索引

  1. 主键是一种约束,目的是对这个表的某一列进行限制。
  2. 唯一索引是一种索引,索引时数据库表的一个冗余结构,目的是为了更好的查询。
  3. 主键列不允许为空,而唯一性索引列允许空值。
  4. 一个表最多只能一个主键,但是可以包含多个唯一索引。

16. 数据库的三大范式

  1. 所有列都是不可分割的。(保证原子)
  2. 所有列都与主键有关,由主键决定
  3. 所有列只与主键有关,不存在传递函数依赖。

17. ARP协议

ARP是地址解析协议,一般用于广播获取mac地址。获取了主机的mac地址才能建立链接。根据IP地址获取物理地址,并缓存起来。可能出现ARP欺骗(正因为ARP协议是广播的,才会出现这种情况。)

18. SMTP电子邮件传输协议

2021 Java面试汇总(未分类,按照大厂面试题进行解答)_第1张图片

19. 操作系统的系统调用

操作系统的接口是连接应用软件与操作系统的中间桥梁。接口在程序设计中表现形式就是:函数。操作系统提供的函数就被称为系统调用(system call);

  1. pid_t fork():创建一个新的进程。
    说明:本系统调用产生了一个新的进程,叫子进程,是调用进程的一个复制品,调用的进程叫父进程。子进程继承了父进程几乎所有的属性。
  2. pid_t waitpid(pid, stat_loc,options)
    等待指定进程号的子进程的返回并修改状态。
  3. exit(status):以某种状态码结束进程。

Linux上面在补一下

20.进程之间的通信方式

  1. 使用管道进行通信(相当与对文件进行读写)
  2. 使用共享内存进行通信。(速度快)
  3. 使用互斥量(mutex),比如说我们的生产者,消费者模型。
  4. 通过消息队列进行通信。
  5. 通过网络进行通信。

21. 用户态和内核态的区别

为了防止用户随意更改系统的文件,导致系统崩溃。操作系统把内存划分成了用户态和内核态,用户态运行用户的程序内核态运行系统程序、操作硬件。
运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态(比如操作硬件)。
主要差别:

  1. 处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理器是可被抢占的。
  2. 处于内核态执行时,则能访问所有的内存空间和对象,且所占有的处理器是不允许被抢占的。

什么情况下会导致用户态到内核态的切换:

  1. 系统调用: 这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。
  2. 异常: 用户态在执行程序的时候,发生异常,会触发核心态的异常处理程序当中。比如说最常见的缺页中断。
  3. 外围设备中断: 当外围设备完成用户请求操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将执行的指令转而去执行中断信号对应的处理程序。

22. HTTP和HTTPS的区别

  1. 内容类型不同,HTTP(超文本传输协议),HTTPS(安全的超文本传输协议)。
  2. 端口不同:HTTP使用80端口,HTTPS使用443端口。
  3. HTTPS通过在tcp传输之上加上安全的SSL和TLS层来保证安全传输(包括进行身份认证),而HTTP的数据则是在网络上明文进行传输。(无状态,没有身份认证)。
  4. HTTPS需要到安全认证中心申请证书,需要交纳一定的费用。HTTP免费试用。

23. HTTPS的加密过程

首先:明白HTTPS是在应用层(HTTP)和传输层(TCP)之间增加了一个SSL层,达到了安全传输的目的;
采用对称加密算法加密数据 + 非对称加密算法交换密钥 + 数字证书验证身份

  1. 客户端访问HTTPS加密的服务器。
  2. 服务器返回证书(包含很多信息,证书的颁发机构,过期时间等等)给客户端。
  3. 客户端校验证书(包含公钥)的合法性。
  4. 客户端用公钥加密对称加密的私钥(产生一个随机值),并发送给服务器。
  5. 服务器通过非对称加密的私钥解密信息,获得对称加密的私钥。
  6. 服务端和客户端通过对称加密的私钥对数据进行加密传输。

24 . 磁盘调度算法

磁盘调度:磁盘调度在多道程序设计的计算机系统中,各个进程可能会不断的提出不同的对磁盘进行读/写的请求。由于有时候这些进程的发送请求的速度比磁盘响应快很多,因此我们有必要为每个磁盘设备建立一个等待队列,常用的磁盘调度算法有下面四种。
先来先服务(FCFS): 公平、简单,每个请求都能够一次得到处理。在访问请求比较多的情况下,会降低设备服务的吞吐量。
最短寻道时间优先算法(SSTF): 该算法选择离当前磁头所在磁道最近的磁道进行访问,以每次寻道的时间最短。可以得到较大的吞吐量,但是边缘磁道的请求将会无限期被延迟,得不到处理。
扫描算法(SCAN,电梯调度): 利用电梯的特性,该算法不仅考虑到磁道与当前磁道之间的距离,更加优先考虑的是磁头的移动方向,向外移动到最外,然后向内移动。避免出现饥饿现象。吞吐量大,平均响应时间小,但是由于是摆动的扫描方式,两侧磁道被访问的频率仍然低于中间磁道。
循环扫描算法(CSCAN): 类似于上面的扫描算法,但是扫描算法是摆动扫描,循环扫描是到达一端之后,马上转到另一端,然后继续的扫描,类似于转圈圈。(故称为循环扫描算法)

25. Java字符流和字节流的区别。

  1. 字节数不同,字符流处理的单元为两个字节的unicode字符,可以分别操作字符,字符数组或字符串。字节流处理单元为一个字符,操纵字节或者字节数组。
  2. 字节流可用于任何类型的对象,包括二进制对象,而字符流只能处理字符或者字符串。
  3. 字节流提供了处理任何类型的IO操作的功能,但他不能字节处理Unicode字符,而字符流就可以。
    ps:所有文件的存储都是字节(byte)的存储,在磁盘上保留的并不是文件的字符而是先把字符编码成字节,在存储这些字节到磁盘。在读取文件(特别是文本文件)时,也是一个字节一个字节的读取形成字节序列。

26. Java中线程通信的方式

  1. 同步:使用syncronized关键字。
  2. while 轮训。(等待某个条件的发生)
  3. wait/notify机制。

27. 进程之间通信的方式。

  1. 通过管道。
  2. 共享内存。
  3. 通过信号量。
  4. 消息队列。
  5. socket网络。

28. 守护线程 VS 用户线程

只要当前JVM实例中尚存任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束,守护线程随着JVM一起结束工作。最典型的就是GC(垃圾回收线程),普通的线程可以调用setDaemon(),编程守护线程。
简言之:守护线程就是后台的线程,用户线程就是前台的线程。

29. 索引的最佳实践

  1. 全值匹配:SELECT * FROM employees WHERE name= ‘LiLei’;
  2. 最左前缀法则。
  3. 不要再索引列上做任何操作(计算,函数,类型转换),会导致索引失效。
  4. 尽量使用覆盖索引。不要去select * ,尽量访问索引列。(覆盖索引)
  5. 使用 != , <>,is null, is not null,也无法使用索引。
  6. 使用like以通配符开头,会导致索引失效,可以使用通配符结尾。
  7. 字符串不加单引号索引失效。
  8. 少用or或者in,用他们查询的时候,不一定使用索引,mysql内部会对他们进行一个优化。尽量使用and进行范围查询。

30. 什么是协程,和线程进程有什么区别:

协程是比线程更小的一种执行单元,可以理解为是轻量级的线程,之所以说轻,其中一个方面就是协程持有栈比线程小很多。为什么提出协程?,因为线程切换的成本较高,而协程在这方面很有优势。
线程切换:

  1. 将CPU中的寄存器的信息存储起来,然后读入另一个线程的数据,花费一定的时间。
  2. CPU的高速缓存中的数据可能失效,需要重新加载。
  3. 线程的切换会涉及到用户态到内核态的切换,需要执行大量的指令。
    协程切换:
  4. 切换的时候,寄存器需要保存和加载的数据量比较小。
  5. 高速缓存可以有效的利用。
  6. 没有用户模式到内核模式的切换操作。
  7. 更有效率的调度,因为协程是非抢占式的,前一个协程执行完毕或者阻塞,才会让出CPU,而线程则一般使用了时间片的算法,会进行很多没有必要的切换。

你可能感兴趣的:(面试,框架,Java,面试,java,算法,操作系统)