原视频:黑马JavaIO流教程,史上最全面javaIO流讲解,面试必备哔哩哔哩bilibili

前言

Java IO 流的核心是实现数据的读写,数据来源 / 目的地可以是文件、网络、内存等,而 File 类是操作文件 / 文件夹的基础,字符集是处理文本数据的关键,各类流(字节 / 字符 / 缓冲 / 转换 / 打印 / 数据 / 序列化)则针对不同场景做了功能优化。学习 IO 流的关键是分清流的类型、掌握适用场景、牢记核心方法和避坑点,本文将按这个思路逐一讲解。

一、File类与 IO 流核心概述

1.1 File 类的作用
  • File 类的对象用于代表磁盘中的文件或文件夹,是操作文件 / 文件夹的基础。

  • File 类仅能操作文件本身(创建、删除、获取文件信息、遍历目录),不能读写文件的内容

1.2 IO 流的核心定义

IO 流(Input/Output Stream)是专门用于读写文件 / 网络等数据源内容的工具,核心方向:

  • 数据流向:输入流、输出流

  • 处理单位:字节流(以 byte 为单位,通用)、字符流(专为文本设计),后续的缓冲流、转换流等都是基于字节流 / 字符流的包装增强流(也叫高级流)。

二、File类的使用

File 类是操作文件 / 文件夹的入口,所有 IO 流的文件操作都需要先通过 File 类指定路径,核心掌握路径创建、常用方法、递归遍历 / 搜索三大知识点。

2.1创建File对象:两种路径方式

创建 File 对象的关键是指定文件 / 文件夹路径,支持绝对路径相对路径,适配不同开发场景:

  • 绝对路径:从盘符开始的完整路径(如E:\Java\test.txt),路径唯一;

  • 相对路径:从当前工程目录开始的路径(如src/test.txt),项目移植性更强;

注意:Windows 路径分隔符是\(代码中需写\\,转义字符),Linux/Mac 是/,跨平台推荐用File.separator替代硬编码。

2.2File类的常用方法(按功能分类)

File 类的方法围绕文件 / 文件夹的判断、获取、创建删除、遍历展开,是面试高频考点,按功能分类记忆更高效:

功能分类核心方法方法作用
判断类exists() isFile() isDirectory()判断路径是否存在,存在返回 true。 判断是否是文件 判断是否是文件夹
获取类getName() length() lastModified() getAbsolutePath()获取文件 / 文件夹名(文件含后缀) 获取文件字节大小(文件夹返回 0) 获取最后修改时间(毫秒时间戳) 获取完整绝对路径
创建删除createNewFile() mkdir() mkdirs() delete()创建空文件(需抛 IO 异常) 创建一级文件夹 创建多级嵌套文件夹(推荐) 删除文件 / 空文件夹(非空文件夹需先删内部内容)
遍历类list() listFiles()获取一级文件 / 文件夹名(字符串数组) 获取一级文件 / 文件夹对象(File 数组,推荐)

遍历代码示例:

 File file1 = new File("src/main/resources");
 File[] files=file1.listFiles();
 for (File file:files){
     System.out.println(file.getAbsoluteFile());
 }
 //System.out.println(Arrays.toString(files));或者用Arrays数组自带的方法
2.3 File 类实战:递归搜索文件

listFiles () 仅能遍历一级目录,要遍历 / 搜索所有层级的文件,需结合递归实现,核心是找到递归出口、做好空指针防御

核心需求

递归查找指定目录下的目标文件,找到后立即返回,避免无效递归。

 package src.com.xing;
 import java.io.File;
 //递归查找该文件夹下是否有文件a
 public class SearchFile {
     public static void main(String[] args) {
         File result=func(new File("src"), new File("b"));
         if(result==null){
             System.out.println("未找到文件或无法搜索");
         }else {
             System.out.println("找到文件"+result.getAbsoluteFile());
         }
     }
 ​
 ​
 //自己调用自己:如果当前是文件夹,就打开它(listFiles())遍历里面的每一个东西。
 //找到出口停下来:如果是文件并且名字和目标一样,说明找到了!
 //如果是无参void则递归只做 “重复动作”,特殊递归,状态存在成员变量里
 //有参递归 = 标准递归,状态存在参数里99% 的递归场景都应该用有参递归
 ​
     //比较字符串要用equals     不考虑大小写可以使用equalsIgnoreCase
     public static File func(File file01,File file02){
 ​
         System.out.println("正在检查:" + file01.getAbsolutePath());
         if(file01.isDirectory()) {
             File[] files = file01.listFiles();
             if (files != null) {
                 for (File file : files) {
                     File res=func(file,file02);
                     if (res != null) {//只要任何一层的子调用返回了非 null(找到了)
                         return res;//就立刻原路返回,层层上交,绝不回头。
                     }
                 }
             }
         }
         if(file01.isFile()&&file01.getName().equalsIgnoreCase(file02.getName())){
             System.out.println("找到了");
             return file01;
         }else {
             return null;
         }
     }
 }

