第八章 IO流
IO流的分类
- 什么是IO流?
IO流指的是:程序中数据的流动。数据可以从内存(临时存储文件的空间)流动到硬盘(持久化设备的一种),也可以从硬盘流动到内存。
Java中IO流最基本的作用是:完成文件的读(输入流,硬盘->内存)和写(输出流,内存->硬盘)。
- IO流的分类?
根据数据流向分为:输入和输出是相对于内存而言的,
① 输入流:从硬盘到内存。(输入又叫做读:read)
② 输出流:从内存到硬盘。(输出又叫做写:write)
- 根据读写数据形式分为:
① 字节流:一次读取一个字节。适合读取非文本数据。例如图片、声音、视频等文件。(当然字节流是万能的。什么都可以读和写。)
②字符流:一次读取一个字符。只适合读取普通文本。不适合读取二进制文件。因为字符流统一使用Unicode编码,可以避免出现编码混乱的问题。
注意:Java的所有IO流中凡是以Stream结尾的都是字节流。凡是以Reader和Writer结尾的都是字符流。
- 根据流在10操作中的作用和实现方式来分类:
节点流:节点流负责数据源和数据目的地的连接,是IO中最基本的组成部分。
处理流:处理流对节点流进行装饰/包装,提供更多高级处理操作,方便用户进行数据处理。
节点流关闭的时候会自动刷新,包装流是需要手动刷新的。
流名称 | 是否需要刷新 | 节点流/处理流 | 典型用法 |
---|---|---|---|
FileInputStream | 否 | 节点流 | 直接读取文件中的原始字节。 |
FileOutputStream | 否 | 节点流 | 直接写入字节到文件。 |
BufferedInputStream | 否 | 处理流 | 包装其他输入流,提供缓冲读取。 |
BufferedOutputStream | 是 | 处理流 | 包装其他输出流,缓冲写入并需手动刷新。 |
ByteArrayInputStream | 否 | 节点流 | 从内存字节数组读取数据。 |
ByteArrayOutputStream | 否 | 节点流 | 将数据写入内存字节数组。 |
DataInputStream | 否 | 处理流 | 读取基本数据类型(如 int , double )。 |
ObjectOutputStream | 是 | 处理流 | 序列化对象到流(需调用 flush() )。 |
GZIPInputStream | 否 | 处理流 | 解压 GZIP 格式的压缩数据。 |
GZIPOutputStream | 是 | 处理流 | 压缩数据为 GZIP 格式(需手动刷新)。 |
ZipInputStream | 否 | 处理流 | 读取 ZIP 文件中的条目。 |
ZipOutputStream | 是 | 处理流 | 将文件写入 ZIP 格式压缩包。 |
PrintStream | 是 | 处理流 | 输出格式化的字节数据(如 System.out )。 |
流名称 | 是否需要刷新 | 节点流/处理流 | 典型用法 |
---|---|---|---|
FileReader | 是 | 节点流 | 读取文本文件(默认使用系统编码)。 |
FileWriter | 是 | 节点流 | 写入文本文件(默认使用系统编码)。 |
BufferedReader | 否 | 处理流 | 逐行读取文本文件。 |
BufferedWriter | 是 | 处理流 | 高效写入文本并支持换行符。 |
PrintWriter | 是 | 处理流 | 输出格式化的字符数据(如 HTML 生成)。 |
InputStreamReader | 否 | 处理流 | 将字节流按指定编码转换为字符流。 |
OutputStreamWriter | 是 | 处理流 | 将字符流按指定编码转换为字节流。 |
字节流
FileInputStream
文件字节输入流。负责读。任何文件都能读。但还是建议读二进制文件。例如:图片,声音,视频等。可以读普通文本的。只不过一次读取一个 字节 。容易出现乱码问题。
FileInputStream(String name)
# 通过文件路径构建一个文件字节输入流对象
常用方法
// int read(); 调用一次read()方法则读取一个字节,返回读到的字节本身。如果读不到任何数据则返回 -1in = new FileInputStream("D:/Java/Java学习/Java2025/chapter08/src/file01.txt");int read = in.read();System.out.println("第一次读到的字节:" + read); // a读出的字符是97read = in.read();System.out.println("第二次读到的字节:" + read);// 改进while循环
int readByte = 0;
while((readByte = in.read()) != -1) {System.out.print(readByte);System.out.println((char)readByte);
}
// 文件内容是abcdef
fis = new FileInputStream("D:\\Java\\Java学习\\Java2025\\chapter08\\src\\file01.txt");// int read(byte[] b); 一次最多可以读到 b.length个字节(只要文件内容足够多)。
// 返回值是读取到的字节数量。如果这一次没有读取到任何数据,则返回 -1// 提前准备一个Byte[]数组(一次最多读取四个字节)
byte[] bytes = new byte[4];// 将Byte数组转换成字符串 此时byte数组里面已经存放了abcd
int readCount = fis.read(bytes);
System.out.println("第一次读取到的字节数量:" + readCount); // 4 abcdString str = new String(bytes, 0, readCount);
System.out.println(str);readCount = fis.read(bytes);
System.out.println("第二次读取到的字节数量:" + readCount); // 2
String str1 = new String(bytes, 0, readCount);
System.out.println(str1); // efreadCount = fis.read(bytes);
System.out.println("第三次读取到的字节数量:" + readCount); // -1// 遍历
int readCount = 0;
while((readCount = fis.read(bytes)) != -1) {System.out.print(new String(bytes, 0, readCount));
}
// int read(byte[] b, int off, int len); 一次读取len个字节。将读到的数据从byte数组的off位置开始放。fis = new FileInputStream("D:\\Java\\Java学习\\Java2025\\chapter08\\src\\file01.txt");
byte[] bytes = new byte[10];int readCount = fis.read(bytes, 2, 5);System.out.println("读取到了多少个字节:" + readCount);for (byte b : bytes) {System.out.print(b + " ");System.out.println((char)b);
}// 跳过n个字节
int readByte = fis.read();
System.out.println(readByte); // 97// 跳过两个
fis.skip(2);readByte = fis.read();
System.out.println(readByte); // 100// 还剩多少个字节没读
int readCount1 = fis.available();
System.out.println(readCount1); // 2
FileOutputStream
// FileOutputStream(String name)
// 创建一个文件字节输出流对象,这个流在第一次使用的时候,最开始会将原文件内容全部清空,然后写入。// FileOutputStream(String name, boolean append)
// 创建一个文件字节输出流对象,当append是true的时候,不会清空原文件的内容,在原文件的末尾追加写入。
// 创建一个文件字节输出流对象,当append是false的时候,会清空原文件的内容,然后写入。// void close();
// void flush();
// void write(int b); 写一个字节
// void write(byte[] b); 将整个byte字节数组写出
// void write(byte[] b, int off, int len) 将byte字节数组的一部分写出。public class FileOutputStreamTest01 {public static void main(String[] args) {// 创建文件字节输出流对象FileOutputStream out = null;try {
// out = new FileOutputStream("D:\\Java\\Java学习\\Java2025\\chapter08\\src\\file02.txt", true);// 当原文件中有内容时,后面两个运行结果一样out = new FileOutputStream("D:\\Java\\Java学习\\Java2025\\chapter08\\src\\file02.txt");
// out = new FileOutputStream("D:\\Java\\Java学习\\Java2025\\chapter08\\src\\file02.txt", false);byte[] bytes = {97, 98, 99, 100};out.write(bytes);out.write(bytes, 0, 2);byte[] bs = "动力节点".getBytes();out.write(bs);// 开始写 同一个流只在第一次写的时候会清空out.write(97); // aout.write(98); // about.write(99); // abcout.write(100); // 刷新out.flush(); // abcdab动力节点abcd} catch (FileNotFoundException e) {throw new RuntimeException(e);} catch (IOException e) {throw new RuntimeException(e);} finally {try {out.close();} catch (IOException e) {throw new RuntimeException(e);}}}
}
try-with-resources 资源自动关闭
文件复制
try(FileInputStream fis = new FileInputStream("D:\\Java\\JavaMe\\Java2025\\chapter08\\src\\file01.txt");FileOutputStream out = new FileOutputStream("D:\\Java\\JavaMe\\Java2025\\chapter08\\src\\file03.txt")) {byte[] bytes = new byte[1024];int readCount = 0;while ((readCount = fis.read(bytes)) != -1) {out.write(bytes, 0, readCount);}
} catch (FileNotFoundException e) {throw new RuntimeException(e);
} catch (IOException e) {throw new RuntimeException(e);
}
字符流
FileReader(包装流)
字符输入流,一次至少读取一个字符;
字符流(包装流)需要关闭;
try(FileReader reader = new FileReader("D:\\Java\\JavaMe\\Java2025\\chapter08\\src\\file02.txt")){// 开始读char[] buf = new char[1];int readCount = 0;while ((readCount = reader.read(buf)) != -1) {System.out.print(new String(buf,0,readCount));}} catch (FileNotFoundException e) {throw new RuntimeException(e);
} catch (IOException e) {throw new RuntimeException(e);
}
解码默认UTF-8
// 创建FileReader的时候,没有指定字符集,默认采用UTF-8的字符集进行解码。
FileReader reader = new FileReader("E:\\powernode\\02-JavaSE\\code\\file2.txt");// 开始读
int readCount = 0;
char[] chars = new char[1024];
while((readCount = reader.read(chars)) != -1){System.out.print(new String(chars, 0, readCount));
}reader.close();
FileWriter
try (FileWriter fis = new FileWriter("D:\\Java\\JavaMe\\Java2025\\chapter08\\src\\file03.txt")){fis.write("hello");fis.write("张三李四", 2, 1); // hello李fis.write("\n");fis.write("张三李四王五".toCharArray()); // 张三李四王五fis.write("张三李四王五".toCharArray(), 0, 2); // 张三fis.append("1");fis.write("\n");fis.write("2");fis.flush();
} catch (IOException e) {throw new RuntimeException(e);
}
编码默认UTF-8
// 创建文件字符输出流, 后面的true表示以追加的方式在文件后面 再 进行追加
FileWriter writer = new FileWriter("E:\\powernode\\02-JavaSE\\code\\file3.txt", true);// 开始写
writer.write("动力节点"); // 这里的“动力节点”采用的是UTF-8的编码方式// 刷新
writer.flush();// 关闭
writer.close();
文件的复制
try(FileReader fis = new FileReader("D:\\Java\\JavaMe\\Java2025\\chapter08\\src\\file03.txt");FileWriter fos = new FileWriter("D:\\Java\\JavaMe\\Java2025\\chapter08\\src\\file04.txt")) {char[] buf = new char[1024];int readCount = 0;while ((readCount = fis.read(buf)) != -1) {fos.write(buf, 0, readCount);}
} catch (FileNotFoundException e) {throw new RuntimeException(e);
} catch (IOException e) {throw new RuntimeException(e);
}
解决乱码问题
解码(转换流、字符流InputStreamReader)
// InputStreamReader时,可以指定解码的字符集。用来解决读过程中的乱码问题
// InputStreamReader是一个字符流。是一个转换流,是一个输入和解码的过程// InputStreamReader常用的构造方法:
// InputStreamReader(InputStream in) 采用平台默认的字符集进行解码。
// InputStreamReader(InputStream in, String charsetName) 采用指定的字符集进行解码。// FileReader实际上是InputStreamReader的子类。// FileReader也是一个 包装流 ,不是节点流(InputStream就是节点流)。 // 包装流包括这个节点流
// 创建一个转换流对象(输入流)
// 节点流
//FileInputStream in = new FileInputStream("");
// 包装流
//InputStreamReader isr = new InputStreamReader(in);//InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\powernode\\02-JavaSE\\code\\file3.txt"), "GBK");// 以上代码太长了。在IO流的继承体系结构中,IO流又给InputStreamReader提供了一个子类:FileReader
// 代码可以这样写了:
//FileReader isr = new FileReader("E:\\powernode\\02-JavaSE\\code\\file3.txt", Charset.defaultCharset());
FileReader isr = new FileReader("E:\\powernode\\02-JavaSE\\code\\file3.txt", Charset.forName("GBK"));// 开始读
int readCount = 0;
char[] chars = new char[1024];
while((readCount = isr.read(chars)) != -1){System.out.print(new String(chars, 0, readCount));
}
// 关闭流
isr.close();
编码(转换流、字符流OutputStreamWriter)
// FileWriter是OutputStreamWriter的子类。
// 创建转换流对象OutputStreamWriter
// 以下代码采用的是UTF-8的字符集进行编码。(采用平台默认的字符集)
// 注意:以下代码中输出流以覆盖的方式输出/写。
//OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt"));//通过OutputStreamWriter的第二个参数可强制指定编码
//OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt"), "GBK");//追加写入且使用GBK编码,适合日志文件等需要持续记录的场景
//OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt", true), "GBK");/*OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt", true));*//*OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt", true), "GBK");*/FileWriter osw = new FileWriter("E:\\powernode\\02-JavaSE\\code\\file4.txt", Charset.forName("UTF-8"), true);// 开始写
osw.write("来动力节点学Java");osw.flush();
osw.close();
缓冲流
BufferedInputStream(包装流)
// java.io.BufferedInputStream
// 在内存当中存在一个大的byte数组看作一个缓冲区,这个byte数组是不需要维护的,
// 由BufferedInputStream和BufferedOutputStream自动维护
// 主要功能是减少内存与硬盘之间的交互次数// java.io.BufferedInputStream的用法和FileInputStream用法相同
// 不同点是:
// FileInputStream是节点流。
// BufferedInputStream是缓冲流(包装流/处理流)。这个流的效率高。自带缓冲区。并且自己维护这个缓冲区。
// 读大文件的时候建议采用这个缓冲流来读取。// BufferedInputStream对 FileInputStream 进行了功能增强。增加了一个缓冲区的功能// 创建一个BufferedInputStream对象:BufferedInputStream(InputStream in)
public static void main(String[] args) {BufferedInputStream bis = null;try {
// // 创建节点流
// FileInputStream fis = new FileInputStream("/chapter08/file04.txt");
//
// // 创建包装流
// bis = new BufferedInputStream(fis);// 组合起来写bis = new BufferedInputStream(new FileInputStream("chapter08/src/file05.txt"));// 读和FileInputStream用法完全相同byte[] buf = new byte[1024];int readCount = 0;while ((readCount = bis.read(buf)) != -1) {System.out.println(new String(buf, 0, readCount));}} catch (Exception e) {e.printStackTrace();} finally {// 包装流以及节点流,只需要关闭最外层的包装流即可,节点流不需要手动关闭if (bis != null) {try {bis.close();} catch (IOException e) {e.printStackTrace();}}}}
BufferedOutputStream
try {bos = new BufferedOutputStream(new FileOutputStream("chapter08/src/file05.txt"));// BufferedOutputStream的write()方法只能接受字节数组或单个字节。若直接传入字符串,需通过getBytes()将其转换为字节数组// 若字符串包含中文或其他多字节字符,必须通过getBytes()指定编码// bos.write("中文".getBytes(StandardCharsets.UTF_8)); // 显式指定编码bos.write("张三李四王麻子".getBytes());// 若数据已经是byte[]或单个字节(int类型),可直接写入byte[] data = {97, 98, 99};bos.write(data);bos.write(10); // int, double等直接写入数值会丢失精度(仅取最低8位)
// bos.write(zhangsan.getBytes());// 手动刷新bos.flush();
}
缓冲流的复制
try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream("chapter08/src/file05.txt"));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("chapter08/src/file06.txt"))){// 一边读一边写byte[] bytes = new byte[1024];int readCount = 0;while((readCount = bis.read(bytes)) != -1){bos.write(bytes, 0, readCount);}// 手动刷新bos.flush();
}
BufferedReader / BufferedWriter
bw = new BufferedWriter(new FileWriter("chapter08/src/file07.txt"));bw.write("hello");
bw.write(97);
bw.write("张三李四王麻子");
bw.flush();BufferedReader br = new BufferedReader(new FileReader("chapter08/src/file06.txt"));// 开始读(br.readLine()方法每次读取一行,如果读取不到任何数据,则返回null)
String s = null;
while((s = br.readLine()) != null) {System.out.println(s);
}br.close();
数据流(数据字节流)
DataOutputStream 数据字节输出流
// java.io.DataOutputStream:数据流(数据字节输出流)// 作用:将java程序中的数据直接写入到文件,写到文件中就是二进制。// DataOutputStream写的效率很高,原因是:写的过程不需要转码。// DataOutputStream写到文件中的数据,只能由DataInputStream来读取。
DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));// 准备数据
byte b = -127;
short s = 32767;
int i = 2147483647;
long l = 1111111111L;
float f = 3.0F;
double d = 3.14;
boolean flag = false;
char c = '国';
String str = "动力节点";// 开始写
dos.writeByte(b);
dos.writeShort(s);
dos.writeInt(i);
dos.writeLong(l);
dos.writeFloat(f);
dos.writeDouble(d);
dos.writeBoolean(flag);
dos.writeChar(c);
dos.writeUTF(str);dos.flush();
dos.close();
DataInputStream 数据字节输入流
// java.io.DataInputStream:数据流(数据字节输入流)// 作用:专门用来读取使用DataOutputStream流写入的文件。// 注意:读取的顺序要和写入的顺序一致。(要不然无法恢复原样。)// 创建数据字节输入流对象
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));// System.out.println(dis.readBoolean()); // 此时读到的是第一个二进制位// 开始读
// byte b = dis.readByte();
// short s = dis.readShort();
// int i = dis.readInt();
// long l = dis.readLong();
// float f = dis.readFloat();
// double d = dis.readDouble();
// boolean flag = dis.readBoolean();
// char c = dis.readChar();
// String str = dis.readUTF();
//
// System.out.println(b);
// System.out.println(s);
// System.out.println(i);
// System.out.println(l);
// System.out.println(f);
// System.out.println(d);
// System.out.println(flag);
// System.out.println(c);
// System.out.println(str);// 关闭流
dis.close();FileInputStream fis = new FileInputStream("data.txt");System.out.println(fis.read()); // 129 无符号值
System.out.println(fis.read()); // 127
System.out.println(fis.read()); // 255
System.out.println(fis.read()); // 127fis.close();
对象流
ObjectOutputStream (序列化)
// java.io.ObjectOutputStream:对象流(对象字节输出流)// 1. 它的作用是完成对象的序列化过程。// 2. 它可以将JVM当中的Java对象序列化到文件中/网络中。// 3. 序列化:将Java对象转换为字节序列的过程。(字节序列可以在网络中传输。)// 4. 序列化:serial// 创建“对象字节输出流”对象
// 包装流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("object"));// 准备一个Java对象
Date nowTime = new Date();
System.out.println(nowTime);// 序列化 serial
oos.writeObject(nowTime);// 刷新
oos.flush();// 关闭
oos.close();
序列化对象如果是多个对象的话,一般会序列化一个集合。
Date date1 = new Date();
Date date2 = new Date();
Date date3 = new Date();
Date date4 = new Date();
Date date5 = new Date();
Date date6 = new Date();List<Date> list = new ArrayList<>();list.add(date1);
list.add(date2);
list.add(date3);
list.add(date4);
list.add(date5);
list.add(date6);// 序列化
ObjectOutputStream dos = new ObjectOutputStream(new FileOutputStream("dates"));
dos.writeObject(list);// 反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("dates"));List<Date> dates = (List<Date>)ois.readObject();for(Date date : dates){System.out.println(date);
}dos.flush();
dos.close();
序列化自定义对象
- 凡是参与 序列化和反序列化 的对象必须实现 java.io.Serializable 可序列化的接口。
这个接口是一个标志接口,没有任何方法。只是起到一个标记的作用。
当java程序中类实现了Serializable接口,编译器会自动给该类添加一个“序列化版本号”:序列化版本号:serialVersionUID
序列化版本号的作用?
在Java语言中是如何区分class版本的?
首先通过类的名字,然后再通过序列化版本号进行区分的。
为了保证序列化的安全,只有同一个class才能进行序列化和反序列化。在java中是如何保证同一个class的?
类名 + 序列化版本号:serialVersionUID
Student对象
public class Student implements Serializable {// 建议:不是必须的。// 如果你确定这个类确实还是以前的那个类。类本身是合法的。没有问题。// 建议将序列化版本号写死!@Serialprivate static final long serialVersionUID = -7005027670916214239L;// 随着java版本的更改,源代码类中同样有着固定的序列化版本号 Alt+回车可以随机生成序列号private String name;private transient int age; // transient关键字修饰的属性不会参与序列化。反序列化为0private String addr;public String getAddr() {return addr;}public void setAddr(String addr) {this.addr = addr;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}
}
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("student"));Student stu = new Student("zhangsan", 20);oos.writeObject(stu);// 其实ObjectOutputStream中也有这些方法,和DataOutputStream中的方法一样。
oos.writeInt(100);
oos.writeBoolean(false);
oos.writeUTF("张三");oos.flush();oos.close();
ObjectInputStream 反序列化
专门完成反序列化的。(将字节序列转换成JVM当中的java对象。)
// 包装流
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("object"));// 读
Object o = ois.readObject();System.out.println(o);// 关闭
ois.close();
打印流PrintStream
专业的负责打印的流,(字节形式)。
PrintStream不需要手动刷新,自动刷新。
// 创建一个打印流对象
// 构造方法:PrintStream(OutputStream out)
// 构造方法:PrintStream(String fileName)
PrintStream ps = new PrintStream("log1");// 没有这样的构造方法。
//PrintStream ps2 = new PrintStream(new FileWriter(""));//PrintStream ps2 = new PrintStream(new FileOutputStream("log1")); // 这个支持// 打印流可以打印各种数据类型数据。
ps.print(100);
ps.println(false);
ps.println("abc");
ps.println('T');
ps.println(3.14);
ps.println("hell world");
ps.println(200);ps.println("\"hello world!\"");String name = "张三";
double score = 95.5;ps.printf("姓名:%s,考试成绩:%.2f", name, score); // %c表示字符 %d表示整数// 关闭
ps.close();
PrintWriter(字符形式)
需要手动刷新
/**
PrintWriter比PrintStream多一个构造方法:PrintStream构造方法:PrintStream(OutputStream)PrintWriter构造方法:PrintWriter(OutputStream)PrintWriter(Writer)
*/
// 创建字符打印流
//PrintWriter pw = new PrintWriter(new FileOutputStream("log2"));PrintWriter pw = new PrintWriter(new FileWriter("log2"), true); // 支持自动刷新// 打印
pw.println("world hello!!!");
pw.println("zhangsan hello!!!");// 刷新
//pw.flush();// 关闭
pw.close();
标准输入流
/*** 标准输入流:System.in* 1. 标准输入流怎么获取?* System.in 获取到的InputStream就是一个标准输入流* 2. 标准输入流是从哪个数据源读取数据的?* 控制台。* 3. 普通输入流是从哪个数据源读取数据的?* 文件或者网络或者其他.....* 4. 标准输入流是一个全局的输入流,不需要 手动关闭 。JVM退出的时候,JVM会负责关闭这个流。* 标准输入流是用来接收用户在控制台上的输入的; 普通输入流是用来获得文件或网络中的数据*/
public class SystemInTest {public static void main(String[] args) throws Exception{// 获取标准输入流对象。(直接通过系统类System中的in属性来获取标准输入流对象。)InputStream in = System.in;// 开始读byte[] bytes = new byte[1024];int readCount = in.read(bytes);for (int i = 0; i < readCount; i++) {System.out.println(bytes[i]); // 回车键还有个 10}}
}
修改数据源
// 改变数据源的。不让其从控制台读数据。也可以让其从文件中或网络中读取数据// 修改标准输入流的数据源。System.setIn(new FileInputStream("log2"));// 获取标准输入流
InputStream in = System.in;
从键盘接受用户的输入
public class SystemInTest03 {public static void main(String[] args) throws Exception{// 创建BufferedReader对象// InputStreamReader是转换流(包装流)// System.in是节点流BufferedReader br = new BufferedReader(new InputStreamReader(System.in));/*InputStream in = System.in;Reader reader = new InputStreamReader(in);BufferedReader br = new BufferedReader(reader);*/String s = null;while((s = br.readLine()) != null){if("exit".equals(s)){break;}System.out.println("您输入了:" + s);}/*Scanner scanner = new Scanner(System.in);String name = scanner.next();System.out.println("您的姓名是:" + name);*/}
}
File类
// java.io.File// File和IO流没有继承关系,父类是Object,通过File不能完成文件的读和写// File可能是一个文件,也可能是一个目录
public class FileTest01 {public static void main(String[] args) throws Exception{// 构造一个File对象File file = new File("e:/file");// 调用File对象的相关方法System.out.println(file.exists() ? "存在" : "不存在");// 如果不存在则以新文件的形式创建if(!file.exists()){// 以新的文件的形式创建出来file.createNewFile();}// 如果不存在则以目录的形式新建if(!file.exists()){file.mkdir();}// 构造一个File对象File file2 = new File("e:/a/b/c/d");// 如果不存在则以目录的形式新建if(!file2.exists()){file2.mkdirs();}}
}
public class FileTest02 {public static void main(String[] args) {// 构造一个File对象File file1 = new File("E:\\新建文本文档.txt");// 判断文件是否存在,如果存在则删除if(file1.exists()){file1.delete();}File file2 = new File("E:\\a\\b\\c\\d");if(file2.exists()){file2.delete(); // 删除目录}// 创建File对象File file3 = new File("log"); // 此时用的是相对路径// 获取绝对路径System.out.println("log文件的绝对路径:" + file3.getAbsolutePath());// 获取名字System.out.println("文件名:" + file3.getName());// 创建File对象File file4 = new File("E:\\a\\b\\c");// 获取父路径System.out.println("父路径:" + file4.getParent()); // E:\a\b// 判断该路径是否为绝对路径System.out.println(file4.isAbsolute()? "是绝对路径" : "不是绝对路径"); // 是// 判断某个File是目录还是文件。File file5 = new File("E:\\file");System.out.println(file5.isDirectory() ? "是目录" : "是文件");System.out.println(file5.isFile() ? "是文件" : "是目录");// 判断该文件是否是隐藏文件System.out.println(file1.isHidden() ? "隐藏文件" : "非隐藏文件");// 获取文件的最后修改时间点long l = file1.lastModified();Date time = new Date(l);SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");String str = sdf.format(time);System.out.println("文件最后修改时间点:" + str);// 获取文件的大小:总字节数System.out.println(file1.getName() + "文件的总字节数:" + file1.length() + "字节"); // abcdef是六个字节// 重命名 会将file1文件移动到当前项目下,并重命名为file2File file2 = new File("file2");file1.renameTo(file2);}
}
常用方法
File类的常用方法:File[] listFiles();
public class FileTest04 {public static void main(String[] args) {File file = new File("D:\\Java\\JavaMe\\Java2025\\chapter08\\src");// 获取所有的子文件,包括子目录。 但不会获取子目录中的文件File[] files = file.listFiles();// 遍历数组for(File f : files){System.out.println(f.getName());}System.out.println("=====================================");File file1 = new File("D:\\Java\\JavaMe\\Java2025\\chapter08\\src");File[] files1 = file1.listFiles(new FilenameFilter() {@Overridepublic boolean accept(File dir, String name) {/*if (name.endsWith(".mdj")) {return true;}return false;*/return name.endsWith(".txt");}});for(File f : files1){System.out.println(f.getName());}}
}
目录的递归拷贝
public static void main(String[] args) {// 拷贝源File src = new File("D:\\Java\\Java学习\\Java2025\\chapter08\\src\\"); //// 拷贝目标File dest = new File("D:\\a\\b\\c"); // 桌面:\a\b\c\powernode\02-JavaSE\code\chapter01\A.java// 开始拷贝copy(src, dest);
}
Copy函数
private static void copy(File src, File dest) {if(src.isFile()){// 是文件的时候要拷贝。try(FileInputStream in = new FileInputStream(src);// .substring(2) 指的是从第三个字符位置开始FileOutputStream out = new FileOutputStream(dest.getAbsoluteFile() + src.getAbsolutePath().substring(2))){// 开始拷贝byte[] bytes = new byte[1024 * 1024];int readCount = 0;while((readCount = in.read(bytes)) != -1){out.write(bytes, 0, readCount);}out.flush();}catch(IOException e){e.printStackTrace();}return;}// 假设src是一个目录// 程序能够执行到此处一定是一个目录// 创建目录File newDir = new File(dest.getAbsolutePath() + src.getAbsolutePath().substring(2));if(!newDir.exists()){newDir.mkdirs();}File[] files = src.listFiles();for (File file : files){//System.out.println(file.getAbsolutePath());copy(file, dest);}
}
压缩流
文件压缩GZIPOutputStream(xxx.gz)
// 创建文件字节输入流(读某个文件,这个文件将来就是被压缩的。)
FileInputStream in = new FileInputStream("chapter08/file09.txt");// 创建一个GZIP压缩流对象
GZIPOutputStream gzip = new GZIPOutputStream(new FileOutputStream("chapter08/file09.txt.gz"));// 开始压缩(一边读一边读)
byte[] bytes = new byte[1024];
int readCount = 0;
while((readCount = in.read(bytes)) != -1){gzip.write(bytes, 0, readCount);
}// 非常重要的代码需要调用
// 刷新并且最终生成压缩文件。
gzip.finish();// 关闭流
in.close();
gzip.close();
解压缩GZIPInputStream(xxx.gz)
// 创建GZIP解压缩流对象
GZIPInputStream gzip = new GZIPInputStream(new FileInputStream("chapter08/file09.txt.gz"));// 创建文件字节输出流
FileOutputStream out = new FileOutputStream("chapter08/file09.txt");// 一边读一边写
byte[] bytes = new byte[1024];
int readCount = 0;
while((readCount = gzip.read(bytes)) != -1){out.write(bytes, 0, readCount);
}// 关闭流
gzip.close();
// 节点流关闭的时候会自动刷新,包装流是需要手动刷新的。(补充的知识点。)
out.close();
属性配置文件
使用Properties集合类 + IO流来读取属性配置文件。
将属性配置文件中的配置信息加载到内存中。
(属性配置文件中配置信息为key=value, key不能重复,重复会对key的value进行覆盖)
// 创建输入流对象
//FileReader reader = new FileReader("chapter08/src/db.properties");// getContextClassLoader():获取当前线程的类加载器
// getResource("db.properties"):从类路径根目录(通常是 src 或 resources 目录)查找文件
// getPath():返回文件的系统路径
String path = Thread.currentThread().getContextClassLoader().getResource("db.properties").getPath(); // 这个地址是从类的根目录开始
FileReader reader = new FileReader(path);// 创建一个Map集合(属性类对象)
Properties pro = new Properties();// 加载:将jdbc.properties文件中的配置信息加载到Properties对象中。
pro.load(reader);// 获取所有key
Enumeration<?> names = pro.propertyNames(); // 返回所有键的枚举
while (names.hasMoreElements()) {String name = (String)names.nextElement();String value = pro.getProperty(name); // 根据键获取值System.out.println(name + "=" + value);
}// 通过key来获取value
String driver = pro.getProperty("driver");
String url = pro.getProperty("url");
String user = pro.getProperty("user");
String password = pro.getProperty("password");System.out.println(driver);
System.out.println(url);
System.out.println(user);
System.out.println(password);// 关闭输入流
reader.close();
绑定配置文件
使用JDK中提供的资源绑定器来绑定属性配置文件。
// 获取资源绑定器对象
// 使用这个工具要求文件也必须是一个属性配置文件。xxx.properties
// 无需处理文件路径和流,ResourceBundle 会自动处理此编码的读取,避免中文等非ASCII字符乱码
// ResourceBundle 默认不支持UTF-8,需通过工具转义字符
ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.javase.io.jdbc"); // 将其当作一个类
//ResourceBundle bundle = ResourceBundle.getBundle("com/powernode/javase/io/jdbc");// 这个获取的是类的根路径下的jdbc.properties文件。 src里面第一层
//ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
// 这个代码的意思是从类的根路径下找db.properties文件。
//ResourceBundle bundle = ResourceBundle.getBundle("db");// 以下两行都是错误的:资源找不到。
//ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.javase.io.db.properties");
//ResourceBundle bundle = ResourceBundle.getBundle("com/powernode/javase/io/db.properties");// 通过key获取value
String driver = bundle.getString("driver");
String url = bundle.getString("url");
String user = bundle.getString("user");
String password = bundle.getString("password");System.out.println(driver);
System.out.println(url);
System.out.println(user);
System.out.println(password);
字节数组流
向内存中的字节数组写数据。
// ByteArrayOutputStream的基本用法。
ByteArrayOutputStream baos = new ByteArrayOutputStream(); //节点流// 开始写
baos.write(1);
baos.write(2);
baos.write(3);// 怎么获取内存中的哪个byte[]数组呢?
byte[] byteArray = baos.toByteArray();
for (byte b : byteArray){System.out.println(b);
}
装饰器设计模式
装饰器设计模式: 包装流和节点流是可以随意组合的。
ObjectOutputStream(包装流)和ByteArrayOutputStream(节点流)进行组合。
ByteArrayOutputStream和ByteArrayInputStream都是内存操作流(节点流),不需要打开和关闭文件等操作。能够方便读写字节数组、图像数据等内存中的数据。程序和内存中的字节数组之间的写入和读取。
// 节点流
ByteArrayOutputStream baos = new ByteArrayOutputStream();
// 包装流
ObjectOutputStream oos = new ObjectOutputStream(baos);// 开始写
oos.writeInt(100);
oos.writeBoolean(false);
oos.writeDouble(3.14);
oos.writeUTF("动力节点");
oos.writeObject(new Date());// 使用了包装流就需要手动刷新一下。
// 但是真正的流是ByteArrayOutputStream所以不用关闭
oos.flush();// 获取内存中的大byte数组
byte[] byteArray = baos.toByteArray();
for(byte b : byteArray){System.out.println(b);
}
// 使用ByteArrayInputStream将上面这个byte数组恢复。
// 读的过程,读内存中的大byte数组。
// 节点流
ByteArrayInputStream bais = new ByteArrayInputStream(byteArray);
// 包装流
ObjectInputStream ois = new ObjectInputStream(bais);// 开始读
System.out.println(ois.readInt());
System.out.println(ois.readBoolean());
System.out.println(ois.readDouble());
System.out.println(ois.readUTF());
System.out.println(ois.readObject());
装饰者设计模式详解
// 接口
public interface Flyable {void fly();
}// 被装饰者
public class Cat implements Flyable{@Overridepublic void fly() {System.out.println("Cat fly!");}
}public class Bird implements Flyable{@Overridepublic void fly() {System.out.println("Bird fly!");}
}
装饰者
/*** 所有的装饰者应该有一个共同的父类。这个父类通常是一个抽象类。* 所有装饰者的头领。*/
public abstract class FlyableDecorator implements Flyable{private Flyable flyable; // 实现抽象类,并且装饰抽象类// FlyableDecorator是一个装饰者,在其的构造方法中传入一个被装饰者public FlyableDecorator(Flyable flyable) {this.flyable = flyable;}@Overridepublic void fly() {flyable.fly();}
}
// 如果实现Flyable接口,那么就必须实现其中的方法
public class LogDecorator extends FlyableDecorator{public LogDecorator(Flyable flyable) {super(flyable);}@Overridepublic void fly() {Date now = new Date();SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");System.out.println(sdf.format(now) + ": 起飞前的准备");super.fly();now = new Date();System.out.println(sdf.format(now) + ": 安全降落");}
}
public class TimerDecorator extends FlyableDecorator{public TimerDecorator(Flyable flyable){super(flyable);}@Overridepublic void fly() {// 这里可以添加代码(前增强)long begin = System.currentTimeMillis();super.fly();// 这里可以添加代码(后增强)long end = System.currentTimeMillis();System.out.println("耗时"+(end - begin)+"毫秒");}
}
功能测试
/*** 0. 装饰器设计模式的主要目标:在松耦合的前提下,能够完成功能的扩展。* 1. 在装饰器设计模式中有两个非常重要的角色:装饰者,被装饰者。* 2. 装饰器设计模式当中要求:装饰者 与 被装饰者 应实现同一个接口/同一些接口,继承同一个抽象类....* 3. 为什么装饰者 与 被装饰者 要实现同一个接口呢?* 因为实现了同一个接口之后,对于客户端程序来说,使用装饰者的时候就向在使用被装饰者一样。* 4. 装饰者含有被装饰者的引用。(A has a B。尽量使用has a【耦合度低一些】。不要使用is a。)* */
//Flyable flyable1 = new Cat();
//Flyable flyable1 = new FlyableDecorator(new Cat());Flyable flyable1 = new TimerDecorator(new Cat());
//Flyable flyable1 = new LogDecorator(new Cat());// BufferedReader装饰者,new FileReader("")就是被装饰者
//BufferedReader br = new BufferedReader(new FileReader(""));
flyable1.fly();//Flyable flyable2 = new Bird();
//Flyable flyable2 = new TimerDecorator(new Bird());
Flyable flyable2 = new LogDecorator(new Bird());
flyable2.fly();
深克隆
/*** 之前的深克隆是重写clone()方法* 使用ByteArrayOutputStream和ByteArrayInputStream直接复制的对象就是一个深克隆。* 将要克隆的Java对象写到内存中的字节数组中,再从内存中的字节数组中读取对象,读取到的就是一个深克隆** 除了这个方式之外,对象的拷贝方式:* 1. 调用Object的clone方法,默认是浅克隆,需要深克隆的话,就需要重写clone方法。* 2. 可以通过序列化和反序列化完成对象的克隆。*/
// 准备对象
Address addr = new Address("北京", "朝阳");
User user = new User("zhangsan", 20, addr);// 将Java对象写到一个byte数组中。
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);oos.writeObject(user);oos.flush();// 从byte数组中读取数据恢复java对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);// 这就是经过深拷贝之后的新对象
// 深克隆, user并不受影响
User user2 = (User) ois.readObject();user2.getAddr().setCity("南京");System.out.println(user);
System.out.println(user2);