【黑马教程笔记】Java IO 流超全精讲
原视频:黑马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 核心特点
-
缓冲流是包装流,必须基于原始流创建(装饰者模式);
-
字节缓冲流默认 8KB 缓冲区,可自定义大小;
-
字符缓冲流提供独有便捷方法(readLine ()/newLine ()),专为文本优化;
-
缓冲输出流同样需要刷新,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 对象,必须满足两个条件:
-
实体类实现Serializable接口(标记接口,无抽象方法);
-
实体类的所有成员变量都可序列化(基本数据类型默认可序列化,引用类型需实现 Serializable);
-
可选:显式指定序列化版本号(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 反序列化的注意事项
-
序列化和反序列化的类结构必须一致(推荐指定 serialVersionUID);
-
反序列化时,类的构造方法不会被执行;
-
静态变量不会被序列化(静态变量属于类,而非对象)。
更多推荐




所有评论(0)