目录
4.1 BufferedInputStream(字节缓冲输入流)
1.IO流概述
IO流的作用?
IO流用来读写文件数据
我们对于IO流的学习分两个步骤:
- 先搞清楚IO流的分类、体系
- 再挨个学习每个IO流的作用、用法
2.IO流-字节流
2.1 字节读取方法
package com.itheima.d2_byte_stream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class FileInputStreamDemo1 {
public static void main(String[] args) throws Exception {
// 目标:掌握文件字节输入流每次读取一个字节的形式。
// 1、创建一个文件字节输入流管道(对象)与源文件接通。
//会报一个文件找不到异常提示,所以要把异常抛出
//InputStream is = new FileInputStream(new File("day09-charset-io\\src\\dlei01.txt"));//完整写法
//简洁写法,不需要写new File创建对象,在源码中已经帮助我们创建
InputStream is = new FileInputStream("D:\\IDEAProjects\\javasepromax\\day09-charset-io\\src\\dlei01.txt");
// 2、public int read() 每次读取一个字节返回,如果没有字节可读了,返回-1
// int b1 = is.read();
// //返回字节数
// System.out.println(b1);
// //强转为char查看内容
// System.out.println((char) b1);
//
// int b2 = is.read();
// System.out.println((char) b2);
//
// int b3 = is.read();
// System.out.println(b3);
is.close();
// 3、使用循环改进每次读取一个字节。
InputStream is2 = new FileInputStream(new File("day09-charset-io\\src\\dlei02.txt"));
int b; // 定义一个变量用于记住每次读取的字节。
while ((b = is2.read()) != -1) {
System.out.print((char) b);
}
// 拓展了:每次读取一个字节存在很多问题。
// 1、无法避免读取汉字输出乱码的问题,会截断汉字的字节。
// 2、每次读取一个字节,性能极差!
is2.close();
}
}
2.2 字节流每次读取多个字节
package com.itheima.d2_byte_stream;
import java.io.FileInputStream;
import java.io.InputStream;
public class FileInputStreamDemo2 {
public static void main(String[] args) throws Exception {
// 目标:掌握文件字节输入流每次读取多个字节。
// 1、创建一个文件字节输入流管道与源文件接通
// InputStream is = new FileInputStream(new File("day09-charset-io\\src\\dlei03.txt"));
InputStream is = new FileInputStream("day09-charset-io\\src\\dlei03.txt"); // 简化写法
// 2、每次读取一个字节数组的字节,会返回读取的字节个数,没有字节可读返回-1:public int read(byte[] buffer)
byte[] buffer = new byte[3];
int len = is.read(buffer);
// String rs = new String(buffer);
// System.out.println("读取的内容:" + rs);
System.out.println("读取的内容" + new String(buffer));
System.out.println("读取了几个字节:" + len);
// 第一桶水: buffer = [a, b, c]
// 第二桶水: buffer = [3, m, c]
int len2 = is.read(buffer);
// 规范:读取多少就倒出多少。
String rs2 = new String(buffer, 0, len2);
System.out.println("读取了几个字节:" + len2);
System.out.println("读取的内容:" + rs2);
// 没有字节可读返回-1
int len3 = is.read(buffer);
System.out.println(len3);
System.out.println("----------------------------------------------------------------");
// 使用循环改进读取方案。
InputStream is2 = new FileInputStream("day09-charset-io\\src\\dlei04.txt"); // 简化写法
byte[] buffer2 = new byte[3];
int len4; // 用于记录每次读取的字节个数
while ((len4 = is2.read(buffer2)) != -1){
String rs3 = new String(buffer2, 0, len4);
System.out.print(rs3);
}
// 拓展:每次读取多个字节的优势和问题
// 优势:性能得到提升。读取性能较好!
// 缺点:读取汉字输出依然无法避免乱码!!
//用于关闭输入流,释放与该流关联的系统资源,确保系统资源被正确回收
is2.close();
}
}
对于这段代码,我们需要了解以下几个问题:
1.read(byte[] buffer)的工作机制
- 缓冲区重用:buffer数组是重复使用的。每次调用read(buffer)时,新读取的字节会覆盖数组中的旧内容,但未被覆盖的部分会保留上次的数据
- 返回值len:表示本次实际读取的字节数。当文件剩余内容不足数组大小时,len会小于数组长度。可以用作下面new String(buffer,0,len2)的一个参数
2. 为什么需要new String(buffer,0,len2)?
假设文件内容为"abc3m",分两次读取,上面代码中定义byte[ ] buffer = new byte[3]; 每次读取三个字节的内容。
- 第一次读取:读取3个字节[a,b,c],此时len2 = 3。
- 第二次读取:仅剩余2个字节[3,m],此时len2 = 2。
- 若直接转换为整个数组:new String(buffer)会将数组中的所有内容(包括上次残留的c)转换为字符串,得到“3mc”,导致结果错误。
- 正确做法:new String(buffer,0,len2)仅转换有效部分[3,m],得到正确结果“3m”。
3.len2表示需要转换的有效字节数,即实际从文件中读取的字节数量,避免将数组中的残留部分包含进来,这也就是为什么说:读取多少倒出多少
2.3 一次读取完全部字节
package com.itheima.d2_byte_stream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public class FileInputStreamDemo3 {
public static void main(String[] args) throws Exception {
// 目标:学会如何使用文件字节输入流一次性读取完文本的全部字节,以此避免读取汉字乱码的问题。
// 1、创建一个文件字节输入流管道与源文件接通
InputStream is = new FileInputStream("day09-charset-io\\src\\dlei05.txt");
// // 2、定义一个字节数组与源文件一模一样大。
File f = new File("day09-charset-io\\src\\dlei05.txt");
// long size = f.length();
// byte[] buffer = new byte[(int) size];
// System.out.println("文件大小:" + size);
// // 3、一次性读取
// int len = is.read(buffer);
// System.out.println("读取的字节数:" + len);
//
// String rs = new String(buffer);
// System.out.println(rs);
System.out.println("--------------------------------------------------");
// Java官方提供了API;readAllBytes :一次性读取完文件的全部字节
//readAllBytes()方法不适合读取大文件,小文件(几十MB以内)更加高效
//大文件(几百MB以上):必须使用缓冲流分块读取,避免内存溢出
byte[] buffer = is.readAllBytes();
String rs = new String(buffer);
System.out.println(rs);
is.close();
}
}
为什么这个语句 byte[] buffer = new byte[(int) size];为什么要用int来强制转换?
在Java中,数组的长度必须是int类型,而File.length()返回的是long类型。这是因为文件大小可能超过int的最大值(2^31-1,约 2GB),但数组索引必须在int范围内。
- 类型不匹配:File.length()返回long类型,而数组初始化需要int。
- Java语法限制:数组长度必须是int,即使文件大小不超过int范围也需要显式转换
2.4 字节输出流
package com.itheima.d2_byte_stream;
import java.io.FileOutputStream;
import java.io.OutputStream;
public class FileOutputStreamDemo4 {
public static void main(String[] args) throws Exception {
// 目标:文件字节输出流的使用。
// 1、创建一个文件字节输出流管道与目标文件接通。
// OutputStream os = new FileOutputStream("day09-charset-io/src/dlei06-out.txt"); // 覆盖管道
OutputStream os = new FileOutputStream("day09-charset-io/src/dlei06-out.txt", true); // 追加管道
// 2、开始写字节数据出去。
// public void write(int a): 每次写出去一个字节 。
os.write(97);
os.write('a');
// os.write('徐'); // 汉字是三个字节 [ooo] 只写出去第一个字节,会乱码!
os.write("\r\n".getBytes()); // 换行符号
// public void write(byte[] buffer): 每次写一个字节数组的数据出去。
byte[] bytes = "abc我爱你中国".getBytes();
os.write(bytes);
os.write("\r\n".getBytes()); // 换行符号
// public void write(byte[] buffer, int pos, int len): 写一个字节数组的一部分出去。
// 参数一:字节数组
// 参数二:写出去的第一个字节的索引
// 参数三:总共写出去多少个字节
os.write(bytes, 3, 9);
os.write("\r\n".getBytes()); // 换行符号
//IO流管道属于系统资源,会占用内存和相应的IO资源
// 用完必须关闭,以释放占用的系统资源。
// 为了节省系统资源,提升计算机性能。
//os.flush();//刷新缓存中的数据到磁盘文件中去
os.close();//关闭包含了刷新
}
}
Java文件缓存机制
在Java IO操作中,文件缓存机制是一种通过内存临时存储数据,减少磁盘IO次数以提升性能的技术,当程序进行文件读写时,数据不会直接写入磁盘或从磁盘读取,而是先经过内存缓冲区过渡。
核心原理
写入数据:数据先写入内存缓冲区,当缓冲区满或手动刷新时,才批量写入磁盘。
读取数据:从磁盘读取一批数据到缓冲区,后续读取直接从缓冲区获取。
为什么需要缓存?
- 磁盘IO速度(约100MB/s)远低于内存操作速度(约 25GB/s)
- 减少磁盘寻道和数据传输的开销
- 批量操作比单次操作效率更高
关键方法与缓存控制
1.flush () 方法
- 强制将缓冲区数据写入磁盘
- 适用于需要立即看到写入效果的场景(如日志文件)
2. close () 方法
- 先执行 flush (),再关闭流释放资源,仅刷新数据,流仍可继续使用
- 必须调用,否则缓冲区数据可能丢失,刷新并释放资源,流不可再使用
2.5 字节流复制文件
package com.itheima.d2_byte_stream;
import java.io.*;
public class CopyTest5 {
public static void main(String[] args) {
try {
// 目标:使用字节流完成文件的复制。
// E:\resource\aaa.png ===> D:\resource\meinv.png
// 1、创建一个文件字节输入流管道与源文件接通
InputStream is = new FileInputStream("D:\\resource\\aaa.png");
// 2、创建一个文件字节输出流管道与目标文件接通。
OutputStream os = new FileOutputStream("D:\\resource\\meinv.png");
// 3、准备一个字节数组(一个桶)
byte[] buffer = new byte[1024]; // 1KB
// 4、开始转移数据 1024 + 1024 + 3
int len; // 记录每次读取的字节个数。
while ((len = is.read(buffer)) != -1) {
// 5、把读取到的源文件字节,读取多少写出去到目标文件多少
os.write(buffer, 0, len);
}
// 6、释放资源
os.close();
is.close();
System.out.println("复制完成!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
2.6 资源释放的方式
package com.itheima.d3_finally;
public class Test1 {
public static void main(String[] args) {
// 目标:认识finally的作用。
//try后或者catch后,最终一定要跑一次,除非JVM挂了。
try {
int c = 10 / 2;
System.out.println(c);
// return;
}catch (Exception e){
e.printStackTrace();
}finally {
// 一定要执行一次,除非干掉JVM!
System.out.println("=======finally=========");
}
System.out.println(add(110, 20));
}
public static int add(int a, int b){
try {
return a + b;
} catch (Exception e) {
e.printStackTrace();
return 0;
} finally {
// finally中不要return数据,会覆盖前面的return数据
// return 666;
}
}
}
package com.itheima.d3_finally;
import java.io.*;
public class Test2 {
public static void main(String[] args) {
InputStream is = null;
OutputStream os = null;
try {
// System.out.println(10 / 0);
// 目标:使用try-catch-finally释放资源
// E:\resource\aaa.png ===> D:\resource\meinv.png
// 1、创建一个文件字节输入流管道与源文件接通
is = new FileInputStream("E:\\resource\\aaa.png");
// 2、创建一个文件字节输出流管道与目标文件接通。
os = new FileOutputStream("D:\\resource\\meinv.png");
// 3、准备一个字节数组(一个桶)
byte[] buffer = new byte[1024]; // 1KB
// 4、开始转移数据 1024 + 1024 + 3
int len; // 记录每次读取的字节个数。
while ((len = is.read(buffer)) != -1) {
// 5、把读取到的源文件字节,读取多少写出去到目标文件多少
os.write(buffer, 0, len);
}
System.out.println("复制完成!");
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6、释放资源(最终一定会执行)
try {
if(os != null) os.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
if(is != null) is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
package com.itheima.d3_finally;
import java.io.*;
public class Test3 {
public static void main(String[] args) {
//目标:JDK 7 开始资源释放的新方式:try-with-resource
try (
// 特点:这里只能放资源,并且程序执行完毕后,自动调用他们的close方法关闭!
// int age = 12;
// 1、创建一个文件字节输入流管道与源文件接通
InputStream is = new FileInputStream("E:\\resource\\aaa.png");
// 2、创建一个文件字节输出流管道与目标文件接通。
OutputStream os = new FileOutputStream("D:\\resource\\meinv.png");
//造一个资源实现AutoCloseable接口,验证是否真的会自动调用close()方法
MyConnection conn = new MyConnection("数据库1号链接");
){
// 目标:使用try-with-resource释放资源
// 3、准备一个字节数组(一个桶)
byte[] buffer = new byte[1024]; // 1KB
// 4、开始转移数据 1024 + 1024 + 3
int len; // 记录每次读取的字节个数。
while ((len = is.read(buffer)) != -1) {
// 5、把读取到的源文件字节,读取多少写出去到目标文件多少
os.write(buffer, 0, len);
}
System.out.println("复制完成!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
//造一个资源
class MyConnection implements AutoCloseable{
private String name;
public MyConnection(String name) {
this.name = name;
}
@Override
public void close() throws Exception {
System.out.println(name + "被自动关闭了~~");
}
}
2.7 复制文件夹案例
能够使用递归调用定义方法,复制文件夹。
package work.Test1;
import java.io.*;
public class test1 {
public static void main(String[] args) throws IOException {
//创建File对象srcDir,代表源文件夹
File srcDir = new File("C:\\Users\\Lenovo\\Desktop\\aaa\\day09-字符集、IO流(一)");
//创建File对象desDir,代表目标文件夹
File desDir = new File("C:\\Users\\Lenovo\\Desktop\\aaa\\新建文件夹");
//调用copyDir2Dir方法,传递源文件夹和目标文件夹,完成文件夹的复制
copyDir2Dir(srcDir,desDir);
System.out.println("文件夹复制完毕!!!");
}
//1.定义复制文件到文件夹的方法copyFile2Dir
public static void copyFile2Dir(File file,File dir) throws IOException {
//1.1 创建文件字节输入流FileInputStream类的对象,绑定文件资源
FileInputStream is = new FileInputStream(file);
//1.2创建输出流FileOutputStream类的对象,绑定目标文件
FileOutputStream os = new FileOutputStream(dir);
//1.3定义一个byte数组,保存每次读取到的字节的内容
byte[] bytes = new byte[1024 * 10];
//1.4定义int变量,保存每次读取到的字节数量
int len;
//1.5根据目标文件夹和源文件,创建目标文件,确保是在目标文件夹里创建和源文件同名的文件
//创建一个新的File对象,表示目标文件的路径
//dir这是一个File对象,表示目标文件夹。
//file.getName()这是源文件的文件名,此方法会返回源文件的名称部分
//这段代码的作用就是把目标文件夹和源文件的名称动态组合起来,形成完整的目标文件路径
File destFile = new File(dir,file.getName());
//1.6循环读取源文件并写入到目标文件
while ((len = is.read(bytes)) != -1) {
os.write(bytes,0,len);
}
//1.7 关闭流释放资源
is.close();
os.close();
}
//2.定义复制文件夹到文件夹的方法copyDir2Dir
public static void copyDir2Dir(File srcDir, File desDir) throws IOException {
//2.1 在目标文件夹中创建源文件夹
File newDir = new File(desDir, srcDir.getName());
if (!newDir.exists()) {
newDir.mkdirs();
}
//2.2获取文件夹中的所有文件和文件夹对应的File对象数组
File[] files = srcDir.listFiles();
//2.3判断,如果File对象数组是null或者没有内容,结束方法
if (files == null || files.length == 0) {
return;
}
//2.4遍历File对象数组
for (File file : files) {
//2.5判断,如果当前File对象是文件,调用copyFile2Dir方法,完成文件夹复制
if (file.isFile()) {
copyFile2Dir(file,newDir);
}else {
//2.6如果当前File对象是文件夹,递归调用当前方法,完成文件夹的复制
copyDir2Dir(file,newDir);
}
}
}
}
与上面复制图片到文件夹对比:
当前案例为什么需要 new File(dir, file.getName())
?
目标路径不包含文件名:
你虽然指定了目标文件夹(如D:\destination\
),但文件夹路径本身不包含文件名。例如:源文件是
C:\source\test.txt
目标文件夹是
D:\destination\
最终需要生成的目标文件是
D:\destination\test.txt
动态生成文件名:
代码需要根据源文件的名称(test.txt
),在目标文件夹中创建同名文件。因此必须通过file.getName()
动态获取文件名,并与目标文件夹组合。
上面案例复制单个文件案例(直接指定完整路径包括文件名称 .png ):
为什么不需要动态生成文件名?
完整路径已硬编码:
在创建FileOutputStream
时,你已经直接指定了完整的目标路径(D:\resource\meinv.png
),包括文件夹路径和文件名。因此不需要额外的步骤来组合路径。
总结:
当前案例 :目标路径是文件夹,需要动态补全文件名(否则系统不知道该把文件复制成什么名字)。
上面案例:目标路径已经是完整的文件路径(含文件名),直接写入即可。
2.8 删除文件夹案例
package com.itheima.d8_demo;
import java.io.File;
public class DeleteDirDemo2 {
public static void main(String[] args) {
// 目标:删除文件夹。
deleteDir(new File("D:/resource"));
}
public static void deleteDir(File dir){
// 1、不删除的情况
if(dir == null || !dir.exists()) return;
// 2、如果是文件直接删除。
if(dir.isFile()) {
dir.delete();
return;
}
File[] files = dir.listFiles();
// 3、文件夹没有权限拿文件,直接不删除!
if(files == null){
return;
}
// 4、空文件夹 直接删除
if(files.length == 0){
// 空文件夹 直接删除
// Java提供的File.delete()方法只能删除空文件夹
dir.delete();
return;
}
// 5、遍历全部一级文件对象,删除他们,再删除文件夹自己。
for (File file : files) {
if(file.isFile()){
file.delete();
}else {
deleteDir(file);
}
}
dir.delete(); // 删除自己!
}
}
下面解释一下这段代码的删除逻辑:
边界条件检查:
- 如果传入的目录为null或不存在,直接返回
- 如果传入的是文件而非目录,直接删除并返回
在遍历目录中的子项时:
- 遇到一个文件,直接调用File.delete()删除文件
- 遇到一个空文件夹时,直接调用File.delete()删除空文件夹
- 遇到非空文件夹时,调用自身方法进行递归
- 递归删除子项后,文件夹变为空
- 最后用File.delete()删除自身
3.IO流-字符流
字节流:适合复制文件等,不适合读写文本文件
字符流:适合读写文本文件内容
3.1 FileReader(文件字符输入流)
文件字符输入流的好处是可以避免读汉字的时候乱码
package com.itheima.d1_char_stream;
import java.io.FileReader;
import java.io.Reader;
public class FileReaderDemo1 {
public static void main(String[] args) throws Exception {
// 目标:文件字符输入流的使用:每次读取一个字符。
// 1、创建文件字符输入流管道与源文件接通
Reader fr = new FileReader("day10-io-code\\src\\dlei01.txt");
// 2、读取一个字符编号回来,没有字符可读返回-1: public int read()\
// int c1 = fr.read();
// System.out.println((char) c1);
//
// int c2 = fr.read();
// System.out.println((char) c2);
//
// int c3 = fr.read();
// System.out.println(c3);
// 3、使用循环解决
int c;
while ((c = fr.read()) != -1){
char ch = (char) c;
System.out.print(ch);
}
fr.close();
// 拓展:
// 是否解决了乱码问题:解决了!
// 性能较差,依然是个垃圾代码!
}
}
上面这段代码性能并不好,我们如何一次读多个字符呢?用字符数组来读取文本内容
package com.itheima.d1_char_stream;
import java.io.FileReader;
import java.io.Reader;
public class FileReaderDemo2 {
public static void main(String[] args) {
// 目标:文件字符输入流的使用:每次读取多个字符。
try (
// 1、创建文件字符输入流管道与源文件接通
//注意:字符输入流读的就是字符,并不是字节
Reader fr = new FileReader("day10-io-code\\src\\dlei01.txt");
){
// 2、定义一个字符数组用于读取多个字符
char[] buffer = new char[1024];
int len; // 记住每次读取了多少个字符
while ((len = fr.read(buffer)) != -1){
String rs = new String(buffer, 0, len);
System.out.print(rs);
}
// 拓展:
// 可以避免乱码吗? 可以
// 性能可以吗? 可以
// 这是截至此刻我们学过的读取文本内容最好的方案。
}catch (Exception e){
e.printStackTrace();
}
}
}
3.2 FileWrite(文件字符输出流)
package com.itheima.d1_char_stream;
import java.io.FileWriter;
public class FileWriteDemo3 {
public static void main(String[] args) {
try (
// 目标:掌握文件字符输出流的使用。
// 1、创建一个文件字符输出流管道与目标文件接通
// Writer fw = new FileWriter("day10-io-code/src/dlei02-out.txt"); // 覆盖
FileWriter fw = new FileWriter("day10-io-code/src/dlei02-out.txt", true); // 追加
) {
// 2、写字符数据出去。
// a.写一个字符出去 : public void write(int c)
fw.write(97);
fw.write('磊');
fw.write("\r\n"); // 换行
// b.写一个字符串出去:public void write(String s)
fw.write("abc我爱你中国666");
fw.write("\r\n"); // 换行
// c、写字符串的一部分出去 : public void write(String s, int pos ,int len)
fw.write("abc我爱你中国666",3, 3);
fw.write("\r\n"); // 换行
// d.写一个字符数组出去:public void write(char[] chars)
//转换为字符数组用的是toCharArray(),而字节数组用的是getByte()
char[] chars = "abc我爱你中国666".toCharArray();
fw.write(chars);
fw.write("\r\n"); // 换行
// e、写一个字符数组的一部分出去:public void write(char[] chars, int pos ,int len)
fw.write(chars, 6, 2);
fw.write("\r\n"); // 换行
// fw.flush(); // 刷新
// fw.close(); // 关闭包含刷新
}catch (Exception e){
e.printStackTrace();
}
}
}
在Java代码里使用\r\n进行换行,是和不同操作系统的文本文件换行符规范相关的,是为了不同平台之间的兼容性。
4.IO流-缓冲流
缓冲流的作用是对原始流进行包装,以提高原始流读写数据的性能
4.1 BufferedInputStream(字节缓冲输入流)
package com.itheima.d2_buffer_stream;
import java.io.*;
public class BufferedInputStreamDemo1 {
public static void main(String[] args) {
// 目标:使用字节缓冲流提升原始字节流读写数据的性能的。
try (
// 1、创建字节输入流管道与源文件接通
InputStream is = new FileInputStream("E:\\resource\\meinv2.jpg");
// 使用高级的缓冲字节输入流包装低级的字节输入流
InputStream bis = new BufferedInputStream(is);//多态
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream("E:\\resource\\meinv2-bak2.txt");
// 使用高级的缓存字节输出流包装低级的字节输出流
OutputStream bos = new BufferedOutputStream(os);//多态
) {
// 3、准备一个字节数组
byte[] buffer = new byte[1024];
// 4、转移数据
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
4.2 BufferedReader(字符缓冲输入流)
4.4 BufferedWriter(字符缓冲输出流)
package com.itheima.d2_buffer_stream;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.Writer;
public class BufferedWriterDemo3 {
public static void main(String[] args) {
try (
// 目标:掌握缓冲字符输出流的使用。
// 1、创建一个文件字符输出流管道与目标文件接通
Writer fw = new FileWriter("day10-io-code/src/dlei05-out.txt"); // 覆盖
BufferedWriter bw = new BufferedWriter(fw);
) {
// 2、写字符数据出去。
// a.写一个字符出去 : public void write(int c)
bw.write(97);
bw.write('磊');
bw.newLine(); // 换行,与bw.write("\r\n")作用相同
// b.写一个字符串出去:public void write(String s)
bw.write("abc我爱你中国666");
bw.newLine(); // 换行
// c、写字符串的一部分出去 : public void write(String s, int pos ,int len)
bw.write("abc我爱你中国666",3, 3);
bw.newLine(); // 换行
// d.写一个字符数组出去:public void write(char[] chars)
char[] chars = "abc我爱你中国666".toCharArray();
bw.write(chars);
bw.newLine(); // 换行
// e、写一个字符数组的一部分出去:public void write(char[] chars, int pos ,int len)
bw.write(chars, 6, 2);
bw.newLine(); // 换行
}catch (Exception e){
e.printStackTrace();
}
}
}
4.5 出师表案例
3.侍中、侍郎郭攸之、费祎、董允等,此皆良实,志虑忠纯,是以先帝简拔以遗陛下。愚以为宫中之事,事无大小,悉以咨之,然后施行,必得裨补阙漏,有所广益。 8.愿陛下托臣以讨贼兴复之效,不效,则治臣之罪,以告先帝之灵。若无兴德之言,则责攸之、祎、允等之慢,以彰其咎;陛下亦宜自谋,以咨诹善道,察纳雅言,深追先帝遗诏,臣不胜受恩感激。 4.将军向宠,性行淑均,晓畅军事,试用之于昔日,先帝称之曰能,是以众议举宠为督。愚以为营中之事,悉以咨之,必能使行阵和睦,优劣得所。 2.宫中府中,俱为一体,陟罚臧否,不宜异同。若有作奸犯科及为忠善者,宜付有司论其刑赏,以昭陛下平明之理,不宜偏私,使内外异法也。 1.先帝创业未半而中道崩殂,今天下三分,益州疲弊,此诚危急存亡之秋也。然侍卫之臣不懈于内,忠志之士忘身于外者,盖追先帝之殊遇,欲报之于陛下也。诚宜开张圣听,以光先帝遗德,恢弘志士之气,不宜妄自菲薄,引喻失义,以塞忠谏之路也。 9.今当远离,临表涕零,不知所言。 6.臣本布衣,躬耕于南阳,苟全性命于乱世,不求闻达于诸侯。先帝不以臣卑鄙,猥自枉屈,三顾臣于草庐之中,咨臣以当世之事,由是感激,遂许先帝以驱驰。后值倾覆,受任于败军之际,奉命于危难之间,尔来二十有一年矣。 7.先帝知臣谨慎,故临崩寄臣以大事也。受命以来,夙夜忧叹,恐付托不效,以伤先帝之明,故五月渡泸,深入不毛。今南方已定,兵甲已足,当奖率三军,北定中原,庶竭驽钝,攘除奸凶,兴复汉室,还于旧都。此臣所以报先帝而忠陛下之职分也。至于斟酌损益,进尽忠言,则攸之、祎、允之任也。 5.亲贤臣,远小人,此先汉所以兴隆也;亲小人,远贤臣,此后汉所以倾颓也。先帝在时,每与臣论此事,未尝不叹息痛恨于桓、灵也。侍中、尚书、长史、参军,此悉贞良死节之臣,愿陛下亲之信之,则汉室之隆,可计日而待也。
package com.itheima.d2_buffer_stream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Test4 {
public static void main(String[] args) {
// 目标:完成出师表案例。
try (
// 2、创建缓冲字符输入流与源文件接通
BufferedReader br = new BufferedReader(new FileReader("day10-io-code\\src\\csb.txt"));
// 6、创建缓冲字符输出流与目标文件接通
BufferedWriter bw = new BufferedWriter(new FileWriter("day10-io-code\\src\\csb-new.txt"));
){
// 1、准备一个集合,存储原文每段落。
List<String> data = new ArrayList<>();
// 3、开始按照行读取,并存入到集合中去。
String line;
while ((line = br.readLine()) != null){
data.add(line);
}
System.out.println(data);
// 4、对每段按照首字符编号进行排序
//排序是默认按照首字符的编号进行排序的
Collections.sort(data);
System.out.println(data);
// 5、把排序好的集合中的每段文章写出到新文件中去,每行都要换行。
for (String ln : data) {
bw.write(ln);
bw.newLine();
}
System.out.println("完成了!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
这段代码执行完成之后会把排序好的出师表输出到一个名称为csb-new.txt文件当中去。
4.6 缓冲流,字节流性能分析
package com.itheima.d2_buffer_stream;
import java.io.*;
public class TimeTest5 {
public static final String SRC_VIDEO = "D:\\【黑马】2024年11月AI版Java全栈开发V15课程\\02阶段:java基础进阶\\day02-面向对象进阶(二)\\视频\\11、接口的综合案例.mp4";
public static final String DEST_PATH = "C:\\Users\\Lenovo\\Desktop\\aaa\\";
public static void main(String[] args) {
// 目标:原始流和缓冲流的性能分析。
// copy01(); // 使用低级的字节流按照一个一个字节的形式复制文件: 简直慢的让人无法忍受,直接淘汰,禁止使用!
copy02(); // 使用低级的字节流按照字节数组的形式复制文件:还可以,但是相对较慢!把桶加大性能就变好了
// 性能不一定就慢,把桶加大性能就变好了,但是桶加大到一定程度后影响就微乎其微了。适中就好!
// copy03(); // 使用高级的缓冲字节流按照一个一个字节的形式复制文件:还是特别慢,不推荐使用!
copy04(); // 使用高级的缓冲字节流按照字节数组的形式复制文件:极快!推荐使用!
}
// 4、使用高级的缓冲字节流按照字节数组的形式复制文件。
public static void copy04(){
long start = System.currentTimeMillis();
try (
// 1、创建字节输入流管道与源文件接通
InputStream is = new FileInputStream(SRC_VIDEO);
InputStream bis = new BufferedInputStream(is);
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream(DEST_PATH + "04.avi");
OutputStream bos = new BufferedOutputStream(os);
) {
// 3、转移数据
byte[] buffer = new byte[32*1024]; // 1KB
int len;
while ((len = bis.read(buffer)) != -1){
bos.write(buffer,0,len);
}
} catch (Exception e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("使用高级的缓冲字节流按照字节数组的形式复制文件:" + (end - start) / 1000.0 + "s");
}
// 3、使用高级的缓冲字节流按照一个一个字节的形式复制文件。
public static void copy03(){
long start = System.currentTimeMillis();
try (
// 1、创建字节输入流管道与源文件接通
InputStream is = new FileInputStream(SRC_VIDEO);
InputStream bis = new BufferedInputStream(is);
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream(DEST_PATH + "03.avi");
OutputStream bos = new BufferedOutputStream(os);
) {
// 3、转移数据
int b;
while ((b = bis.read()) != -1){
bos.write(b);
}
} catch (Exception e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("使用高级的缓冲字节流按照一个一个字节的形式复制文件:" + (end - start) / 1000.0 + "s");
}
// 2、使用低级的字节流按照字节数组的形式复制文件。
public static void copy02(){
long start = System.currentTimeMillis();
try (
// 1、创建字节输入流管道与源文件接通
InputStream is = new FileInputStream(SRC_VIDEO);
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream(DEST_PATH + "02.avi");
) {
// 3、转移数据
byte[] buffer = new byte[32*1024];
int len;
while ((len = is.read(buffer)) != -1){
os.write(buffer,0,len);
}
} catch (Exception e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("使用低级的字节流按照字节数组的形式复制文件:" + (end - start) / 1000.0 + "s");
}
// 1、使用低级的字节流按照一个一个字节的形式复制文件。
public static void copy01(){
long start = System.currentTimeMillis();
try (
// 1、创建字节输入流管道与源文件接通
InputStream is = new FileInputStream(SRC_VIDEO);
// 2、创建一个字节输出流管道与目标文件接通
OutputStream os = new FileOutputStream(DEST_PATH + "01.avi");
) {
// 3、转移数据
int b;
while ((b = is.read()) != -1){
os.write(b);
}
} catch (Exception e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("使用低级的字节流按照一个一个字节的形式复制文件耗时:" + (end - start) / 1000.0 + "s");
}
}
5.IO流-转换流
5.1 不同编码读取时会乱码的问题
如果用代码编码UTF-8读取文本文件中GBK的编码文件就会出现乱码的问题。
package com.itheima.d3_transfer_stream;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.Reader;
public class Test1 {
public static void main(String[] args) {
// 目标:不同编码下,字符流读取文本内容的问题。
try (
// 代码编码:UTF-8 文件编码:UTF-8 a我m ==> o [ooo] o 不乱码
// 代码编码:UTF-8 文件编码:GBK a我m ==> o [oo] o 乱码
// 1、创建一个文件字符输入流与源文件接通。
Reader fr = new FileReader("E:\\resource\\abc.txt");
// 把原始的字符输入流包装成高级的缓冲字符输入流
BufferedReader br = new BufferedReader(fr);
){
String line; // 记住每次读取的一行数据。
while ((line = br.readLine()) != null){
System.out.println(line);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
5.2 字符输入转换流
package com.itheima.d3_transfer_stream;
import java.io.*;
public class InputStreamReaderDemo2 {
public static void main(String[] args) {
// 目标:字符输入转换流。
try (
// 1、得到GBK文件的原始字节输入流(GBK文件中文有两个字节)
InputStream is = new FileInputStream("E:\\resource\\abc.txt");
// 2、通过字符输入转换流把原始字节流按照指定编码转换成字符输入流
Reader isr = new InputStreamReader(is, "GBK");
// 3、把字符输入流包装成高级的缓冲字符输入流。
BufferedReader br = new BufferedReader(isr);
){
// 4、按照行读取
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
5.3 字符输出转换流
package com.itheima.d3_transfer_stream;
import java.io.*;
public class OutputStreamWriterDemo3 {
public static void main(String[] args) {
try (
// 目标:掌握字符输出转换流:可以指定写出去的字符编码。
// 1、创建一个文件字节输出流通向目标文件。
OutputStream os = new FileOutputStream("day10-io-code/src/dlei06-out.txt");
// 指定写出去的编码是GBK
Writer osw = new OutputStreamWriter(os, "GBK");
// 2、把字符输出流包装成缓冲输出流
BufferedWriter bw = new BufferedWriter(osw);
){
bw.write("莫道桑榆晚");
bw.newLine();
bw.write("为霞尚满天");
bw.newLine();
bw.write("abc666");
bw.newLine();
} catch (Exception e) {
e.printStackTrace();
}
}
}
实际开发中,可以通过字节转换的方式实现GBK编码转换,可以直接操作字节数组:
核心代码:
String text = "字节编码测试";
byte[] gbkBytes = text.getBytes("GBK"); //字符串->GBK字节数组
String text = new String(gbkBytes,"GBK");//GBK字节数组->字符串
fos.write(gbkBytes);//直接写入字节数组
6.IO流-打印流
package com.itheima.d4_printstream;
import java.io.PrintStream;
public class PrintStreamDemo1 {
public static void main(String[] args) {
try (
// 目标:打印流:方便,高效的写数据出去。
PrintStream ps = new PrintStream("day10-io-code/src/ps.txt");//只能覆盖
// PrintWriter ps = new PrintWriter("day10-io-code/src/ps.txt");
//要追加的话就要包原来的低级管道
// PrintWriter ps = new PrintWriter(new FileWriter("day10-io-code/src/ps.txt", true));
){
ps.println(666);
ps.println(97);
ps.println(97.9);
ps.println('a');
ps.println(true);
ps.println("我爱学Java");
} catch (Exception e) {
e.printStackTrace();
}
}
}
打印流的一种应用:输出语句的重定向,把输出在控制台的语句修改到文档中输出,用System.setOut( )
package com.itheima.d4_printstream;
import java.io.FileOutputStream;
import java.io.PrintStream;
public class PrintStreamDemo2 {
public static void main(String[] args) throws Exception {
// 目标:输出语句的重定向。
System.out.println("红豆生南国");
System.out.println("春来发几枝");
PrintStream ps = new PrintStream(new FileOutputStream("day10-io-code/src/ps2.txt", true));
System.setOut(ps); // 把系统类的打印流改成我的打印流!!
System.out.println("愿君多采撷");
System.out.println("此物最相思");
}
}
7.IO流-数据流
7.1 数据输出流
package com.itheima.d5_dataOutputStream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
public class DataOutputStreamDemo1 {
public static void main(String[] args) {
try (
// 目标:掌握数据输出流写数据出去的特点:可以写数据和其类型,而且后面可以读取出来。
DataOutputStream dos = new DataOutputStream(new FileOutputStream("day10-io-code/src/data.txt"));
){
// 2、写数据和其类型出去
dos.writeByte(97);
dos.writeBoolean(true);
dos.writeInt(1000);
dos.writeChar('a');
dos.writeUTF("我在北京天安门666");
} catch (Exception e) {
e.printStackTrace();
}
}
}
7.2 数据输入流
package com.itheima.d5_dataOutputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
public class DataInputStreamDemo2 {
public static void main(String[] args) {
// 目标:掌握特殊数据输入流读取特殊数据输出流写出去的数据。
try (
// 1、创建高级的特殊数据输入流管道包装低级的字节输入流管道。
DataInputStream dis = new DataInputStream(new FileInputStream("day10-io-code/src/data.txt"));
){
// 2、开始读取
byte b = dis.readByte();
System.out.println(b);
boolean b1 = dis.readBoolean();
System.out.println(b1);
int i = dis.readInt();
System.out.println(i);
char c = dis.readChar();
System.out.println(c);
String s = dis.readUTF();
System.out.println(s);
} catch (Exception e) {
e.printStackTrace();
}
}
}
8.IO流-序列化流
8.1 对象序列化
对象序列化:把Java对象写入到文件中去
如果有一些对象不希望被序序列化,例如Student中的passWord,那么我们可以用transient。transient修饰的成员变量将不参与序列化。
package com.itheima.d6_object;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
// 注意:如果学生对象要参与序列化,必须实现序列化接口
public class Student implements Serializable {
private String name;
private int age;
// transient 修饰的成员变量将不参与序列化
private transient String passWord;
private double height;
}
package com.itheima.d6_object;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
public class ObjectOutputStreamDemo1 {
public static void main(String[] args) throws Exception {
// 目标:完成对象的序列化:把Java对象存储到文件中去
// 1、准备一个对象。
Student s = new Student("风驴子", 27, "mazi666", 160);
// 2、创建对象字节输出流管道与目标文件接通
ObjectOutputStream oos =
new ObjectOutputStream(new FileOutputStream("day10-io-code/src/obj.txt"));
// 3、开始写对象出去
oos.writeObject(s);
// 4、关闭资源
oos.close();
}
}
序列化之后的文件中不是乱码,是对象序列化特殊的存储方式
8.2 对象反序列化
对象反序列化:把文件里的Java对象读出来
package com.itheima.d6_object;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
public class ObjectInputStreamDemo2 {
public static void main(String[] args) throws Exception {
// 目标:对象反序列化 使用对象字节输入流管道把文件中的Java对象又读入到内存中来。
// 1、创建对象字节输入流管道
ObjectInputStream ois =
new ObjectInputStream(new FileInputStream("day10-io-code\\src\\obj.txt"));
// 2、读取对象进来(对象反序列化)
// 实际上Java读取的对象都是Object类型的,因为Java无法判断文件中的具体类型
// 但是我们可以将对象强转为实际类型
Student s = (Student) ois.readObject();
System.out.println(s);
ois.close();
}
}
如何如果要一次序列化多个对象?
用一个ArrayList集合存储多个对象,然后直接对集合进行序列化即可
注意:ArrayList集合已经实现了序列化接口!
9.补充:IO框架
package com.itheima.d7_commons_io;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* 目标:使用CommonsIO框架进行IO相关的操作。
*/
public class CommonsIOTest1 {
public static void main(String[] args) throws Exception {
// FileUtils.copyFile(new File("day10-io-code/src/dlei04.txt"), new File("day10-io-code/src/dlei04-new.txt"));
// FileUtils.copyDirectory(new File("E:\\resource"), new File("E:\\resource-dlei-bak"));
// FileUtils.deleteDirectory(new File("E:\\resource-dlei-bak"));
// JDK 7开始也新增了单行复制相关的计数。
Files.copy(Path.of("day10-io-code/src/dlei04.txt"), Path.of("day10-io-code/src/dlei04-dlei-666.txt"));
//Files.delete(Path.of("day10-io-code/src/dlei04-new.txt"));
}
}