三、字符集:文本处理的核心

3.1 常见字符集特点
  • ASCll码(每个字符占一个字节),仅覆盖英文字母 / 数字 / 符号

  • GBK(汉字字符占两个字节,兼容ASCll,即ASCll字符占一个字节),是可变编码,但不属于前缀编码,通过字符最高位01判断是汉字还是ASCll字符

  • UTF-8(国际通用,覆盖所有字符,汉字占三个字节,ASCll字符占一个字节)属于前缀编码,没有一个编码是另一个编码的前缀,方便解码

  • Unicode字符集(统一码、万国码、4个字节表示一个字符、占据存储很大--->故效率很差)

3.2编码解码

Java 中通过String类的getBytes()(编码)和new String()(解码)实现,显式指定字符集可避免乱码,默认使用系统字符集(易出问题)。

 public class DemoAscll {
     public static void main(String[] args) {
         //编码
         String data="a我b";
         byte[] bytes = data.getBytes();//getBytes方法进行编码 平台字符集默认,返回到数组中
         System.out.println(Arrays.toString(bytes));
 ​
         //解码
         String s = new String(bytes);//String方法可以解码 平台字符集默认
         System.out.println(s);
     }
 }

避坑点:编码和解码必须使用相同的字符集,否则必然出现乱码

四、IO流核心:字节流与字符流(原始流 / 低级流)

字节流和字符流是 IO 流的基础原始流,所有高级流都是基于这两种流的包装,核心区分处理单位适用场景

4.1IO流概述

总共四大类:字节输入,字符输入,字节输出,字符输出

实现类又分为字符流、缓冲流、转换流、打印流、数据流、序列化流

他们适合不同的情况,熟练掌握如何运用这些类及其对应方法

4.2字节流(InputStream/OutputStream)

处理单位:以字节(byte) 为单位,是通用的流,可处理任意数据(文本 / 图片 / 视频 / 压缩包);

核心问题:读取文本时可能因字符截断导致乱码(如 UTF-8 汉字占 3 字节,单字节读取会拆开);

适用场景:文件复制、二进制数据读写(图片 / 视频)、网络数据传输。

4.2.1 文件字节输入流(FileInputStream)

将文件数据读入内存,核心方法read(),推荐字节数组 + while的方式,兼顾性能和通用性,提供 5 种不同读取方式,平时推荐4:

 public class DemoInput {
     public static void main(String[] args) throws Exception {
         //文件字节输入流管道,可以使用多态,可以使用文件对象或者文件路径字符串来打开与实际文件的连接
         InputStream fileInputStream = new FileInputStream("src/main/resources/a");
         //1.开始读取文件的字节数据
 //        int temp=fileInputStream.read();//read读第一个字节,当没有数据时返回-1
 //        System.out.println(temp);
 ​
         //2.使用循环读:但读取性能很差,每次只能读一个字节都要去硬盘调用IO ; 读取中文会乱码
 //        int b;
 //        while( (b=fileInputStream.read())!=-1){
 //            System.out.print(b);//println自动换行,print不换行
 //        }
 ​
         //3.每次读取多个字节   文件大的时候要读取多次,多行代码
 //        byte[] bytes = new byte[1024];//找个位置放
 //        int len = fileInputStream.read(bytes);//连续读取,每次读取都要返回当前读取字节数量
 ////        String s = new String(bytes);
 //        String s=new String(bytes,0,len);//读多少倒多少水
 //        System.out.println(s+"字符个数为"+len);
 //        fileInputStream.close();
 ​
         //4.再使用循环改进 适合文件拷贝工作,但读取汉字也可能截断导致乱码
         byte[] bytes = new byte[1024];
         int len;
         while ((len=fileInputStream.read(bytes))!=-1){//read返回的是int值
             String s = new String(bytes, 0, len);//防止读到空值
             System.out.print(s);//不想换行—>使用print而不是println
         }
         fileInputStream.close();
 ​
         //5.定义一个文件大小的数组一次性存储,读取汉字不会截断导致乱码 但只适合相对较小的文件
 //        File file = new File("src/main/resources/a");
 //        long length = file.length();
 //        byte[] buffer=new byte[(int) length];
 //
 //        int len02 =fileInputStream.read(buffer);
 //        System.out.println(new String(buffer));
 ​
 //        6.官方一次性读取方法readAllBytes  需要jdk9及以上
 //        byte[] buffer=fileInputStream.readAllBytes();
 //        System.out.println(new String(buffer));
 ​
     }
 }
