相信很多同行小伙伴会因为许多原因想跳槽,不论是干得不开心还是想跳槽涨薪,在如此内卷的行业,我们都面临着“面试造火箭,上班拧螺丝”的局面,鉴于当前形势博主呕心沥血整理的干货满满的造火箭的技巧来了,本博主花费2个月时间,整理归纳java全生态知识体系常见面试题!总字数高达百万! 干货满满,每天更新,关注我,不迷路,用强大的归纳总结,全新全细致的讲解来留住各位猿友的关注,希望能够帮助各位猿友在应付面试笔试上!当然如有归纳总结错误之处请各位指出修正!如有侵权请联系博主QQ1062141499!
目录
10 什么是 java 序列化,如何实现 java 序列化?
12 如果你的Serializable类包含一个不可序列化的成员,会发生什么?你是如何解决的?
1 Java中IO 流分为几种?
按功能来分:输入流(input)、输出流(output)。
按类型来分:字节流、字符流。
字节流和字符流的区别是:字节流按 8 位传输以字节为单位输入输出数据,字符流按 16 位传输以字符为单位输入输出数据。
按数据流向:输入流和输出流
输入和输出都是从程序的角度来说的。
输入流:数据流向程序
输出流:数据从程序流出。
按处理单位:字节流和字符流
字节流:一次读入或读出是8位二进制
字符流:一次读入或读出是16位二进制
JDK 中后缀是 Stream 是字节流;后缀是 Reader,Writer 是字符流
按功能功能:节点流和处理流
节点流:直接与数据源相连,读入或写出
处理流:与节点流一块使用,在节点流的基础上,再套接一层
最根本的四大类:InputStream(字节输入流),OutputStream(字节输出流),Reader(字符输入流),Writer(字符输出流)
四大类的扩展,按处理单位区分
InputStream:FileInputStream、PipedInputStream、ByteArrayInputStream、BufferedInputstream、SequenceInputStream、DataInputStream、ObjectInputStream
OutputStream:FileOutputStream、PipedOutputStream、ByteArrayOutputStream、BufferedOutputStream、DataOutputStream、ObjectOutputStream、PrintStream
Reader:FileReader、PipedReader、CharArrayReader、BufferedReader、InputStreamReader
Writer:FileWriter、PipedWriter、CharArrayWriter、BufferedWriter、InputStreamWriter、PrintWriter
常用的流
对文件进行操作:FileInputStream(字节输入流)、FileOutputStream(字节输出流)、FileReader(字符输入流)、FileWriter(字符输出流)
对管道进行操作:PipedInputStream(字节输入流)、PipedOutStream(字节输出流)、PipedReader(字符输入流)、PipedWriter(字符输出流)
字节/字符数组:ByteArrayInputStream、ByteArrayOutputStream、CharArrayReader、CharArrayWriter
Buffered 缓冲流:BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter
字节转化成字符流:InputStreamReader、OutputStreamWriter
数据流:DataInputStream、DataOutputStream
打印流:PrintStream、PrintWriter
对象流:ObjectInputStream、ObjectOutputStream
序列化流:SequenceInputStream
2 字节流和字符流的区别
- Java 中的字节流处理的最基本单位为 1 个字节,通常用来处理二进制数据。字节流类 InputStream 和 OutputStream 类均为抽象类,代表了基本的输入字节流和输出字节流。
- Java 中的字符流处理的最基本的单元是 Unicode 代码单元(大小2字节),通常用来处理文本数据。
区别:
- 字节流操作的基本单元是字节;字符流操作的基本单元是字符
- 字节流默认不使用缓冲区;字符流使用缓冲区
- 字节流通常用于处理二进制数据,不支持直接读写字符;字符流通常用于处理文本数据
- 在读写文件需要对文本内容进行处理:按行处理、比较特定字符的时候一般会选择字符流;仅仅读写文件,不处理内容,一般选择字节流
特征:
- 以 stream 结尾都是字节流,reader 和 writer 结尾是字符流
- InputStream 是所有字节输入流的父类,OutputStream 是所有字节输出流的父类
- Reader 是字符输入流的父类,Writer 是字符输出流的父类
常见的字节流:
- 文件流:FileOutputStream 和 FileInputStream
- 缓冲流:BufferedOutputStream 和 BufferedInputStream
- 对象流:ObjectOutputStream 和 ObjectInputStream
常见的字符流:
- 字节转字符流:InputStreamReader 和 OutputStreamWriter
- 缓冲字符流:PrintWriter 和 BufferedReader
3 输入流和输出流的区别
- 输入输出的方向是针对程序而言,向程序中读入数据,就是输入流;从程序中向外写出数据,就是输出流
- 从磁盘、网络、键盘读到内存,就是输入流,用 InputStream 或 Reader
- 写到磁盘、网络、屏幕,都是输出流,用 OuputStream 或 Writer
4 节点流和处理流区别
按流的处理位置分类
节点流:可以从某节点读数据或向某节点写数据的流。如 FileInputStream
处理流:对已存在的流的连接和封装,实现更为丰富的流数据处理,处理流的构造方法必需其他的流对象参数。如 BufferedReader
5 字节流如何转为字符流
字节输入流转字符输入流通过 InputStreamReader 实现,该类的构造函数可以传入 InputStream 对象。
字节输出流转字符输出流通过 OutputStreamWriter 实现,该类的构造函数可以传入 OutputStream 对象。
6 缓冲流的优缺点
- 不带缓冲的流读取到一个字节或字符,就直接写出数据
- 带缓冲的流读取到一个字节或字符,先不输出,等达到了缓冲区的最大容量再一次性写出去
优点:减少了写出次数,提高了效率
缺点:接收端可能无法及时获取到数据
7 IO、BIO、NIO、AIO 有什么区别?
7.1 基本概念
IO:阻塞IO
BIO:Block IO,同步阻塞IO。服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器需要启动一个线程进行处理,如果这个链接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
NIO:New IO,同步非阻塞IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这需要用户进行不停的去询问。NIO的包括三个核心概念:缓冲区(Buffer)、通道(Channel)、选择器(Selector)。
AIO:Asynchronous IO,异步非阻塞AIO。最大的特性时具有异步能力,这种能力对socket与文件I/O都起作用。AIO其实是一种在读写操作结束之前允许进行其他操作的I/O处理。
可概括为:BIO是“我要读”,NIO是“我可以读了”,AIO是“我读完了”
BIO是一个连接一个线程。
NIO是一个请求一个线程。
AIO是一个有效请求一个线程。
先来个例子理解一下概念,以银行取款为例:
同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写);
异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API);
阻塞 : ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回);
非阻塞 : 柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)
7.2 区别
(1)NIO和IO的主要区别
IO | NIO |
面向流 | 面向缓冲区 |
阻塞 | 非阻塞 |
无 | 选择器 |
(2)BIO,NIO,AIO对比
| 同步阻塞IO(BIO) | 非阻塞IO(NIO) | 异步IO(AIO) |
客户端个数:IO线程 | 1:01 | M:1(1个IO线程处理多个客户端连接) | M:0(不需要启动额外的IO线程,被动回调) |
IO类型(阻塞) | 阻塞 | 非阻塞 | 非阻塞 |
IO类型(同步) | 同步 | 同步 | 异步 |
API使用难度 | 简单 | 简单 | 复杂 |
调试难度 | 简单 | 简单 | 复杂 |
可靠性 | 非常差 | 差 | 高 |
吞吐量 | 低 | 中 | 高 |
7.3 应用场景
BIO:适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4之前的唯一选择,但是程序简单直观容易理解。
NIO:适用于连接数目多且连接比较短(轻操作)的架构,比如聊推荐服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。
AIO:适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK1.7开始支持。
8 IO与NIO区别
NIO是为了弥补IO操作的不足而诞生的,NIO的一些新特性有:非阻塞I/O,选择器,缓冲以及管道。管道(Channel),缓冲(Buffer) ,选择器( Selector)是其主要特征。
概念解释
Channel——管道实际上就像传统IO中的流,到任何目的地(或来自任何地方)的所有数据都必须通过一个 Channel 对象。一个 Buffer 实质上是一个容器对象。
每一种基本 Java 类型都有一种缓冲区类型:
ByteBuffer——byte
CharBuffer——char
ShortBuffer——short
IntBuffer——int
LongBuffer——long
FloatBuffer——float
DoubleBuffer——double
Selector——选择器用于监听多个管道的事件,使用传统的阻塞IO时我们可以方便的知道什么时候可以进行读写,而使用非阻塞通道,我们需要一些方法来知道什么时候通道准备好了,选择器正是为这个需要而诞生的。
NIO和传统的IO有什么区别呢?
IO是面向流的,NIO是面向块(缓冲区)的。
IO面向流的操作一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。导致了数据的读取和写入效率不佳。
NIO面向块的操作在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多,同时数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。通俗来说,NIO采取了“预读”的方式,当你读取某一部分数据时,他就会猜测你下一步可能会读取的数据而预先缓冲下来。
IO是阻塞的,NIO是非阻塞的
对于传统的IO,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。
而对于NIO,使用一个线程发送读取数据请求,没有得到响应之前,线程是空闲的,此时线程可以去执行别的任务,而不是像IO中那样只能等待响应完成。
NIO和IO适用场景
NIO是为弥补传统IO的不足而诞生的,但是尺有所短寸有所长,NIO也有缺点,因为NIO是面向缓冲区的操作,每一次的数据处理都是对缓冲区进行的,那么就会有一个问题,在数据处理之前必须要判断缓冲区的数据是否完整或者已经读取完毕,如果没有,假设数据只读取了一部分,那么对不完整的数据处理没有任何意义。所以每次数据处理之前都要检测缓冲区数据。
那么NIO和IO各适用的场景是什么呢?
如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,例如聊天服务器,这时候用NIO处理数据可能是个很好的选择。
而如果只有少量的连接,而这些连接每次要发送大量的数据,这时候传统的IO更合适。使用哪种处理数据,需要在数据的响应等待时间和检查缓冲区数据的时间上作比较来权衡选择。
通俗解释,最后,对于NIO和传统IO
有一个网友讲的生动的例子:
以前的流总是堵塞的,一个线程只要对它进行操作,其它操作就会被堵塞,也就相当于水管没有阀门,你伸手接水的时候,不管水到了没有,你就都只能耗在接水(流)上。
nio的Channel的加入,相当于增加了水龙头(有阀门),虽然一个时刻也只能接一个水管的水,但依赖轮换策略,在水量不大的时候,各个水管里流出来的水,都可以得到妥善接纳,这个关键之处就是增加了一个接水工,也就是Selector,他负责协调,也就是看哪根水管有水了的话,在当前水管的水接到一定程度的时候,就切换一下:临时关上当前水龙头,试着打开另一个水龙头(看看有没有水)。当其他人需要用水的时候,不是直接去接水,而是事前提了一个水桶给接水工,这个水桶就是Buffer。也就是,其他人虽然也可能要等,但不会在现场等,而是回家等,可以做其它事去,水接满了,接水工会通知他们。
这其实也是非常接近当前社会分工细化的现实,也是统分利用现有资源达到并发效果的一种很经济的手段,而不是动不动就来个并行处理,虽然那样是最简单的,但也是最浪费资源的方式。
9 同步与异步、阻塞与非阻塞
同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。
而异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。我们可以用打电话和发短信来很好的比喻同步与异步操作。
阻塞与非阻塞主要是从 CPU 的消耗上来说的,阻塞就是 CPU 停下来等待一个慢的操作完成 CPU 才接着完成其它的事。
非阻塞就是在这个慢的操作在执行时 CPU 去干其它别的事,等这个慢的操作完成时,CPU 再接着完成后续的操作。虽然表面上看非阻塞的方式可以明显的提高 CPU 的利用率,但是也带了另外一种后果就是系统的线程切换增加。增加的 CPU 使用时间能不能补偿系统的切换成本需要好好评估。
如果你想吃一份宫保鸡丁盖饭,
同步阻塞:你到饭馆点餐,然后在那儿等着,还要一边喊:好了没啊!
同步非阻塞:在饭馆点完餐,就去遛狗了。不过遛一会儿,就回饭馆喊一声:好了没啊!
异步阻塞:遛狗的时候,接到饭馆电话,说饭做好了,让您亲自去拿。
异步非阻塞:饭馆打电话说,我们知道您的位置,一会儿给你送过来,安心遛狗就可以了。
在弄清楚以上几个问题之前,我们首先得明白什么是同步、异步、阻塞、非阻塞,只有这几个概念单个理解清楚了,然后再组合起来理解,就比较容易了。
1 同步和异步是针对应用程序和内核的交互而言的。
2 阻塞和非阻塞是针对于进程在访问数据时,根据I/O操作的就绪状态来采取的不同方式,说白了是一种读取或写入操作函数的实现方式。阻塞方式下,读取或写入函数将一直等待,而非阻塞方式下,读取或写入函数会立即返回一个状态值。
以上描述可以总结为一句简短的话:同步和异步是目的,阻塞和非阻塞是实现方式。
1 同步:指的是用户进程触发I/O操作并等待或轮询地去查看I/O操作是否就绪。自己上街买衣服,自己亲自干这件事,别的事干不了。
2 异步:异步是指用户进程触发I/O操作以后便开始做自己的事情,而当I/O操作完成的时候会得到“I/O完毕”的通知(异步的特点就是通知)。告诉朋友自己合适衣服的尺寸、颜色、款式,委托朋友去买,然后自己可以去干别的事(使用异步I/O时,Java将I/O读写委托给OS处理,需要将数据缓冲区地址和大小传给OS)。
3 阻塞:所谓阻塞方式的意思是指,当试图对该文件描述符进行读写时,如果当时没有东西可读,或暂时不可写,程序就进入等待状态,直到有东西可读或可写为止。去地铁站充值,发现这个时候充值员不在(可能上厕所去了),然后我们就在原地等待,一直等到充值员回来为止。
4 非阻塞:非阻塞状态下,如果没有东西可读,或不可写,读写函数马上返回,而不会等待。在银行里办业务时,领取一张小票,之后我们可以玩手机,或与别人聊聊天,当轮到我们时,银行的喇叭会通知,这时候我们就可以去办业务了。
一个I/O操作其实分成了两个步骤:发起I/O请求和实际的I/O操作。
同步I/O和异步I/O的区别就在于第二个步骤是否阻塞,如果实际的I/O读写阻塞请求进程,那么就是同步I/O。
阻塞I/O和非阻塞I/O的区别在于第一步,发起I/O请求是否会被阻塞,如果阻塞直到I/O完成,那么就是传统的阻塞I/O,如果不阻塞,那么就是非阻塞I/O。
同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发I/O操作并等待或轮询地去查看I/O操作是否就绪,而异步是指用户进程触发I/O操作以后便开始做自己的事情,而当I/O操作完成的时候会得到I/O完毕的通知。
阻塞和非阻塞是针对于进程在访问数据时,根据I/O操作的就绪状态来采取的不同方式,说白了是读取或写入操作函数的一种实现方式,阻塞方式下读写函数一直等待,而非阻塞方式下,读写函数会立即返回一个状态值。
所以,I/O操作可以分为3类:同步阻塞(即早期的BIO操作)、同步非阻塞(NIO)、异步非阻塞(AIO)。
同步阻塞(BIO):
在此种方式下,用户进程在发起一个I/O操作后,必须等待I/O操作的完成,只有当真正完成了I/O操作以后,用户进程才能运行。JAVA传统的I/O模式属于此种方式。
同步非阻塞(NIO):
在此种方式下,用户进程发起一个I/O操作以后便可返回做其他事情,但是用户进程需要时不时地询问I/O操作是否就绪,这就要求用户周期性地去询问,从而引入不必要的CPU资源浪费。目前JAVA的NIO就属于同步非阻塞I/O。
异步非阻塞(AIO):
此种方式下是指用户进程发起一个I/O操作以后,不等待内核I/O操作的完成,内核完成I/O操作以后会通知用户进程。
同步阻塞I/O(JAVA BIO):
同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器就需要启动一个线程进行处理,如果这个连接不做任何事情就会造成不必要的线程开销。当然可以通过线程池机制改善。
同步非阻塞I/O(JAVA NIO):
同步非阻塞,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。用户进程需要时不时地询问I/O操作是否就绪,这就要求用户进程周期性地去询问。
异步阻塞I/O (JAVA NIO):
此种方式下是指应用发起一个I/O操作以后,不等待I/O操作的完成,内核完成I/O操作以后会通知应用程序,这其实就是同步和异步最关键的区别,同步必须等待或主动地去询问I/O是否完成,那么为什么说是阻塞的呢?因为此时是通过select系统调用来完成的,而select函数本身的实现方式是阻塞的,而采用select函数有个好处就是它可以同时监听多个文件句柄(如果从 UNP 的角度看,select属于同步操作,因为select之后,进程还需要读写数据),从而提高系统的并发性!
异步非阻塞I/O(JAVA AIO(NIO.2)):
此种方式下,用户进程之需要发起一个I/O操作然后立即返回,等I/O操作真正地完成以后,应用程序会得到I/O操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的I/O读写操作,因为真正的I/O读写操作已经由内核完成了。
10 什么是 java 序列化,如何实现 java 序列化?
序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。序列化是为了解决在对对象流进行读写操作时所引发的问题。
序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的,然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(对象流)对象,接着,使用ObjectOutputStream对象的writeObject(Objectobj)方法就可以将参数为obj的对象写出(即保存其状态),要恢复的话则用输入流。
11 什么是 Java 序列化?什么情况下需要序列化?
序列化:将 Java 对象转换成字节流的过程。
反序列化:将字节流转换成 Java 对象的过程
Java 序列化是为了保存各种对象在内存中的状态,并且可以把保存的对象状态再读出来。
以下情况需要使用 Java 序列化:
- 想把的内存中的对象状态保存到一个文件中或者数据库中时候;
- 想用套接字在网络上传送对象的时候;
- 想通过RMI(远程方法调用)传输对象的时候。
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
/**
* 测试序列化,反序列化
* @author lijun
* @date 2020-06-30 09:31:22
*/
public class TestSerializable implements Serializable {
private static final long serialVersionUID = 5887391604554532906L;
private int id;
private String name;
public TestSerializable(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "TestSerializable [id=" + id + ", name=" + name + "]";
}
@SuppressWarnings("resource")
public static void main(String[] args) throws IOException, ClassNotFoundException {
//序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("TestSerializable.obj"));
oos.writeObject("测试序列化");
oos.writeObject(618);
TestSerializable test = new TestSerializable(1, "Lee");
oos.writeObject(test);
//反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("TestSerializable.obj"));
System.out.println((String)ois.readObject());
System.out.println((Integer)ois.readObject());
System.out.println((TestSerializable)ois.readObject());
}
}
测试序列化
618
TestSerializable [id=1, name=Lee]
11 如何将一个 java 对象序列化到文件里
在 java 中能够被序列化的类必须先实现 Serializable 接口,该接口没有任何抽象方法只是起到一个标记作用。
//对象输出流
ObjectOutputStream objectOutputStream =
new ObjectOutputStream(new FileOutputStream(new File("D://obj")));
objectOutputStream.writeObject(new User("lijun", 100));
objectOutputStream.close();
//对象输入流
ObjectInputStream objectInputStream =
new ObjectInputStream(new FileInputStream(new File("D://obj")));
User user = (User) objectInputStream.readObject();
System.out.println(user);
objectInputStream.close();
12 如果你的Serializable类包含一个不可序列化的成员,会发生什么?你是如何解决的?
任何序列化该类的尝试都会因NotSerializableException而失败,但这可以通过在 Java中 为 static 设置瞬态(trancient)变量来轻松解决。
Java 序列化相关的常见问题
Java 序列化是一个重要概念, 但它很少用作持久性解决方案, 开发人员大多忽略了 Java 序列化 API。根据我的经验, Java 序列化在任何 Java核心内容面试中都是一个相当重要的话题, 在几乎所有的网面试中, 我都遇到过一两个 Java 序列化问题, 我看过一次面试, 在问几个关于序列化的问题之后候选人开始感到不自在, 因为缺乏这方面的经验。
他们不知道如何在 Java 中序列化对象, 或者他们不熟悉任何 Java 示例来解释序列化, 忘记了诸如序列化在 Java 中如何工作, 什么是标记接口, 标记接口的目的是什么, 瞬态变量和可变变量之间的差异, 可序列化接口具有多少种方法, 在 Java 中,Serializable 和 Externalizable 有什么区别, 或者在引入注解之后, 为什么不用 @Serializable 注解或替换 Serializalbe 接口。
关于Java序列化的10个面试问题
大多数商业项目使用数据库或内存映射文件或只是普通文件, 来满足持久性要求, 只有很少的项目依赖于 Java 中的序列化过程。无论如何,这篇文章不是 Java 序列化教程或如何序列化在 Java 的对象, 但有关序列化机制和序列化 API 的面试问题, 这是值得去任何 Java 面试前先看看以免让一些未知的内容惊到自己。
对于那些不熟悉 Java 序列化的人, Java 序列化是用来通过将对象的状态存储到带有.ser扩展名的文件来序列化 Java 中的对象的过程, 并且可以通过这个文件恢复重建 Java对象状态, 这个逆过程称为 deserialization。
什么是 Java 序列化
序列化是把对象改成可以存到磁盘或通过网络发送到其他运行中的 Java 虚拟机的二进制格式的过程, 并可以通过反序列化恢复对象状态. Java 序列化API给开发人员提供了一个标准机制, 通过 java.io.Serializable 和 java.io.Externalizable 接口, ObjectInputStream 及ObjectOutputStream 处理对象序列化. Java 程序员可自由选择基于类结构的标准序列化或是他们自定义的二进制格式, 通常认为后者才是最佳实践, 因为序列化的二进制文件格式成为类输出 API的一部分, 可能破坏 Java 中私有和包可见的属性的封装。
如何序列化
让 Java 中的类可以序列化很简单. 你的 Java 类只需要实现 java.io.Serializable 接口, JVM 就会把 Object 对象按默认格式序列化. 让一个类是可序列化的需要有意为之. 类可序列会可能为是一个长期代价, 可能会因此而限制你修改或改变其实现. 当你通过实现添加接口来更改类的结构时, 添加或删除任何字段可能会破坏默认序列化, 这可以通过自定义二进制格式使不兼容的可能性最小化, 但仍需要大量的努力来确保向后兼容性。序列化如何限制你更改类的能力的一个示例是 SerialVersionUID。
如果不显式声明 SerialVersionUID, 则 JVM 会根据类结构生成其结构, 该结构依赖于类实现接口和可能更改的其他几个因素。假设你新版本的类文件实现的另一个接口, JVM 将生成一个不同的 SerialVersionUID 的, 当你尝试加载旧版本的程序序列化的旧对象时, 你将获得无效类异常 InvalidClassException。
问题 1) Java 中的可序列化接口和可外部接口之间的区别是什么?
这是 Java 序列化访谈中最常问的问题。Externalizable 给我们提供 writeExternal() 和 readExternal() 方法, 这让我们灵活地控制 Java 序列化机制, 而不是依赖于 Java 的默认序列化。正确实现 Externalizable 接口可以显著提高应用程序的性能。
问题 2) 可序列化的方法有多少?如果没有方法,那么可序列化接口的用途是什么?
可序列化 Serializalbe 接口存在于java.io包中,构成了 Java 序列化机制的核心。它没有任何方法, 在 Java 中也称为标记接口。当类实现 java.io.Serializable 接口时, 它将在 Java 中变得可序列化, 并指示编译器使用 Java 序列化机制序列化此对象。
问题 3) 什么是 serialVersionUID ?如果你不定义这个, 会发生什么?
我最喜欢的关于Java序列化的问题面试问题之一。serialVersionUID 是一个 private static final long 型 ID, 当它被印在对象上时, 它通常是对象的哈希码,你可以使用 serialver 这个 JDK 工具来查看序列化对象的 serialVersionUID。SerialVerionUID 用于对象的版本控制。也可以在类文件中指定 serialVersionUID。不指定 serialVersionUID的后果是,当你添加或修改类中的任何字段时, 则已序列化类将无法恢复, 因为为新类和旧序列化对象生成的 serialVersionUID 将有所不同。Java 序列化过程依赖于正确的序列化对象恢复状态的, ,并在序列化对象序列版本不匹配的情况下引发 java.io.InvalidClassException 无效类异常
问题 4) 序列化时,你希望某些成员不要序列化?你如何实现它?
另一个经常被问到的序列化面试问题。这也是一些时候也问, 如什么是瞬态 trasient 变量, 瞬态和静态变量会不会得到序列化等,所以,如果你不希望任何字段是对象的状态的一部分, 然后声明它静态或瞬态根据你的需要, 这样就不会是在 Java 序列化过程中被包含在内。问题
5) 如果类中的一个成员未实现可序列化接口, 会发生什么情况?
关于Java序列化过程的一个简单问题。如果尝试序列化实现可序列化的类的对象,但该对象包含对不可序列化类的引用,则在运行时将引发不可序列化异常 NotSerializableException, 这就是为什么我始终将一个可序列化警报(在我的代码注释部分中), 代码注释最佳实践之一, 指示开发人员记住这一事实, 在可序列化类中添加新字段时要注意。
问题 6) 如果类是可序列化的, 但其超类不是, 则反序列化后从超级类继承的实例变量的状态如何?
Java 序列化过程仅在对象层次都是可序列化结构中继续, 即实现 Java 中的可序列化接口, 并且从超级类继承的实例变量的值将通过调用构造函数初始化, 在反序列化过程中不可序列化的超级类。一旦构造函数链接将启动, 就不可能停止, 因此, 即使层次结构中较高的类实现可序列化接口, 也将执行构造函数。正如你从陈述中看到的, 这个序列化面试问题看起来非常棘手和有难度, 但如果你熟悉关键概念, 则并不难。
问题 7) 是否可以自定义序列化过程, 或者是否可以覆盖 Java 中的默认序列化过程?
答案是肯定的, 你可以。我们都知道,对于序列化一个对象需调用ObjectOutputStream.writeObject(saveThisObject), 并用 ObjectInputStream.readObject() 读取对象, 但 Java 虚拟机为你提供的还有一件事, 是定义这两个方法。如果在类中定义这两种方法, 则 JVM 将调用这两种方法, 而不是应用默认序列化机制。你可以在此处通过执行任何类型的预处理或后处理任务来自定义对象序列化和反序列化的行为。
需要注意的重要一点是要声明这些方法为私有方法, 以避免被继承、重写或重载。由于只有 Java 虚拟机可以调用类的私有方法, 你的类的完整性会得到保留, 并且 Java 序列化将正常工作。在我看来, 这是在任何 Java 序列化面试中可以问的最好问题之一, 一个很好的后续问题是, 为什么要为你的对象提供自定义序列化表单?
问题 8) 假设新类的超级类实现可序列化接口, 如何避免新类被序列化?
在 Java 序列化中一个棘手的面试问题。如果类的 Super 类已经在 Java 中实现了可序列化接口, 那么它在 Java 中已经可以序列化, 因为你不能取消接口, 它不可能真正使它无法序列化类, 但是有一种方法可以避免新类序列化。为了避免 Java 序列化,你需要在类中实现 writeObject() 和 readObject() 方法, 并且需要从该方法引发不序列化异常NotSerializableException。这是自定义 Java 序列化过程的另一个好处, 如上述序列化面试问题中所述, 并且通常随着面试进度, s它作为后续问题提出。
问题 9) 在 Java 中的序列化和反序列化过程中使用哪些方法?
这是很常见的面试问题, 在序列化基本上面试官试图知道: 你是否熟悉 readObject() 的用法、 writeObject()、readExternal() 和 writeExternal()。Java 序列化由java.io.ObjectOutputStream类完成。该类是一个筛选器流, 它封装在较低级别的字节流中, 以处理序列化机制。要通过序列化机制存储任何对象, 我们调用 ObjectOutputStream.writeObject(savethisobject), 并反序列化该对象, 我们称之为 ObjectInputStream.readObject()方法。调用以 writeObject() 方法在 java 中触发序列化过程。关于 readObject() 方法, 需要注意的一点很重要一点是, 它用于从持久性读取字节, 并从这些字节创建对象, 并返回一个对象, 该对象需要类型强制转换为正确的类型。
问题 10) 假设你有一个类,它序列化并存储在持久性中, 然后修改了该类以添加新字段。如果对已序列化的对象进行反序列化, 会发生什么情况?
这取决于类是否具有其自己的 serialVersionUID。正如我们从上面的问题知道, 如果我们不提供 serialVersionUID, 则 Java 编译器将生成它, 通常它等于对象的哈希代码。通过添加任何新字段, 有可能为该类新版本生成的新 serialVersionUID 与已序列化的对象不同, 在这种情况下, Java 序列化 API 将引发 java.io.InvalidClassException, 因此建议在代码中拥有自己的 serialVersionUID, 并确保在单个类中始终保持不变。
11) Java序列化机制中的兼容更改和不兼容更改是什么?
真正的挑战在于通过添加任何字段、方法或删除任何字段或方法来更改类结构, 方法是使用已序列化的对象。根据 Java 序列化规范, 添加任何字段或方法都面临兼容的更改和更改类层次结构或取消实现的可序列化接口, 有些接口在非兼容更改下。对于兼容和非兼容更改的完整列表, 我建议阅读 Java 序列化规范。
12) 我们可以通过网络传输一个序列化的对象吗?
是的 ,你可以通过网络传输序列化对象, 因为 Java 序列化对象仍以字节的形式保留, 字节可以通过网络发送。你还可以将序列化对象存储在磁盘或数据库中作为 Blob。
13) 在 Java 序列化期间,哪些变量未序列化?
这个问题问得不同, 但目的还是一样的, Java开发人员是否知道静态和瞬态变量的细节。由于静态变量属于类, 而不是对象, 因此它们不是对象状态的一部分, 因此在 Java 序列化过程中不会保存它们。由于 Java 序列化仅保留对象的状态,而不是对象本身。瞬态变量也不包含在 Java 序列化过程中, 并且不是对象的序列化状态的一部分。在提出这个问题之后,面试官会询问后续内容, 如果你不存储这些变量的值, 那么一旦对这些对象进行反序列化并重新创建这些变量, 这些变量的价值是多少?这是你们要考虑的。
13 如何读取文件a.txt中第10个字节?
FileInputStream in = new FileInputStream("a.txt");
in.skip(9);//skip(long n) 方法,调过文件 n 个字节数
int b = in.read();
14 如何将字符串写入文件?
String cx = "我是牛蒡子";
FileOutputStream fos = null;
try {
fos = new FileOutputStream("E:/lee.txt");
fos.write(cx.getBytes());//注意字符串编码
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
sd ld