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);
    }

}
```



posted @ 2020-06-05 15:51  是居居呀  阅读(42)  评论(0)    收藏  举报