Java IO流详解
Java IO流详解一、IO 是什么IO 就是 内存与硬盘的数据传输通道。直白解释就是读和写输入流 Input读硬盘里的文件数据通过IO通道流入程序内存程序才能拿到文件内容。输出流 Output写程序内存里的文字/数据通过IO通道流出到硬盘保存成文件。【外部文件/硬盘】 ←——(输出流OutputStream)——→ 【程序内存】 【外部文件/硬盘】 ——(输入流InputStream)——→ 【程序内存】计算机只有两种存储硬盘永久存储存的是 0101 二进制字节内存临时存储程序运行结束数据消失IO 流就是连接两者的数据管道输入流硬盘 → 内存读文件输出流内存 → 硬盘写文件IO实际上就像一根管子起到了运输作用。注控制台打印System.out.println()也是输出流数据流向显示器。对应你笔记的流转关系内存 Memory ↔ 文件 Files ↔ 显示器 display二、字节流和字符流IO所有流顶层父类 ┌───────────────┴───────────────┐ 字节流所有二进制文件通用 字符流只处理纯文本 InputStream(输入) OutputStream(输出) Reader(输入) Writer(输出) 最小单位1字节(8位二进制) 最小单位1字符(专门处理文字) 适用图片、视频、压缩包、exe程序 适用txt、java、md等文字文档 特点不处理文字编码不会乱码/损坏文件 特点自带编码转换中文友好1字节流 InputStream / OutputStream本质直接读写硬盘原生二进制数据不做任何处理、不做编码转换。直接操作原始二进制字节直接读写硬盘上原生的 8 位二进制数据。最小操作单位1 byte 8 bit底层行为读取硬盘原生二进制原封不动放入内存写入时直接把内存二进制原样刷入硬盘。编码逻辑完全不涉及字符编码没有解码、编码转换过程。能处理图片、视频、压缩包、文本、exe 所有文件同时因为无编码、无转换所以不破坏文件。例硬盘中文 “中” 存储二进制11100100 10111000 10101101UTF-8 三字节字节流读取直接把这 3 个字节原封不动读到内存不会翻译成文字。2字符流 Reader / Writer底层本质封装了字节流编码转换器底层依然靠字节流读写二进制只是自动增加「编码/解码」转换逻辑。底层依然读字节最小操作单位1 char 16 bitJava内部字符统一使用UTF-16存储底层行为读文件解码硬盘字节 → 字节流读取 → 根据指定编码翻译成Java内部16位字符写文件编码Java16位字符 → 根据指定编码转为硬盘字节 → 字节流写入硬盘编码逻辑必须经过编码转换是字节流的上层封装。例读取硬盘“中”的3个UTF-8字节字符流自动执行解码3字节二进制 → 转为Java内存中16位字符中。一般使用图片、视频、压缩包 → 只能用字节流只读写文字、要正常显示中文 → 优先字符流一句话总结字符流是“带翻译的字节流”注字符流底层还是字节流只是包装了文字翻译功能或者实际上说”万物皆字节“三、相对路径 绝对路径前置底层知识操作系统读取文件时必须提供文件唯一定位地址否则系统不知道去哪找文件路径就是文件的“地址”。Java中File、IO流底层都会把路径交给操作系统API操作系统根据路径定位磁盘文件。1. 绝对路径------从磁盘根目录开始的完整唯一地址原理操作系统不需要任何推导直接精准定位文件。从磁盘根分区开始完整、无歧义的完整地址包含盘符/根目录、所有父文件夹、文件名。操作系统拿到路径可以直接定位文件不需要额外推导。系统示例WindowsD:/JavaProject/io/demo/test.txt从D盘根目录开始Mac/Linux/Users/student/code/test.txt从系统根目录/开始作用固定定位文件无论程序在哪运行都能精准找到文件适合工具类、固定配置文件、本地固定资源优缺点优点不会出现找不到文件FileNotFoundException定位稳定缺点移植性极差换电脑、换磁盘分区路径直接失效代码耦合本地环境2. 相对路径------不以盘符开头以「程序当前运行目录」为基准自动拼接路径原理JVM 自动补全路径最终依然会变成绝对路径去访问文件。不写完整地址以「程序当前工作目录」为基准向后推导文件位置。程序启动时JVM会自动生成一个「当前工作目录」相对路径会拼接这个基准目录生成完整绝对路径再交给系统。基准目录规则新手必懂IDEA/Eclipse直接运行main方法基准目录 项目根文件夹打包jar运行基准目录 jar包所在文件夹示例项目根目录为D:/JavaProject/io/demo写相对路径test.txt→ JVM自动拼接为完整路径D:/JavaProject/io/demo/test.txt写相对路径doc/hello.txt→ 完整路径D:/JavaProject/io/demo/doc/hello.txt作用项目整体打包迁移、发给别人运行不用修改文件地址项目资源统一放在项目内部规范管理优缺点优点移植性强项目整体复制到其他电脑也能正常运行缺点基准目录改变就会找不到文件不同运行环境基准目录不一致容易报错3. 代码区分演示importjava.io.File;publicclassPathTest{publicstaticvoidmain(String[]args){// 1. 绝对路径Windows完整磁盘地址FileabsFilenewFile(D:/JavaIO/test.txt);// 打印系统最终读取的完整地址System.out.println(绝对路径完整地址absFile.getAbsolutePath());// 2. 相对路径以项目根目录为基准FilerelFilenewFile(test.txt);System.out.println(相对路径解析后完整地址relFile.getAbsolutePath());}}四、所有流统一4步操作不管字节、字符流读写文件永远固定4步确定文件路径数据源/保存位置创建流对象打通内存和文件的通道调用read()读取 / write()写入数据关闭流close()释放系统文件资源小优化JDK7及以上try-with-resources写法代码执行完自动关流不用手动写close。五、常用流简单介绍1. 基础文件流直接操作硬盘字节流FileInputStream读文件、FileOutputStream写文件字符流FileReader/FileWriter缺点字符基础流使用电脑系统默认编码Windows是GBK读取UTF-8中文文件会乱码新手不推荐直接用。2. 转换流解决中文乱码核心InputStreamReader、OutputStreamWriter作用字节流 ↔ 字符流的转换桥梁可以手动指定UTF-8编码彻底解决中文乱码。标准文本读写链路图硬盘文件 → FileInputStream(字节通道) → InputStreamReader(转字符UTF-8) → 程序内存 程序内存 → OutputStreamWriter(转字节UTF-8) → FileOutputStream(字节通道) → 硬盘文件3. 缓冲流提速首选最常用内置内存缓冲区批量读写数据减少硬盘访问次数读写速度更快。字节缓冲BufferedInputStream/BufferedOutputStream字符缓冲BufferedReader/BufferedWriter独有便利功能BufferedReader.readLine()一次性读取一整行文字处理文本超简单。4. 对象流ObjectInputStream、ObjectOutputStream把Java对象保存到文件。硬性要求实体类必须实现Serializable接口。例案例1读取文本文件UTF-8无乱码字符缓冲流importjava.io.BufferedReader;importjava.io.FileInputStream;importjava.io.InputStreamReader;publicclassReadTextDemo{publicstaticvoidmain(String[]args)throwsException{// 1. 搭建通道文件→字节流→转换流(指定UTF-8)→缓冲流BufferedReaderbrnewBufferedReader(newInputStreamReader(newFileInputStream(test.txt),UTF-8));Stringline;// 2. 循环一行一行读取文字while((linebr.readLine())!null){System.out.println(line);}// 3. 关闭通道br.close();}}使用说明在项目根目录新建test.txt写入中文运行不会乱码。案例2写入文本文件追加文字不会覆盖原有内容importjava.io.BufferedWriter;importjava.io.FileOutputStream;importjava.io.OutputStreamWriter;publicclassWriteTextDemo{publicstaticvoidmain(String[]args)throwsException{// new FileOutputStream(路径, true) true代表追加写入BufferedWriterbwnewBufferedWriter(newOutputStreamWriter(newFileOutputStream(out.txt,true),UTF-8));bw.write(今天学习Java IO流);bw.newLine();// 自动换行跨平台兼容bw.flush();// 把缓冲区数据写入文件bw.close();}}案例3字节流复制图片支持所有文件通用importjava.io.BufferedInputStream;importjava.io.BufferedOutputStream;importjava.io.FileInputStream;importjava.io.FileOutputStream;publicclassCopyImgDemo{publicstaticvoidmain(String[]args)throwsException{// 输入通道读取原图BufferedInputStreambisnewBufferedInputStream(newFileInputStream(1.jpg));// 输出通道写入新图片BufferedOutputStreambosnewBufferedOutputStream(newFileOutputStream(copy.jpg));byte[]tempBoxnewbyte[1024];// 临时缓存容器批量传输数据intlength;while((lengthbis.read(tempBox))!-1){bos.write(tempBox,0,length);}bis.close();bos.close();}}使用说明项目下放一张名为1.jpg的图片运行后生成复制后的图片。六、流的打开关闭规则先开后开、后开先关底层原理IO流会向操作系统申请文件句柄文件资源占用标识操作系统能同时打开的句柄数量有限不关闭会导致资源泄漏。同时缓冲流有内存缓冲区缓冲区数据存在内存没有写入磁盘关闭顺序错误会导致数据丢失。1. 标准打开顺序先开输入流后开输出流逻辑先读取硬盘原始数据再写入新文件符合文件复制/读取写入的业务逻辑。// 第一步打开输入流占用源文件句柄BufferedInputStreambisnewBufferedInputStream(newFileInputStream(src.jpg));// 第二步打开输出流占用目标文件句柄BufferedOutputStreambosnewBufferedOutputStream(newFileOutputStream(copy.jpg));2. 标准关闭顺序后开先关后创建的流优先关闭先创建的流最后关闭底层原因输出流存在缓冲区如果先关输入流程序直接结束输出缓冲区残留数据无法刷新到磁盘文件空白、数据丢失。// 读写操作省略bos.close();// 后打开的输出流先关闭自动flush缓冲区数据落盘bis.close();// 先打开的输入流最后关闭3. 优化方案try-with-resourcesJDK7新增实现AutoCloseable接口的流会自动逆序关闭不用手动管理顺序。七、序列化 反序列化 Serializable接口底层原理1. 底层本质原理为什么需要序列化Java对象存放在JVM内存中程序停止后直接销毁无法持久保存到硬盘。序列化JVM把内存中对象的成员变量数据转换成硬盘可存储的二进制字节序列写入文件持久保存。对象内存数据 → 硬盘二进制保存对象反序列化读取硬盘二进制字节序列重新在JVM内存中还原出完整Java对象。硬盘二进制 → 恢复成内存对象读取对象2. Serializable标记接口底层作用Serializable 接口到底是什么publicinterfaceSerializable{}接口内部没有任何抽象方法是标记型接口。底层原理JVM 默认不会帮你保存对象。只有标记了 SerializableJVM 才会允许该类对象转为二进制允许持久化、网络传输没写直接报错NotSerializableExceptionpublicinterfaceSerializable{}因此只有类实现SerializableJVM才会生成该类对象的序列化二进制转换逻辑未实现该接口直接序列化JVM抛出NotSerializableExceptionprivate static final long serialVersionUID序列化版本号底层作用反序列化时校验文件二进制与当前类版本是否匹配类新增/删除属性后版本号不变可以正常反序列化否则抛版本异常。transient关键字修饰的成员变量序列化时JVM直接忽略该字段不会写入二进制读取后为默认值。3. 极简完整案例步骤1实体类实现Serializableimportjava.io.Serializable;// 标记接口告诉JVM该类支持序列化publicclassUserimplementsSerializable{// 手动定义序列化版本号避免类修改后反序列化报错privatestaticfinallongserialVersionUID1L;privateStringname;// transient修饰不会序列化保存privatetransientinttempNum;publicUser(Stringname,inttempNum){this.namename;this.tempNumtempNum;}OverridepublicStringtoString(){returnnamename临时变量tempNumtempNum;}}步骤2序列化对象写入文件持久化importjava.io.FileOutputStream;importjava.io.ObjectOutputStream;publicclassSerialTest{publicstaticvoidmain(String[]args)throwsException{// 相对路径项目根目录生成user.obj文件ObjectOutputStreamoosnewObjectOutputStream(newFileOutputStream(user.obj));UserusernewUser(张三,999);// 将对象转为二进制写入文件oos.writeObject(user);oos.close();}}步骤3反序列化读取二进制还原对象importjava.io.FileInputStream;importjava.io.ObjectInputStream;publicclassDeserialTest{publicstaticvoidmain(String[]args)throwsException{ObjectInputStreamoisnewObjectInputStream(newFileInputStream(user.obj));// 读取二进制还原内存对象Useruser(User)ois.readObject();// 输出name张三临时变量tempNum0transient字段不保存默认值0System.out.println(user);ois.close();}}例案例1字符流读取UTF-8文本演示编码转换底层importjava.io.BufferedReader;importjava.io.FileInputStream;importjava.io.InputStreamReader;publicclassReadTxt{publicstaticvoidmain(String[]args)throwsException{// 相对路径基准目录为项目根目录BufferedReaderbrnewBufferedReader(newInputStreamReader(newFileInputStream(test.txt),UTF-8));Stringline;while((linebr.readLine())!null){System.out.println(line);}br.close();}}案例2字节流复制图片演示纯二进制传输、先开后关importjava.io.BufferedInputStream;importjava.io.BufferedOutputStream;importjava.io.FileInputStream;importjava.io.FileOutputStream;publicclassCopyImage{publicstaticvoidmain(String[]args)throwsException{// 先开输入流BufferedInputStreambisnewBufferedInputStream(newFileInputStream(src.jpg));// 后开输出流BufferedOutputStreambosnewBufferedOutputStream(newFileOutputStream(copy.jpg));byte[]buffernewbyte[1024];intlen;while((lenbis.read(buffer))!-1){bos.write(buffer,0,len);}// 后开先关bos.close();bis.close();}}案例3追加写入文本文件importjava.io.BufferedWriter;importjava.io.FileOutputStream;importjava.io.OutputStreamWriter;publicclassWriteTxt{publicstaticvoidmain(String[]args)throwsException{// 第二个参数true开启追加模式不覆盖原有二进制数据BufferedWriterbwnewBufferedWriter(newOutputStreamWriter(newFileOutputStream(out.txt,true),UTF-8));bw.write(底层区分字节流和字符流);bw.newLine();bw.flush();bw.close();}}八、新手核心坑点底层原理总结FileReader中文乱码底层原因FileReader默认使用操作系统的系统编码Windows GBK解码规则和文件UTF-8二进制不匹配翻译文字出错必须使用转换流手动指定UTF-8编码。相对路径找不到文件程序启动的基准工作目录发生变化JVM拼接出的完整地址错误优先使用绝对路径调试。序列化报错 NotSerializableException实体类没有实现Serializable标记接口JVM没有生成对象二进制转换逻辑。关闭顺序错误文件空白输出流缓冲区数据驻留内存未刷新写入磁盘就关闭输入流程序直接退出缓冲区数据丢失。字符流读取图片损坏字符流会对图片二进制进行编码解码转换篡改原始二进制数据图片彻底无法打开。