尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Java IO核心知识点梳理:字节流、字符流、文件读写与NIO

Java IO核心知识点梳理:字节流、字符流、文件读写与NIO 搞Java开发绕不开输入输出IO不管是读写文件、处理控制台输入还是搞网络编程、对接HTTP接口底层全是流在流转。很多人刚开始学Java时对System.out.println()用得贼溜但一碰到文件读写、字节流和字符流的区别、编码乱码问题就开始犯迷糊。这篇博文我就把这些年实际开发里用Java输入输出的经验、踩过的坑和排查思路整理出来从基础流分类到文件读写实操、编码处理再到NIO和序列化最后补充面试高频考点希望对正在学习Java或者准备面试的朋友有帮助。1. Java IO的整体架构与关键流类的选型思路1.1 字节流与字符流的分工Java的IO体系核心围绕两个抽象基类展开字节流以InputStream和OutputStream为根字符流以Reader和Writer为根。这个设计是Java老前辈们从JDK 1.0就开始规划的到今天依然适用。字节流处理的是原始二进制数据读取的单位是一个字节适合读取图片、音频、视频、压缩包这类非文本文件。字符流则在底层字节流之上做了编码解码读取的单位是一个字符char适合处理纯文本文档并且需要指定合适的字符编码才能避免乱码。实际开发中我经常遇到一种情况有人看到InputStreamReader和BufferedReader就不知道它们到底该配合谁用。这里要记住一条规律——字符流的底层一定包装了一个字节流因为数据在磁盘和网络上传输时永远是字节形态字符流只是在内存侧额外加了一层“翻译”。我们看一个典型的字符流构造链FileInputStream inputStream new FileInputStream(test.txt); InputStreamReader reader new InputStreamReader(inputStream, StandardCharsets.UTF_8); BufferedReader bufferedReader new BufferedReader(reader);FileInputStream负责从文件读字节InputStreamReader负责把字节翻译成字符BufferedReader负责提供缓冲并支持readLine()这种按行读取的便捷方法。拆开看每层各司其职组合起来就是一段完整的字符读取管道。1.2 流的包装模式与实际选型Java IO使用的是装饰器模式这也解释了为什么我们总看到流对象一层包一层。BufferedInputStream可以包装FileInputStreamBufferedOutputStream可以包装FileOutputStream同理BufferedReader可以包装任何Reader子类。装饰器模式的好处是可以在不改变原有类结构的情况下动态增强功能。比如你写了一个输出流想让它的写入性能更好不需要修改原类在外面套上BufferedOutputStream即可如果还想要自动刷缓冲、支持按行写再套一层PrintWriter。选型上有几条实战经验值得记录高频写入小文本数据优先用BufferedWriter包装FileWriter或者在OutputStreamWriter外层加缓冲减少频繁的底层系统调用。写入二进制大块数据建议直接用FileOutputStream配合BufferedOutputStream一次性写大数组比逐字节写高效得多。读写文本且需要按行处理BufferedReader.readLine()是性价比最高的API条件允许的话再用Java 8的Files.lines()结合Stream API。写入纯文本且需要打印格式PrintWriter提供了println()和printf()用起来比手动拼接字符串再写要顺手得多。包装链一旦搭起来就必须注意关闭顺序和资源释放问题。最外层流的close()方法会触发底层流的关闭但如果手动关掉了底层流再对外层流调用写入就会抛IOException。后面我会专门讲怎么用try-with-resources优雅解决。2. 文件读写实操从字节到字符的完整落地2.1 文件读写的标准姿势文件读写是Java IO里最基础也是最高频的场景。我见过不少初学者抄网上的代码用几层裸奔的FileInputStream去读文件也没加缓冲也没处理编码结果在大文件或非ASCII字符上就翻车了。一个我日常比较常用的标准读文件模板长这样// Java 7 推荐的写法try-with-resources 自动关闭资源 long start System.currentTimeMillis(); try (BufferedReader reader new BufferedReader( new InputStreamReader( new FileInputStream(data.log), StandardCharsets.UTF_8))) { String line; while ((line reader.readLine()) ! null) { // 处理每一行 System.out.println(line); } } catch (IOException e) { e.printStackTrace(); } long cost System.currentTimeMillis() - start;这段代码里有两个特别值得注意的细节第一try后面的括号里声明了BufferedReader代码块执行结束或者抛异常时资源会被自动调用close()不用手动写finally块第二读取循环条件用的是(line reader.readLine()) ! null把“读一行”和“判断是否结束”合并成了一个表达式这是很地道的Java写法。写文件的模板类似try (BufferedWriter writer new BufferedWriter( new OutputStreamWriter( new FileOutputStream(output.txt), StandardCharsets.UTF_8))) { writer.write(第一行内容); writer.newLine(); writer.write(第二行内容); writer.flush(); // 或者 writer.write(多行内容\n); } catch (IOException e) { e.printStackTrace(); }这里提醒一下flush()的语义——它把缓冲区的数据强制推到目的地但是不关闭流。如果你在BufferedWriter里写入大量内容却忘记flush()程序结束时会因为流未关闭而丢失缓冲区里残存的数据。正确习惯是要么在循环结束后显式flush()要么直接依赖try-with-resources的隐式关闭close()内部会先flush()。2.2 字符编码为什么中文会乱码编码问题可以排到Java IO坑榜第一名。根源在于默认字符集的不可控——System.getProperty(file.encoding)返回的值在不同操作系统、不同JDK版本、甚至同一个系统的不同启动方式下都可能不同。早年的JDK在Windows中文系统上默认是GBK在Linux上默认是UTF-8Java 18之后JDK才正式把UTF-8作为默认字符集。我曾经遇到过这样一个线上事故某服务在Windows本地启动时一切正常打包发到Linux服务器后读取配置文件时中文全部变成乱码服务启动直接失败。后来排查发现本地代码里用了new FileReader(config.properties)这个构造方法没有显式指定编码默认按系统的file.encoding来解码而生产环境服务器的file.encoding发生了变化中文内容自然就解析错了。处理编码问题最稳妥的原则是任何涉及文本与字节互转的场景都必须显式指定字符集。具体做法就是// 推荐显式指定 UTF-8 new InputStreamReader(new FileInputStream(config.properties), StandardCharsets.UTF_8); // 不推荐依赖系统默认编码 new FileReader(config.properties);同理在写文件时也要做对应替换。另一个常被忽略的编码问题是String与byte[]互转String content 你好Java; // 推荐显式指定 UTF-8 byte[] bytes content.getBytes(StandardCharsets.UTF_8); // 不推荐content.getBytes()默认编码可能随系统环境变化 String restored new String(bytes, StandardCharsets.UTF_8);很多人在HTTP请求参数解析、数据库字段读写、消息队列传输的场景下遇到中文乱码都是因为在这类代码里没指定编码。编码守恒是个非常重要的心智模型一个字符串在字节形态流动时编码和解码必须用同一套字符集任何一环脱节乱码就会出现。2.3 文件复制的最佳方案文件复制是IO里最常见的“练手题”但也最容易写出低性能代码。初级写法是循环单字节读写经过层层系统调用性能极差中级写法是申请一个大字节数组做缓冲一次性读一批写一批高级做法是使用FileChannel的transferTo()充分利用操作系统层面的零拷贝能力。// 推荐使用 FileChannel.transferTo 做高效文件复制 try (FileChannel in new FileInputStream(source.bin).getChannel(); FileChannel out new FileOutputStream(target.bin).getChannel()) { long position 0; long size in.size(); while (position size) { position in.transferTo(position, size - position, out); } }transferTo()的实现底层在Linux上会调用sendfile系统调用数据不会在用户态和内核态之间来回复制因此拷贝大文件时性能优势明显。但注意它返回的是实际传输字节数通常一次调用不一定能传完所有数据所以需要循环累加position直到全部传完。这个小细节网上很多示例代码都没提我在实际项目中确实遇到过单次transferTo()没传完导致文件损坏的情况。3. 控制台输入与网络输出两个容易出事的场景3.1 控制台输入Scanner与System.in的注意点控制台输入看似简单但System.in本质上是一个字节流直接被Scanner使用时可能隐藏不少坑。最常见的坑是Scanner配合next()和nextLine()混用导致的阻塞或者丢数据。比如下面这段代码Scanner scanner new Scanner(System.in); System.out.print(请输入一个整数); int num scanner.nextInt(); System.out.print(请输入一行字符串); String line scanner.nextLine(); // 这里拿到的很可能是空字符串原因不在nextLine()本身而在nextInt()不会消费输入行尾的换行符。用户输入数字并回车后缓冲区内还残留着\r\nnextLine()一上来就读到了空行。解决办法是在nextInt()之后补一个scanner.nextLine()把残留换行符吞掉或者直接用nextLine()读整行再解析。还有一个阻塞问题Scanner.hasNext()在输入结束时不会返回false而是一直阻塞等待用户输入。你在IDE里运行程序发现明明输入完了程序却不退出多半就是这个原因。如果你确实需要支持“输入EOF才结束”的逻辑可以改成用BufferedReader配合readLine()文件末尾时它返回nulltry (BufferedReader reader new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8))) { String line; while ((line reader.readLine()) ! null) { // 处理用户输入 System.out.println(echo: line); } }这个写法在刷题网站和在线评测环境里尤其重要因为它天然支持文件重定向输入。3.2 网络传输中的IOTCP与HTTP场景的读写要点网络IO中的输入输出是Java后端开发的重灾区。先说HttpURLConnection或者你直接上Apache HttpClient、OkHttp的场景我们经常需要构造一个带JSON消息体的POST请求这个过程中最容易犯的错误是编码和换行符问题。URL url new URL(https://api.example.com/data); HttpURLConnection connection (HttpURLConnection) url.openConnection(); connection.setRequestMethod(POST); connection.setDoOutput(true); connection.setRequestProperty(Content-Type, application/json; charsetutf-8); String payload {\name\:\测试\,\age\:30}; try (OutputStream os connection.getOutputStream()) { os.write(payload.getBytes(StandardCharsets.UTF_8)); } int responseCode connection.getResponseCode(); try (BufferedReader reader new BufferedReader( new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) { String line; StringBuilder response new StringBuilder(); while ((line reader.readLine()) ! null) { response.append(line); } System.out.println(response); }这其中的核心点是payload.getBytes()必须指定UTF-8InputStreamReader解码响应体时必须指定UTF-8。如果两端编码不一致轻则中文乱码重则请求被服务端直接拒绝。再说TCP编程。用Socket做底层读写时有一个容易被忽略的TCP粘包和半包问题。如果自定义协议没有设定消息边界接收方可能会在两次发送之间收到黏在一起的数据包。实战中如果做简单工具最省心的做法是采用“长度字段内容”的协议设计或者直接每行文本作为一个消息单元用Readline方式解析。Java的ObjectOutputStream在TCP传输里默认自带了一些头部信息但传输大对象时效率并不理想后续序列化部分我会展开。网络IO还有一个非常重要的性能点TCP_NODELAY。如果业务是高频小包写入关闭Nagle算法能明显降低延迟Socket socket new Socket(); socket.setTcpNoDelay(true);不过大多数应用层的框架Netty、Tomcat等已经帮你处理了这些细节你要做的就是搞懂底层的语义避免在自研协议时犯低级错误。3.3 怎么避免Stream被反复打开导致的性能损耗连接池和IO复用是现代后端架构的核心思想。文件流也一样如果每个请求都重新打开关闭文件开销是很大的。在处理日志、配置文件热加载、通用数据字典这类场景我常采用两种优化策略。第一类是缓存流对象把BufferedReader或RandomAccessFile的实例放在并发容器里复用同一个打开的流避免反复open/close。第二类是批量合并IO比如把多个小的写操作合并成一个大的write(byte[], int, int)减少系统调用的次数。但如果只是做一次性读取完全不建议手动做缓存优化直接使用标准API并且让try-with-resources管理生命周期即可。过早优化是万恶之源这句话在IO领域同样适用。4. Java NIO与Files工具类新一代IO实践4.1 Buffer、Channel基础概念NIONon-blocking IO是Java 1.4引入的一套全新IO模式核心组件是Channel、Buffer和Selector。很多初学者学NIO时被三个概念搞昏头Channel负责连接数据源与目标Buffer负责存储数据Selector负责监听多个Channel的事件。只要记住一条NIO是面向缓冲的Buffer-Oriented传统IO是面向流的Stream-Oriented。传统IO按字节顺序读取无法前后移动NIO则允许你在Buffer中随机读写位置。使用Buffer最核心的四个操作是flip()把写模式切换为读模式。因为在写数据时position不断增加切换为读模式时需要把limit设置为当前position再把position归零。clear()清空后准备写入position0、limitcapacity。compact()只清除已经读过的部分未读数据移到Buffer头部再准备写入。rewind()重置position0用于重复读取。ByteBuffer buffer ByteBuffer.allocate(1024); buffer.put(hello.getBytes(StandardCharsets.UTF_8)); buffer.flip(); // 切换为读模式 byte[] dst new byte[buffer.remaining()]; buffer.get(dst); System.out.println(new String(dst, StandardCharsets.UTF_8)); // helloremaining()返回的是limit - position也就是当前还有多少字节可读。一遍代码走下来对Buffer的读写姿势就清晰了。4.2 Files与Paths的舒适区Java 7加入Files工具类后日常文件IO的编码难度大幅下降。不需要再去手动包装各种流很多常规操作一行搞定// 读取所有内容到字符串适合小文件 String content Files.readString(Paths.get(test.txt), StandardCharsets.UTF_8); // 写入字符串适合小文件 Files.writeString(Paths.get(test.txt), 写入内容, StandardCharsets.UTF_8); // 按行读取 ListString lines Files.readAllLines(Paths.get(test.txt), StandardCharsets.UTF_8); // 移动/重命名文件 Files.move(Paths.get(a.txt), Paths.get(b.txt), StandardCopyOption.REPLACE_EXISTING);Files.readString()是Java 11加入的便捷方法。如果是读取超大文件比如好几GB的日志就不要用readAllLines()了因为会导致内存占用飙高应该改用Files.lines()返回的Stream并配合limit或filter做流式处理try (StreamString lines Files.lines(Paths.get(access.log), StandardCharsets.UTF_8)) { lines.filter(line - line.contains(ERROR)) .limit(100) .forEach(System.out::println); }注意Files.lines()返回的Stream是持有底层文件句柄的必须在一个try-with-resources里使用不然文件句柄会一直释放不掉。这个坑我在日志分析脚本里踩过排查了半天才发现是Stream没关导致句柄堆积到系统上限。4.3 NIO与网络的高性能组合SocketChannelSelector是构建高性能非阻塞网络应用的基础Netty的底层就在这上面搭建。NIO的非阻塞模型允许一个线程处理成百上千个连接适合高并发场景。但如果你只是写工具脚本或者业务QPS不高完全没有必要上手自研NIO服务器直接用传统的ServerSocket加线程池明显更清晰。如果要用NIO做简单客户端代码长这样SocketChannel channel SocketChannel.open(new InetSocketAddress(localhost, 8080)); ByteBuffer buffer ByteBuffer.allocate(1024); buffer.put(GET / HTTP/1.1\r\nHost: localhost\r\n\r\n.getBytes(StandardCharsets.UTF_8)); buffer.flip(); channel.write(buffer); ByteBuffer readBuf ByteBuffer.allocate(1024); int bytesRead channel.read(readBuf); if (bytesRead 0) { readBuf.flip(); byte[] data new byte[readBuf.remaining()]; readBuf.get(data); System.out.println(new String(data, StandardCharsets.UTF_8)); } channel.close();注意channel.read()是异步的可能返回0当前没有数据或-1连接关闭。在非阻塞模式下你需要配合Selector的OP_READ事件才能优雅处理数据到达的时机。这块水比较深如果感兴趣建议一步步从ServerSocketChannelSelector模型开始搭建。5. 对象序列化把对象变成字节流再变回来5.1 ObjectOutputStream/ObjectInputStream的使用序列化是Java输入输出里一个非常实用的分支场景将Java对象转换成字节流方便存储到文件或通过网络传输反序列化则是把字节流还原为Java对象。基础用法很直接// 序列化对象到文件 try (ObjectOutputStream oos new ObjectOutputStream(new FileOutputStream(user.obj))) { User user new User(张三, 30, 123456); oos.writeObject(user); } // 反序列化 try (ObjectInputStream ois new ObjectInputStream(new FileInputStream(user.obj))) { User user (User) ois.readObject(); System.out.println(user.getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); }要序列化的类必须实现java.io.Serializable接口这个接口是一个标记接口不含任何方法。类中的所有字段默认都会被序列化但被transient修饰的字段不会被序列化。密码、Token这种敏感信息或者像Thread对象这种不可序列化的引用类型都应该标记transient。这里有个安全细节经验反序列化时一定要校验输入数据的来源。ObjectInputStream默认机制允许攻击者构造恶意字节码流可能存在安全风险。如果数据来源不可信建议优先使用JSON格式Jackson、Gson而不是原生序列化。5.2 serialVersionUID的坑这个字段几乎是Java序列化中最经典的坑。如果你定义了一个Serializable类但没显式声明serialVersionUIDJDK会基于类结构自动生成一个版本号。一旦你修改了类的字段比如加了字段或者改了类型自动生成的版本号就会变化反序列化时就会抛InvalidClassException。正确的做法是始终显式声明public class User implements Serializable { private static final long serialVersionUID 1L; private String name; private int age; }指定一个固定的serialVersionUID之后只要你能保证新旧类结构兼容旧序列化数据能映射到新类的字段上反序列化就可以正常进行。兼容性规划上尽量不要删除旧类里已有的字段可以新增字段但做好默认值兜底。还有一个容易被忽视的点静态变量属于类不属于对象不会被序列化transient修饰的字段在反序列化时为默认值对象为null数值为0所以如果这些字段有业务含义需要在readObject()方法里做初始化或校验。5.3 序列化的替代方案Java原生的序列化机制有几个明显短板生成的字节流体积大、性能低、跨语言能力差。实际业务中我更推荐JSON方案。以Jackson为例ObjectMapper mapper new ObjectMapper(); String json mapper.writeValueAsString(user); User parsed mapper.readValue(json, User.class);JSON序列化的好处是人类可读、跨语言友好前端JavaScript、Python服务端都能处理、体积相对可控。它的缺点是对二进制内容支持不好如果对象里有图片、文件等大字段需要用Base64编码或者另存文件再在JSON里引用路径。如果是追求极致性能的RPC框架像Kryo和Protobuf这类二进制序列化方案会更合适。Kryo的序列化结果比Java原生的小很多Protobuf则需要在编写.proto文件但性能和跨语言能力都更优。6. 常见问题排查与面试必杀技6.1 资源泄漏与并发问题问题1为什么程序运行了一段时间后日志文件句柄一直增长排查步骤很清晰先看代码里有没有在finally块中遗漏关闭流的路径再看Stream有没有被正确关闭最后用jstack查线程堆栈配合lsof -p 进程号 | wc -l统计句柄数。正常情况下每个打开的流对应一个文件描述符不关闭的话一次请求泄漏一个几天后系统就会报Too many open files。这种问题在开发环境很难出现因为文件数少反应慢一到生产环境压力一大就立刻暴露。问题2并发场景下多个线程同时向同一个文件写入导致内容互相覆盖怎么办最简单粗暴的办法是同步块加锁但这会让性能急剧下降。更好的方案是每个线程独立文件或者使用“日志收集”类的框架比如Logback的异步Appender。如果业务上必须多线程共享一个输出流注意BufferedWriter不是线程安全的需要外部加锁或直接用线程安全的类如PrintStream。6.2 性能排查为什么读写大文件这么慢慢的原因通常出现在以下环节没有缓冲、频繁单字节IO、网络往返过多、编码转换耗时、以及磁盘本身的IO瓶颈。排查时建议先打好时间点把文件IO和业务计算分开计时再分别优化。优化常见手段有用BufferedInputStream或BufferedOutputStream把写入缓冲设为64KB甚至1MB。用transferTo()或FileChannel做零拷贝复制。// 手动指定缓冲大小的例子 byte[] buffer new byte[8192]; try (BufferedInputStream bis new BufferedInputStream(new FileInputStream(large.bin), 65536); BufferedOutputStream bos new BufferedOutputStream(new FileOutputStream(copy.bin), 65536)) { int bytesRead; while ((bytesRead bis.read(buffer)) ! -1) { bos.write(buffer, 0, bytesRead); } }代码中buffer数组长度为8KB但BufferedInputStream的内部缓存被指定为64KB这样可以减少底层读的次数适合读取大文件。写侧同样配了64KB的缓冲有效减少了系统调用的频率。注意read和write的第三个参数bos.write(buffer, 0, bytesRead)中的bytesRead是关键不写的话会把整个数组包括未被填充的尾巴都写进去。很多新手的复制文件结果为什么比源文件大就是这个参数造成的。6.3 面试高频考点从基础到进阶根据相关热词java面试题、java八股文来看输入输出这块核心的高频面试知识点可以汇总如下考察方向高频问题与答案要点字节流 vs 字符流字节流不关心编码字符流重点是编码转换四大基类InputStream / OutputStream / Reader / Writer缓冲流作用减少系统调用次数提升读写性能编码与乱码编解码字符集不一致导致乱码解决方案是统一使用UTF-8try-with-resources自动打开资源并关闭优于传统finally写法NIO三要素Channel、Buffer、Selector各自的作用零拷贝transferTo底层使用sendfile减少用户态与内核态切换序列化注意事项实现Serializable接口显式声明serialVersionUIDtransient修饰不需要序列化的字段System.out.println为什么慢内部有锁synchronized且默认开启了行自动刷新面试答题时不要只背定义最好从使用场景出发比如谈一谈通过Java输入输出做文件上传下载时怎么防止内存占用过高这类实战角度更容易打动面试官。6.4 高频面试追问Java输入输出底层到底发生了什么当面试官把话题往深层引的时候常见的一个问题是“聊了这么多Java输入输出你有没有想过System.out.println到底调用了什么”这个问题看起来简单实际是把输入输出的全链路打开了一个口子。System.out是一个PrintStream实例它内部持有一个BufferedWriter再往下包装了一个FileOutputStream。当你调用println()时会先经过同步块然后把字符转成字节最终通过文件描述符写入到标准输出的管道里。在Linux上这一切最终落在write系统调用。正因为println是同步的并且默认开了自动刷新所以在高频打印日志时System.out容易成为性能瓶颈。顺着这个思路如果面试官继续追问“Java的网络IO和文件IO底层有什么不同”你要能答出网络Socket使用的文件描述符与普通文件描述符在网络栈上的处理路径差异以及Non-blocking模式下Selector如何多路复用。能讲到这个深度说明你不仅会API是真的理解IO模型。7. 实际项目里的经验从开发到阅读源码的心得做Java开发这几年输入输出这块我最大的心得可以浓缩成三条第一条IO的难点不在于API而在于心智模型。读写文本、读取文件、网络收发这些操作本身不难但一旦进入异步、缓冲、非阻塞的语境思维模型就要切换。建议学习有基础的读者去读一遍BufferedReader的源码看看底层是如何管理缓冲区、fill()是如何在字符数组之间搬运数据的。读完后你对IO的“阻塞与缓冲”理解会上一个台阶。第二条一切IO问题最终都要落到“资源的生命周期管理”上。什么时候申请流、什么时候冲刷缓冲、什么时候释放句柄是把控整个IO生命周期的关键。用try-with-resources是Java 7开始的标准做法也是后来代码审查里我比较看重的一个规范。见过太多历史项目里流对象在finally中手工close时的顺序问题、重复关闭问题。第三条编码问题要用规范去消灭。与其每次都人工检查编码是否一致不如在项目层面定好规范配置文件统一UTF-8数据库连接设定characterEncodingutf8HTTP响应头显式声明的charset保持大写缩写的UTF-8。规范定了乱码问题基本就绝迹了。有次我排查一个定时任务生成的CSV文件在Excel里打开乱码的问题折腾了半天发现是Excel默认用系统ANSI编码解析CSV而生成的CSV用的是UTF-8。这事的根本解法不是改Java代码而是在CSV文件开头写入UTF-8 BOM\ufeff让Excel正确识别编码。技术问题的解法常常在技术之外——输入输出要跟现实世界的软件生态打交道所以不能只盯着Java本身的API。如果你正在学Java输入输出我强烈建议沿着这条路径走一遍先从FileInputStream用起然后换BufferedReader再到NIO的ByteBuffer最后到Files工具类。每写一次代码都记录一下耗时和遇到问题这套流程走完Java IO的地基就打牢了。临结束前再分享一个小技巧。在排查线上问题需要快速知道类的serialVersionUID值是什么时可以用JDK自带的serialver命令serialver -classpath . com.example.User这种小工具链其实解决了好多实际沟通成本——避免了两个团队联调时因为序列化版本不一致导致的偶发反序列化失败。输入输出这东西知道得越底层踩的坑就越少。
返回列表