4.2.2文件字节输出流(FileInputStream)

将内存数据写出到文件,核心方法write(),支持覆盖模式追加模式,追加模式在构造器中加true即可

 //默认是覆盖文件,new FileOutputStream("src/main/resources/a",true)
 public class DemoOutput {
     public static void main(String[] args) throws Exception {
     //1.创建一个字节输出流管道与目标文件接通
         FileOutputStream os = new FileOutputStream("src/main/resources/a",true);
 ​
     //2.测试write方法
         byte[] bytes="wondaiodnao法网发".getBytes();
         os.write(bytes);//数组全部写出
         os.write(bytes,0,3);//只写0~3个字节
         
         os.close();//流使用完毕必须关闭,否则会占用系统资源
     }
 }
4.3核心重点:IO 流资源的释放方式、

IO 流是系统资源,使用后必须关闭,否则会造成资源泄漏,Java 提供两种释放方式,try-with-resources是推荐方式,简洁且安全

 public class DemoFreeRes {
     public static void main(String[] args) {
         //直接close时,当中间代码异常时,资源无法释放
         //第1种.try- catch-finally
 //          finally{  if(os!=null) os.close  } 在finally中加入close,但是close还要加try-catch 代码太臃肿了
 ​
 ​
         //第2种.try-catch-resource该方法隐含调用了文件的close方法
         try (
         //把资源的定义放到了try的()中,可能异常的代码放到后面的{}
                 InputStream is=new FileInputStream("src/main/resources/a");
                 OutputStream os=new FileOutputStream("src/main/resources/a01");
                 ){
             byte[] bytes = new byte[1024];
             int len;
             while ((len=is.read(bytes))!=-1){
                 os.write(bytes,0,len);
             }
 ​
         }catch (Exception e){
             e.printStackTrace();
         }
 ​
 ​
 ​
     }
 }
4.字符流(原始流/低级流)
  • 处理单位:以字符(char) 为单位,基于字符集编码实现,专为文本数据设计;

  • 核心优势:按编码规则读取字符,不会出现文本乱码

  • 适用场景:纯文本文件的读写(TXT/Java 文件 / 配置文件)。

4.2.1 文件字符输入流(FileReader)

