I/O流学习笔记
# I/O
## 字节流
InputStream字节输入流
OutputStream字节输出流
用于以字节的形式读取和写入数据
1. 练习-拆分文件
找到一个大于100k的文件,按照100k为单位,拆分成多个子文件,并且以编号作为文件名结束。
打印文件名和文件大小
拆分的思路,先把源文件的所有内容读取到内存中,然后从内存中挨个分到子文件里
提示,这里用到了数组复制Arrays.copyOfRange
总结:
1. 创建一个File对象,读取源文件路径
2. 创建一个byte内容数组,接收文件的全部内容
3. 创建一个文件字节输入流fis,参数为源File对象
4. 调用fis的read方法,把File文件的内容读取到byte内容数组中
5. 计算拆分成几个子文件
6. 根据数目,循环给子文件装内容
7. 先定义子文件的文件名,以及输出File对象
8. 定义一个byte接收数组,调用Arrays的复制数组方法,把前面内容数组的内容一部分一部分读进来
9. 创建文件字节输出流fos,参数为输出File对象
10. 调用fos的write方法,把接收数组内容写到子文件中去,并打印
## 字符流
Reader字符输入流
FileReader 是Reader子类
Writer字符输出流
FileWriter 是Writer的子类
专门用于字符的形式读取和写入数据
## 关闭流的方式
1. 在try中关闭
不推荐使用。如果文件不存在,或者读取的时候出现问题而抛出异常,那么就不会执行这一行关闭流的代码,存在巨大的资源占用隐患。
2. 在finally中关闭
这是标准的关闭流的方式
1. 首先把流的引用声明在try的外面,如果声明在try里面,其作用域无法抵达finally.
2. 在finally关闭之前,要先判断该引用是否为空
3. 关闭的时候,需要再一次进行try catch处理
这是标准的严谨的关闭流的方式,但是看上去很繁琐,所以写不重要的或者测试代码的时候,都会采用上面的有隐患try的方式,因为不麻烦~
3. 使用try()的方式
把流定义在try()里,try,catch或者finally结束的时候,会自动关闭
这种编写代码的方式叫做 try-with-resources, 这是从JDK7开始支持的技术
所有的流,都实现了一个接口叫做 AutoCloseable,任何类实现了这个接口,都可以在try()中进行实例化。 并且在try, catch, finally结束的时候自动关闭,回收相关资源。
```java
File f = new File("d:/lol.txt");
//把流定义在try()里,try,catch或者finally结束的时候,会自动关闭
try (FileInputStream fis = new FileInputStream(f)) {
byte[] all = new byte[(int) f.length()];
fis.read(all);
for (byte b : all) {
System.out.println(b);
}
} catch (IOException e) {
e.printStackTrace();
}
```
## 中文问题、转换流
所有字符都是以数字形式编码并存在与计算机中的,包括中文。
有很多种编码方式
我们一般使用UTF-8编码
```java
FileInputStream 可以手动设置编码方式
System.out.println(new String(content,"UTF-8"));//content是读取文件内容的byte数组
FileReader是不能手动设置编码方式的,需要使用转换流InputStreamReader来代替
InputStreamReader isr = new InputStreamReader(new FileInputStream(inputFile),"UTF-8")
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(outputFile),"GBK");
```
## 缓存流
缓存流在读取的时候,会一次性读较多的数据到缓存中,以后每一次的读取,都是在缓存中访问,直到缓存中的数据读取完毕,再到硬盘中读取。
缓存流在写入数据的时候,会先把数据写入到缓存区,直到缓存区达到一定的量,才把这些数据,一起写入到硬盘中去。按照这种操作模式,就不会像字节流,字符流那样每写一个字节都访问硬盘,从而减少了IO操作
1. 使用缓存流读取数据
```java
File f = new File("d:/lol.txt");
FileReader fr = new FileReader(f);// 创建文件字符流
BufferedReader br = new BufferedReader(fr);// 缓存流必须建立在一个存在的流的基础上
String line = br.readLine();// 一次读一行
```
2. 使用缓存流写出数据
```java
File f = new File("d:/lol2.txt");
FileWriter fw = new FileWriter(f);// 创建文件字符流
PrintWriter pw = new PrintWriter(fw); // 缓存流必须建立在一个存在的流的基础上
pw.println("garen kill teemo");
```
3. flush
有的时候,需要立即把数据写入到硬盘,而不是等缓存满了才写出去。 这时候就需要用到flush
4. 练习-移除注释
设计一个方法,用于移除Java文件中的注释
public void removeComments(File javaFile)
注: 如果注释在后面,或者是/**/风格的注释,暂不用处理
注意:要在通过BuffedReader 读取完数据后,才能建立Printwriter,因为创建输出流的时候,会把目标文件内容清空
```java
public class Ex {
public static void main(String[] args) {
File filePath = new File("D:\\IdeaProjects\\Java_Intermediate\\IOStream\\src\\BufferedStream\\file.java");
removeComments(filePath);
}
public static void removeComments(File javaFile){
StringBuffer sb = new StringBuffer();
try (BufferedReader br = new BufferedReader(new FileReader(javaFile))){
while (true){
String line = br.readLine();
if (line == null)
break;
String comment = "//";
if (!line.trim().startsWith(comment))
sb.append(line).append("\r\n");//如果不是注释行,就暂存到sb中
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try(PrintWriter pw = new PrintWriter(new FileWriter(javaFile))) {
pw.write(sb.toString());
System.out.println(sb);
} catch (IOException e) {
e.printStackTrace();
}
}
}
```
## 数据流
使用数据流的writeUTF()和readUTF() 可以进行数据的格式化顺序读写
如本例,通过DataOutputStream 向文件顺序写出 布尔值,整数和字符串。 然后再通过DataInputStream 顺序读入这些数据。
注: 要用DataInputStream 读取一个文件,这个文件必须是由DataOutputStream 写出的,否则会出现EOFException,因为DataOutputStream 在写出的时候会做一些特殊标记,只有DataInputStream 才能成功的读取。
```java
File f = new File("d:/lol.txt");
try(DataOutputStream dos = new DataOutputStream(new FileOutputStream(f))) {
dos.writeBoolean(true);
dos.writeInt(3005);
dos.writeBoolean(false);
dos.writeUTF("123 this is gareen");
File f = new File("d:/lol.txt");
try (DataInputStream dis = new DataInputStream(new FileInputStream(f))){
boolean b = dis.readBoolean();
boolean b1 = dis.readBoolean();
int i = dis.readInt();
String s = dis.readUTF();
```
```
```
## 对象流
对象流指的是可以直接把一个对象以流的形式传输给其他的介质,比如硬盘
一个对象以流的形式进行传输,叫做序列化。 该对象所对应的类,必须是实现Serializable接口
//创建对象输出流
FileOutputStream fos = new FileOutputStream(f);
ObjectOutputStream oos =new ObjectOutputStream(fos);
//创建对象输入流
FileInputStream fis = new FileInputStream(f);
ObjectInputStream ois =new ObjectInputStream(fis);
## 缓存流、数据流、对象流,都是建立在有一个流的基础上的
缓存流:BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream(建立在字节流、字符流基础上)
数据流:DataInputStream、DataOutputStream(建立在字节流基础上)
对象流:ObjectInputStream、ObjectOutputStream(建立在字节流基础上)
## 综合练习
1. 练习-复制文件夹
复制文件夹,实现如下方法,把源文件夹下所有的文件 复制到目标文件夹下(包括子文件夹)
public static void copyFolder(String srcFolder, String destFolder){
}
思路总结:
1. 编写复制文件方法
2. 编写复制文件夹方法
1. 首先进行边界条件判定(源文件夹不是文件夹、不存在,目标文件夹是文件、不存在)
2. 然后遍历该文件夹,如果是文件,就调用复制文件方法,如果是文件夹,就递归调用复制文件夹方法
```java
public class Ex_CopyFolder {
public static void main(String[] args) {
String srcFolder = "D:\\Games\\test";
String destFolder = "D:\\newFolder";
copyFolder(srcFolder, destFolder);
}
/**
* 复制文件
* @param srcPath 源文件
* @param destPath 目标文件
*/
public static void copyFile(String srcPath,String destPath){
File srcFile = new File(srcPath);
File destFile = new File(destPath);
System.out.println(srcPath + "是文件");
try (FileInputStream fis = new FileInputStream(srcFile);
FileOutputStream fos = new FileOutputStream(destFile);){
byte[] fileContent = new byte[(int) srcFile.length()];
fis.read(fileContent);
fos.write(fileContent, 0, fileContent.length);
fos.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 复制文件夹
* @param srcPath 源文件夹路径
* @param destPath 目标文件夹路径
*/
public static void copyFolder(String srcPath, String destPath) {
File srcFolder = new File(srcPath);
File destFolder = new File(destPath);
if (!srcFolder.isDirectory() || !srcFolder.exists())
return;
if (destFolder.isFile())
return;
if (!destFolder.exists())
destFolder.mkdirs();
System.out.println(srcFolder+"是文件夹");
//遍历该文件夹
File[] files = srcFolder.listFiles();
for (File file : files){
//如果是文件,就复制
if (file.isFile()){
copyFile(file.getAbsolutePath(), new File(destPath,file.getName()).getAbsolutePath());
}
//如果是文件夹,就递归调用
else
copyFolder(file.getAbsolutePath(), new File(destPath,file.getName()).getAbsolutePath());
}
}
}
```
2. 练习-查找文件内容
public static void search(File folder, String search);
假设你的项目目录是 e:/project,遍历这个目录下所有的java文件(包括子文件夹),找出文件内容包括 Magic的那些文件,并打印出来。
思路:
* 边界条件判断:folder是否存在,不存在返回;folder是文件?是的话调用查找文件内容方法
* 遍历folder下面的子文件和文件夹,如果是文件,则调用查找文件内容方法;如果是文件夹,则递归调用search方法
总结:bug有两处
* 一个是增强型for循环里面传参传错了,searchFileContent(f, search)传递的第一个参数是for循环里面定义的那个,我给传成别的参数了
* 第二个是流使用错误,因为题目要求查询字符串,所以肯定要用字符流FileReader,如果使用字节流,byte数组里面的内容是以数字形式存在的,那肯定找不到
```java
public class Ex_FindFileContent {
/**
* 遍历文件夹中的文件,找出文件内容包括 Content 的那些文件,并打印出来
* @param folder 文件夹
* @param search 查询内容
*/
public static void search(File folder, String search){
if (!folder.exists())
return;
//如果是文件,则调用查找文件内容方法
if (folder.isFile())
searchFileContent(folder, search);
//如果是文件夹,则递归调用search方法
else {
File[] files = folder.listFiles();
for (File f : files){
if (f.isFile())
searchFileContent(f, search);
else
search(f, search);
}
}
}
/**
* 在文件中查找内容
* @param file 文件
* @param searchContent 查询的内容
*/
public static void searchFileContent(File file,String searchContent){
char[] fileContent = new char[(int) file.length()];
try(FileReader fr = new FileReader(file)) {
fr.read(fileContent);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
String sb = String.valueOf(fileContent);
if (sb.contains(searchContent)){
System.out.printf("找到目标子字符串%s,在文件:%s", searchContent,file.getAbsolutePath());
System.out.println();
}
}
public static void main(String[] args) {
File itemFolder = new File("D:\\IdeaProjects\\Java_Intermediate\\IOStream");
String str = "Content";
search(itemFolder, str);
}
}
```

浙公网安备 33010602011771号