目录
关于内容的读写
关于读InputStream
End Of Stream(EOS)
处理文本数据(字符数据)
文件内容中填充中文的读操作
利用 Scanner 进行字符读取
关于写OutputStream
Writer抽象类OutputStreamWriter 实现类
利用 PrintWriter 找到我们熟悉的方法
小结
练习
关于内容的读写
关于读InputStream
1. 直接读取(以二进制数据的方式读取,表现在代码中 byte为单位)
2. 文本读取
1. java.io.InputStream 输入流
2. 本身是一个抽象类,真正使用过程中要依赖这个抽象的具体实现子类
FilelnputStream关于文件的输入流
3. 抽象模型4.要记得关闭
is.close()import java.io.FileInputStream; import java.io.InputStream; import java.util.Arrays; public class Demo4 { public static void main(String[] args) throws Exception { InputStream is = new FileInputStream("./hello.txt"); // FileInputStream 赋值给 InputStream,是因为有继承关系 // FileInputStream是InputStream的下级类 // 准备水桶 // 1024 代表能接 1024 滴(字节)水,我们准备好桶的容量 byte[] buf = new byte[1024]; // n是真正接到多少(字节)的水 // n >= 0&& n < 1024 int n = is.read(buf); //12 System.out.println(n); // 真正的数据放在 buf 从 [0, 12) byte[] bytes = Arrays.copyOf(buf, n); for (byte b : bytes) { System.out.printf("%02x\n", b); } is.close(); } }
windows 上的换行默认是“\r\n"也写作CRLF
如何利用InputStream进行二进制数据的直接读取
1)一个字节
2)一次读一批
理解EOS (-1)
End Of Stream(EOS)
除了要接水之外,我们还需要一个明确的信号(说明肯定不会有新的数据了)
End Of Stream(EOS)
如果这次接了0滴水
A.是暂时现在没水了?——0B.还是以后永远没水了?—— -1
一次读一滴
import java.io.FileInputStream; import java.io.InputStream; public class Demo5 { public static void main(String[] args) throws Exception { InputStream is = new FileInputStream("./hello.txt"); while (true) { int data = is.read(); if (data == -1) { // 所有数据都被读完了 break; } // 否则,data 就是我们取到的数据 byte b = (byte) data; System.out.printf("%02x\n", b); } is.close(); } }
一次读一桶
import java.io.FileInputStream; import java.io.InputStream; public class Demo6 { public static void main(String[] args) throws Exception { InputStream is = new FileInputStream("./hello.txt"); byte[] buf = new byte[5]; while (true) { int n = is.read(buf); // n == 0 只是本次没数据,以后还有 // n == -1 本次没数据,以后也没数据了 if (n == -1) { // 代表数据全部读完 break; } for (int i = 0; i < buf.length; i++) { byte b = buf[i]; System.out.printf("%02x\n", b); } } is.close(); } }
处理文本数据(字符数据)
处理文本数据(字符数据)
ASCII、Unicode、UTF-8、GBK (GB18030、GB2312)
字符集(char set)
字符编码规则(char encoding)
计算机中的数据<->数字(有范围的整数)
图像、声音、文字->数字如果每个人都定义自己的字符集,实际上没有任何的意义。
所以,总是由标准委员会来规定好一个字符集,大家一起遵守!
所以才有各种各样的标准字符集的出现!
文件内容中填充中文的读操作
public class Demo8 { public static void main(String[] args) throws Exception { try (InputStream is = new FileInputStream("./test.txt")) { byte[] bytes = new byte[1024]; int n = is.read(bytes); String s = new String(bytes, 0, n, "UTF-8"); System.out.println(s); } } }
利用 Scanner 进行字符读取
Scanner scanner = new Scanner(System.in);
import java.io.FileInputStream; import java.io.InputStream; import java.util.Scanner; public class Demo8 { public static void main(String[] args) throws Exception { try (InputStream is = new FileInputStream("./test.txt")) { try (Scanner scanner = new Scanner(is, "UTF-8")) { while (scanner.hasNextLine()) { String line = scanner.nextLine(); System.out.println("|" + line + "|"); } } } } }
关于写OutputStream
内存的写速度远远快于硬盘的写速度。
所以,为了平衡这个速度之差,一般通过“缓冲区buffer”来处理之前文件不存在,直接写入
import java.io.FileOutputStream; import java.io.OutputStream; public class Demo10 { public static void main(String[] args) throws Exception { // OutputStream -> FileOutputStream // 所以很少用 file.createNewFile() // 如果文件之前不存在,则会进行创建(创建可能失败: 1. 权限、2. 路径上的目录还不存在) // 如果文件之前存在,会清空之前的文件内容,重新写入 try (OutputStream os = new FileOutputStream("./out.txt")) { // 0xe6 0x88 0x91 是 "我" 的 UTF-8 编码的字节序列 os.write(0xe6); os.write(0x88); os.write(0x91); // 确保把缓冲区内可能遗留的数据全部写入 Output 设备中 os.flush(); } } }
之前文件存在,清空内容,重新写入
import java.io.FileOutputStream; import java.io.OutputStream; public class Demo10 { public static void main(String[] args) throws Exception { // OutputStream -> FileOutputStream // 所以很少用 file.createNewFile() // 如果文件之前不存在,则会进行创建(创建可能失败: 1. 权限、2. 路径上的目录还不存在) // 如果文件之前存在,会清空之前的文件内容,重新写入 try (OutputStream os = new FileOutputStream("./out.txt")) { // 0xe6 0x88 0x91 是 "我" 的 UTF-8 编码的字节序列 // os.write(0xe6); // os.write(0x88); // os.write(0x91); os.write(0x20); // 空格 os.write(0x0d); // '\r' os.write(0x0a); // '\n' os.write(0x65); os.write(0x65); os.write(0x65); // 确保把缓冲区内可能遗留的数据全部写入 Output 设备中 os.flush(); } } }
连续写入
import java.io.FileOutputStream; import java.io.OutputStream; public class Demo11 { public static void main(String[] args) throws Exception { try(OutputStream os = new FileOutputStream("./out.txt")) { byte[] buf = {0x65,0x65,0x20,0x65,0x0d,0x0a,(byte) 0xe6,(byte)0x88,(byte)0x91}; // 整个 buf 的数据全部写入 os.write(buf); // os.write(buf,2,4); os.flush(); } } }
Writer抽象类OutputStreamWriter 实现类
import java.io.*; public class Demo12 { public static void main(String[] args) throws Exception { try (OutputStream os = new FileOutputStream("./word.txt")) { try (Writer writer = new OutputStreamWriter(os, "UTF-8")) { writer.write("你好\r\n"); writer.write("哈哈"); writer.flush(); } } } }
利用 PrintWriter 找到我们熟悉的方法
PrintWriter
printIn(...)print(...)
printf(fmt, ...)
import java.io.*; public class Demo12 { public static void main(String[] args) throws Exception { try (OutputStream os = new FileOutputStream("./printWriter.txt")) { try (Writer writer = new OutputStreamWriter(os, "UTF-8")) { try (PrintWriter printWriter = new PrintWriter(writer)) { printWriter.println(1+1); printWriter.print(3); printWriter.printf("%s+%d","我",10); printWriter.flush(); } } } } }
小结
InputStream输入流,背后就是输入设备(模拟的输入设备)
1.read() 、read(byte[])
2.EOS(-1)
3.Scanner(is, "UTF-8");lnputStream 读取数据流的设备。将输入设备抽象成数据流的源头。
1.数据被抽象成流式(stream)的形式。
2.需要有内存空间,存放读取到的数据:一个变量的空间或者一个数组的空间3.EOS:表示数据已经被读完。和本次读取暂时没有读到数据。
4.可以将InputStream接上其他的数据处理对象(水龙头上接净水器)OutputStream输出流,背后就是输出设备(模拟的输出设备)
1. write(int)、write(byte[])
2. flush()缓冲区(buffer)平衡写入次数
3. OutputStreamWriter(做字符集编码处理) + PrintWriter(使用我们熟悉的print/println/printf)OutputStream将内存中的数据,通过写入数据流的设备,最终将数据写到输出设备中。
1. 写的时候,为了同步内存和输出设备之间的写入速度差,一般是存在缓冲区(buffer)的。减少写的频次,提升写的速度。2. 冲刷缓冲区(flush)的操作非常重要。
练习
1.给定路径,查找文件名中包含指定的字符的文件列表,并且根据用户的选择,决定是否删除
思路
1.从一个树的根开始,遍历整棵树
2.将每个结点的名字匹配查找条件
3.如果符合条件,就保存File对象
4.遍历完成之后,得到一组符合条件的File对象5.依次询问用户,这些对象的下一步处理方式import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.Scanner; public class FindAndDelete { public static void main(String[] args) throws IOException { Scanner scanner = new Scanner(System.in); String rootPath = "D:\\学习"; String condition = ".txt"; List<File> resultList = new ArrayList<>(); // 存放符合条件的文件列表 // 遍历整棵树,找到符合条件的所有文件,并且将结果放入 resultList File rootFile = new File(rootPath); traversal(rootFile, condition, resultList); // 依次针对每个结果,询问用户是否要删除 for (File file : resultList) { System.out.println("是否要删除该文件: " + file.getCanonicalPath()); if (!scanner.hasNextBoolean()) { System.out.println("退出"); return; } boolean isDelete = scanner.nextBoolean(); if (isDelete) { file.delete(); } } } private static void traversal(File dirFile, String condition, List<File> resultList) { File[] files = dirFile.listFiles(); if (files == null) { return; } for (File file : files) { if (file.isDirectory()) { traversal(file, condition, resultList); } else if (file.isFile()) { // 判断文件名是否满足条件 :是否以 xxx 结尾 String name = file.getName(); if (name.endsWith(condition)) { // 说明符合查找条件 resultList.add(file); } } } } }
2. 一个文件的复制
给定两个路径:源文件路径(必须存在)、目标路径(必须不存在&&目录存在),源文件只是普通文件,不是目录。思路
1.由于我们只是进行复制,所以根本不考虑文件的内容具体是什么
2.所以我们要做的是:遍历{从源文件中读取数据,写入目标文件}直到所有的数据全部写完。import java.io.*; public class CopyFile { public static void main(String[] args) throws Exception { File srcFile = new File("D:\\学习\\1.png"); File destFile = new File("D:\\学习\\123.png"); // 0. 统计复制速度 long startedAt = System.currentTimeMillis(); // 1. 准备好搬数据的桶 byte[] buf = new byte[1024]; // 2. 打开两个文件 try (InputStream is = new FileInputStream(srcFile)) { try (OutputStream os = new FileOutputStream(destFile)) { // 3. 不断地用桶从 is 接水,倒入 os 中 while (true) { int n = is.read(buf); if (n == -1) { // 全部读完了,可以中止循环了 break; } // 直接将读入的数据,原封不同的写入 os 中 os.write(buf, 0, n); } // 4. 冲刷缓冲区 os.flush(); } } // 0. 统计复制速度 long endedAt = System.currentTimeMillis(); // 获取当前时间戳(ms) long ms = endedAt - startedAt; double s = ms / 1000.0; System.out.printf("复制共消耗了: %.3f 秒\n", s); } }
3. 一个目录的复制
给定两个路径:源文件路径(必须存在&&是目录)、目标路径(必须不存在&&父目录存在)
思路:遍历整棵树
if目录:继续递归+目标的相对位置创建目录if 文件:目标的相对位置,进行文件的复制
import java.io.*; public class CopyDirectory { // 定义成 static 属性,保证在 traversal 中可以读取 static File srcFile = new File("D:\\学习\\content"); static File destFile = new File("D:\\学习\\dest"); public static void main(String[] args) throws Exception { traversal(srcFile); } private static void traversal(File dirFile) throws Exception { File[] files = dirFile.listFiles(); if (files == null) { return; } for (File file : files) { // 怎么得到相对位置:利用 srcFile 的 绝对路径、destFile 的绝对路径、file 的绝对路径 String srcFilePath = srcFile.getCanonicalPath(); String filePath = file.getCanonicalPath(); String destPath = destFile.getCanonicalPath(); String relativePath = filePath.substring(srcFilePath.length()); String destFilePath = destPath + relativePath; File oneDestFile = new File(destFilePath); if (file.isDirectory()) { // 是目录时,直接创建目录 oneDestFile.mkdir(); // 我们可以保证其父目录一定存在 traversal(file); } else if (file.isFile()) { copyFile(file, oneDestFile); } } } private static void copyFile(File srcFile, File destFile) throws Exception { // 0. 用于统计复制速度 long startedAt = System.currentTimeMillis(); // 获取当前时间戳(ms) // 1. 准备好搬数据的桶 byte[] buf = new byte[1024]; // 2. 打开两个文件 int count = 0; try (InputStream is = new FileInputStream(srcFile)) { try (OutputStream os = new FileOutputStream(destFile)) { // 3. 不断地用桶从 is 接水,倒入 os 中 while (true) { int n = is.read(buf); count += n; // System.out.printf("已经复制了 %d 字节的数据\n", count); if (n == -1) { // 全部读完了,可以中止循环了 break; } // 直接将读入的数据,原封不同的写入 os 中 os.write(buf, 0, n); } // 4. 冲刷缓冲区 os.flush(); } } // 0. 用于统计复制速度 long endedAt = System.currentTimeMillis(); // 获取当前时间戳(ms) long ms = endedAt - startedAt; double s = ms / 1000.0; System.out.printf("复制共消耗了: %.3f 秒\n", s); } }