类似字节输入流,区别是用字符数组接收数据,避免文本乱码:

 package src.com.xing.OriginalStream;
 import java.io.FileReader;
 import java.io.Reader;
 ​
 //字符输入流 把文件输入到内存
 //类似字节输入流FileInput,但是数组要用字符数组char[]  而不是byte[]单字节数组
 public class DemoReader {
     public static void main(String[] args){
         try (
                 //多态
                 Reader fileReader = new FileReader("src/main/resources/a");
                 )
         {
         //类似字节输入流
             char[] bytes = new char[1024];
             int len;
             while ((len=fileReader.read(bytes))!=-1){
                 System.out.print(new String(bytes,0,len));
             }
         }
         catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 }
4.2.2 文件字符输出流(FileWriter)

因为IO需要系统调用,被优化后,是先把数据放到缓冲区,最后只需一次系统调用,所以需要刷新流或者关闭流(包含刷新)以确保最后一次系统调用会成功,否则会数据丢失!

 public class DemoWriter {
     public static void main(String[] args) {
         try (
             Writer fw=new FileWriter("src/main/resources/a",true);//true是为了追加数据,如果没有就是覆盖文本数据
         )
         {
             char[] buffer= {'打','啊','的','等'};
             fw.write("a");//写一个字符
             fw.write("\r\n");//换行
             fw.write("ahduwiu的危害",0,2);//写字符串一部分
             fw.write(buffer,0,4);//写数组一部分
             fw.flush();//刷新流
    
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
     }
 }

五、缓冲流(高级流)----字符字节流

缓冲流(BufferedXXX)是对原始字节 / 字符流的包装增强,核心是在内存中开辟8KB 缓冲区,减少磁盘 IO 的系统调用次数,大幅提升读写性能,是开发中最常用的高级流。

5.1 核心特点
  1. 缓冲流是包装流,必须基于原始流创建(装饰者模式);

  2. 字节缓冲流默认 8KB 缓冲区,可自定义大小;

  3. 字符缓冲流提供独有便捷方法(readLine ()/newLine ()),专为文本优化;

  4. 缓冲输出流同样需要刷新,close () 包含刷新。

5.2 字节缓冲流:BufferedInputStream/BufferedOutputStream

适用于所有数据的高性能读写,尤其是大文件复制,用法和原始字节流一致,仅多了一层包装:

 package src.com.xing.BufferedStream;
 import java.io.*;
 ​
 //字节缓冲流  在内存开辟8KB缓冲池,减少系统调用次数
 public class DemoBufInput {
     public static void main(String[] args) {
         try(
                 InputStream is = new FileInputStream("src/main/resources/a");
                 InputStream bis=new BufferedInputStream(is); //1.定义一个字节输入缓冲流包装原始流
 // InputStream bis=new BufferedInputStream(is,8192*2);可以自定义缓冲区大小
                 
                 OutputStream os = new FileOutputStream("src/main/resources/a01");
                 OutputStream bos=new BufferedOutputStream(os);//2.定义一个字节输出流包装原始的字节输出流
 ​
         ){
             byte[] bytes = new byte[1024];
             int len;
             while ((len=bis.read(bytes))!=-1){
                 bos.write(bytes,0,len);
             }
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
 ​
 ​
     }
 }
5.3字符缓冲输入流(BufferedReader)

专为文本文件优化,提供readLine()(按行读取)和newLine()(跨平台换行)两大独有方法,是文本读写的首选:

 package src.com.xing.BufferedStream;
 import java.io.BufferedInputStream;
 import java.io.BufferedReader;
 import java.io.FileReader;
 import java.io.Reader;
 ​
 //字符缓冲输入流  只读取文本文件,需要按照编码读取,最小单位是字符=1/2/3个字节
 //把原始的字符输入流包装到缓冲池
 public class DemoBufReader {
     public static void main(String[] args) {
         try (
             FileReader fr = new FileReader("src/main/resources/a");
             //创建字符输入流包装原始输入流
             BufferedReader bfr=new BufferedReader(fr);
             //这里不能用多态,多态是父类指向子类,不能调用子类独有的方法,直接子类引用
         ){
 ​
             //之前的方法也可以,但这里使用BufferedReader特有方法readLine(),每次读取一行
             String line;
             while ((line=bfr.readLine())!=null){
                 System.out.println(line);
             }
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 }
5.4字符缓冲输出流(BufferedWriter)
 package src.com.xing.BufferedStream;
 ​
 import java.io.BufferedWriter;
 import java.io.FileWriter;
 import java.io.IOException;
 ​
 public class DemoBufWriter {
     public static void main(String[] args) {
         try (
                 FileWriter fw = new FileWriter("src/main/resources/a01",true);
 ​
                 //创建一个字符缓冲输出流
                 BufferedWriter bfw=new BufferedWriter(fw);
         ){
 ​
             bfw.write("dawddwaf");
             bfw.newLine();    //BUfferedWrite自带方法,可实现换行
             bfw.write("第二行建瓯都发我房间");
 ​
         } catch (IOException e) {
             throw new RuntimeException(e);
         }
     }
 }
5.5缓冲流与原始流的性能比较

核心结论缓冲流 + 字节数组的组合是性能最优的文件复制方式,单字节读取的性能最差,以下是 4 种复制方式的耗时对比(测试文件:jdk-21 压缩包):

复制方式耗时性能评价
run1:原始字节流 一个字节为单位复制极慢(一分钟以上)垃圾
run2:原始字节流 字节数组为单位复制较慢(约 0.8s)可用,性能一般
run3:缓冲字节流 一个字节为单位复制较慢(约 1.7s)不推荐,浪费缓冲区优势
run4:缓冲字节流 字节数组为单位复制极快(约 0.2s)推荐,性能最优
 package src.com.xing.BufferedStream;
 ​
 import java.io.*;
 //比较原始流和缓冲流是否有较大的性能提升,因为要输出输入zip文件,所以不能使用字符输入输出,使用字节输入输出,字节输入输出流是FileInput FileOutput
 public class Performance_4 {
     private static final String SOURCE_FILE="E:\\EdgeDownload\\jdk-21_windows-x64_bin.zip";//要复制的文件
     private static final String DIRECTORY_FILE="D:\\";//要复制到的地址
 ​
     public static void main(String[] args) {
 //        copy01();低级字节流,太慢了....
         copy02();//较慢,0.8s,还可以
         copy03();//慢,  1.7s
         copy04();//缓冲流使用数组复制,最快,最推荐,0.2s
 ​
     }
     
     public static void copy01(){
         long startTime=System.currentTimeMillis();//计时开始
         try(
                 InputStream is=new FileInputStream(SOURCE_FILE);
                 OutputStream os=new FileOutputStream(DIRECTORY_FILE+"1.zip");//复制为D:\1.zip文件
                 )
         {
             int a;
             while((a=is.read())!=-1){
                 os.write(a);
             }
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
         long endTime=System.currentTimeMillis();
         System.out.println("低级字节流,一个字节为单位复制,花费了"+(endTime-startTime));
     }
     
     
     public static void copy02(){
         long startTime=System.currentTimeMillis();//计时开始
         try(
                 InputStream is=new FileInputStream(SOURCE_FILE);
                 OutputStream os=new FileOutputStream(DIRECTORY_FILE+"2.zip");
         )
         {
             byte[] bytes = new byte[1024];
             int len;
             while((len=is.read(bytes))!=-1){
                 os.write(bytes,0,len);
             }
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
         long endTime=System.currentTimeMillis();
         System.out.println("低级字节流,1KB大小数组为单位复制,花费了"+(endTime-startTime));
     }
     
     
     public static void copy03(){
         long startTime=System.currentTimeMillis();//计时开始
         try(
                 InputStream is=new FileInputStream(SOURCE_FILE);
                 BufferedInputStream bis=new BufferedInputStream(is);
                 OutputStream os=new FileOutputStream(DIRECTORY_FILE+"3.zip");
                 BufferedOutputStream bos=new BufferedOutputStream(os);
         )
         {
             int a;
             while((a=bis.read())!=-1){
                 bos.write(a);
             }
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
         long endTime=System.currentTimeMillis();
         System.out.println("缓冲字节流,一个字节为单位,花费了"+(endTime-startTime));
     }
     
     
     public static void copy04(){
         long startTime=System.currentTimeMillis();//计时开始
         try(
                 InputStream is=new FileInputStream(SOURCE_FILE);
                 BufferedInputStream bis=new BufferedInputStream(is);
                 OutputStream os=new FileOutputStream(DIRECTORY_FILE+"4.zip");
                 BufferedOutputStream bos=new BufferedOutputStream(os);
         )
         {
             byte[] bytes = new byte[1024];
             int len;
             while((len=bis.read(bytes))!=-1){
                 bos.write(bytes,0,len);
             }
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
         long endTime=System.currentTimeMillis();
         System.out.println("缓冲流,1KB大小数组为单位复制,花费了"+(endTime-startTime));
     }
 }

六、转换流----仅字符流

转换流(InputStreamReader/OutputStreamWriter)是字节流和字符流的桥梁,当代码编码与文本编码不一致时,用字符流读取文本文件会出现乱码,所以我们需要掌握不同编码读取乱码问题

6.1字符输入转换流:InputStreamReader

字节输入流指定字符集转换为字符输入流,是解决文本读取乱码的核心:

 public class DemoResIn {
     public static void main(String[] args) {
 ​
         try (
                 //代码编码:UTF-8   文本编码:UTF-8
                 // Reader fr = new FileReader("src/com/xing/ConversionStream/io1");
 ​
                 //问题出现:代码编码:UTF-8   文本编码:UTF-16(idea右下角修改)
                 Reader fr = new FileReader("src/com/xing/ConversionStream/io2");
                 BufferedReader bfr=new BufferedReader(fr);
 ​
                 //解决方法:
                 //1.获得文件的原始字节流(是字节流!)
                 InputStream is=new FileInputStream("src/com/xing/ConversionStream/io2");
                 //2.把原始的字节流转化为特定编码的字符流
                 Reader isr=new InputStreamReader(is, "UTF_16BE");
                 //3.把字符流包装为缓冲字符流(提高效率)
                 BufferedReader bisr=new BufferedReader(isr);
                 )
         {
             String line;
             while ((line=bisr.readLine())!=null){
                 System.out.println(line);
             }
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 }
6.2字符输出转换流(不常用):OutputStreamWriter

字节输出流指定字符集转换为字符输出流,控制文本的写出编码,开发中更推荐用 String.getBytes () 实现,转换流使用较少:

 package src.com.xing.ConversionStream;
 import java.io.*;
 ​
 //字符输出转换流//不推荐使用
 //1.推荐String提供的方法getBytes方法解决
 //        String data="dawd的花纹";
 //        byte[] bytes=data.getBytes("GBK");
 //先获取字节输出流,再按照指定的字符集编码转化为字符输出流
 public class DemoResOut {
     public static void main(String[] args) {
         try (
                 //创建文件字节输出流
                 OutputStream os=new FileOutputStream("src/com/xing/ConversionStream/io1",true);
                 //把原始的字节输出流按照指定编码转化为字符输出流数据
                 OutputStreamWriter osw=new OutputStreamWriter(os,"UTF_16BE");
                 //把字符输出流包装为缓冲流字符数据
                 BufferedWriter bw=new BufferedWriter(osw);
                 )
         {
             bw.write("daw");
             bw.write("dad富瓦夫"); 
 ​
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 }

七、打印流----仅输出流

7.1打印流详解
  • 构造器如下:

PrintStream(OutputStream/File/String):指向字节输出流 / 文件 / 文件路径

PrintWriter(OutputStream//Writer/File/String):指向字节输出流 /字符输出流/ 文件 / 文件路径

PrintStream\PrintWriter(String fileName, Charset charset):指定字符编码

PrintStream\PrintWriter(OutputStream out, boolean autoFlush):开启自动刷新

PrintStream\PrintWriter(OutputStream out, boolean autoFlush, String encoding):自动刷新 + 指定编码

  • 方法如下:

println(xxx):打印任意类型数据

write(int/byte[]/...):写字符数据(字符流特性)

  • 问题1:有System.out.println为什么还需要打印流?

打印流的核心价值:「任意目标输出 + 编码可控 + 灵活刷新」,System.out.println() 是打印流的 “子集”,只能输入到控制台

  • 问题2:打印流PrintStream和PrintWriter有什么区别?

二者的差异本质源于 Java IO 「字节流」和「字符流」两大体系的设计逻辑,PrintStream 属于字节流,适配 “通用数据”,PrintWriter 属于字符流,适配 “纯文本数据” 场景。场景选择:文本用 PrintWriter,字节 / 混合用 PrintStream。

  • 问题3:print方法和writer方法有什么区别?

print是把任意数据转成字符串输出,write方法则可以精准输出字节 / 字符原始数据,比如

ps.print(100); // 文件中写入:"100" ps.write(100); // 文件中写入:"d"

  • 问题4:当打印流PrintStream和PrintWriter分别对应print方法和writer方法有什么区别

打印流类型底层体系print()write()
PrintStream字节流任意数据→字符串→按编码转字节输出直接输出原始字节
PrintWriter字符流任意数据→字符串→按编码转字符输出直接输出原始字符
 package src.com.xing.PrintStream;
 ​
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.nio.charset.Charset;
 ​
 //便利+高效实现打印数据
 //PrintStream 是字节流(适配所有数据),PrintWriter 是字符流(仅适配文本);
 public class DemoPrintStream {
     public static void main(String[] args) {
         try (
                 //创建一个打印流管道,不使用多态
                 PrintStream ps=new PrintStream("src/main/resources/a01", Charset.forName("UTF-8"));
            
 ​
                 //高级流不支持直接追加,先包装为低级流再追加
 //PrintWriter pws=new PrintWriter(new FileOutputStream("src/main/resources/a",true));
                 )
         {
             ps.println(97);
             ps.print("日日日日日日日日日日日日日日dada");
 ​
             ps.write(97);//a
 ​
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 }
7.2打印流重定向

将控制台输出(System.out)重定向到文件,实现日志的持久化,是调试的常用技巧:

 package src.com.xing.PrintStream;
 import java.io.FileNotFoundException;
 import java.io.PrintStream;
 //把输出语句的打印位置改到文件中
 public class PrintRedirection {
     public static void main(String[] args) {
         try (
                 PrintStream ps=new PrintStream("src/main/resources/a01");
                 ){
             System.setOut(ps);//打印重定位
         } catch (FileNotFoundException e) {
             throw new RuntimeException(e);
         }
     }
 }

八、数据流----仅字节流

普通流会丢 “数据类型”,读取时要手动解析(易出错),把 int 21写成字符串 "21"(占 2 个字节)如果文件里是 "abc",直接报数字解析异常;数据流则可以同时输入/输出 数据和数据类型,保存在文件时是乱码,但可以通过数据流顺序读写再次解析出来

适用场景:程序间的结构化数据传输(本地文件 / 网络 Socket),不适合人类可读的场景。

 package src.com.xing.DataStream;
 import java.io.*;
 ​
 public class DataStream {
     public static void main(String[] args) {
         //创建数据输出流对象
         try (
                 //数据输出流
                 DataOutputStream ds = new DataOutputStream(new FileOutputStream("src/main/resources/a"));
                 //数据输入流
                 DataInputStream di=new DataInputStream(new FileInputStream("src/main/resources/a"));
                 ){
             ds.writeInt(21);
             ds.writeBoolean(true);
             ds.writeUTF("数据输出流");
 ​
 //            该文件输出时必须严格按写入顺序读取
             System.out.println(di.readInt());
             System.out.println(di.readBoolean());
             System.out.println(di.readUTF());
 ​
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 }

九、序列化

序列化流(ObjectInputStream/ObjectOutputStream)是将 Java 对象转换为二进制数据(序列化)和将二进制数据还原为 Java 对象(反序列化)的流,核心实现对象的持久化(写入文件)和网络传输

9.1 序列化的核心前提(面试必问)

要实现序列化的 Java 对象,必须满足两个条件:

  1. 实体类实现Serializable接口(标记接口,无抽象方法);

  2. 实体类的所有成员变量都可序列化(基本数据类型默认可序列化,引用类型需实现 Serializable);

  3. 可选:显式指定序列化版本号(private static final long serialVersionUID),避免类结构修改导致反序列化失败。

9.2 瞬态关键字:transient

transient修饰的成员变量,不会被序列化,反序列化时会返回该类型的默认值(如 String 返回 null,int 返回 0),适用于敏感数据(如密码)。

 package src.com.xing.ObjectStream;
 import java.io.*;
 ​
 //对象字节输出流,序列化,把java对象写到文件中去
 //对象字节输入流,反序列化,把文件中的java对象读出来
 public class ObjectOut {
     public static void main(String[] args) {
 ​
         //创建对象字节输出流对象包装原始的字节输出流
         try (
                 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("src/main/resources/a"));
 ​
                 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("src/main/resources/a"));
 ​
                 ){
             User u=new User("admmin",19,"password");
             oos.writeObject(u);
 ​
             System.out.println(ois.readObject());
 ​
         } catch (Exception e) {
             throw new RuntimeException(e);
         }
     }
 }

User类

 package src.com.xing.ObjectStream;
 ​
 import java.io.Serializable;
 ​
 //如果对象需要序列化,该对象要实现序列化接口
 //如果密码不想被打印出来,在字符前添加transient修饰符
 public class User implements Serializable {
     private String name;
     private int age;
     private transient String passeord;
 ​
     public User() {
     }
 ​
     public User(String name, int age, String passeord) {
         this.name = name;
         this.age = age;
         this.passeord = passeord;
     }

9.3 反序列化的注意事项

  1. 序列化和反序列化的类结构必须一致(推荐指定 serialVersionUID);

  2. 反序列化时,类的构造方法不会被执行

  3. 静态变量不会被序列化(静态变量属于类,而非对象)。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