1 概述

IO流概述.png

2 计算机中一切均为字节

8个比特位就是1个字节,1个二进制位就是一个比特位。
在数据传输过程中,一切数据(文本、图像、声音等)最终存储的均为一个个字节,即8个二进制数字。所以数据传输过程中使用二进制数据可以完成任意数据的传递。
我们向一个文件中存储一定的数据(一些数字),如果使用文本方式打开,则会以文本的方式解释数据。如果以视频的方式打开,则会以视频的方式解释数据。音频、可行执行文件等亦是如此。所以,在文件传输过程中,我们要时刻明确,传输的始终为二进制数据。

3 字节流

3.1 字节输出流OutputStream

OutputStream是所有字节输出流的顶层父类。并且他是一个抽象类。
我们一般使用它的子类叫做FileOutputStream。
输出流用来写,可以将java程序中的数据写入到文件中。

构造方法:
FileOutputStream(File file): 传递一个File类型的文件路径。
FileOutputStream(String name): 传递一个字符串类型的文件路径。

其他方法:
void write(int b):向文件中写入一个字节。如果传递的是一个数字,最终写入进行的会是对应ASCII码表中的字符。
void write(byte[] b): 向文件中写入一个字节数组。
void write(byte[] b, int off, int len): 向文件中写入字节数组的一部分。b表示要写入的字节数组。 off表示要从这个位置开始写。 len写入的个数。

写入的具体步骤:
1. 创建字节输出流对象,绑定一个目的地,用来写入。
2. 调用write方法进行写入。
3. 释放资源。

注意:只有字符流写需要刷新,字节流写是不需要刷新的。

字符在java中占两个字节。但是如果这个字符是ASCII码表中的字符,那么这个字符在操作系统中是占一个字节的。

注意:在操作系统中,中文默认是占2个字节。所以不能使用一次写入一个字节的方式写入一个中文字符。

3.2 字符串与字节数组的互转

字符串->字节数组
byte[] getBytes(): 把字符串转成字节数组。


字节数组->字符串
String(byte[] bytes): 将字节数组转成字符串。
String(byte[] bytes, int offset, int length) :将字节数组的一部分转成字符串。bytes字节数组。 offset从那个位置开始转。 length个数。

gbk中文占2个字节。
utf中文占3个字节。

3.3 使用字节输出流向文件中写入字节数组

void write(byte[] b): 向文件中写入一个字节数组。
void write(byte[] b, int off, int len): 向文件中写入字节数组的一部分。b表示要写入的字节数组。 off表示要从这个位置开始写。 len写入的个数。

字节数组推荐通过字符串调用getBytes方法获取到,因为这样更加的简便。
写入中文,可以先把对应的中文字符串转成字节数组

3.4 IO流释放资源的方式

无论如何,一定要保证流最终释放掉。
把close的代码放入到finally,因为finally里面的代码无论如何都会执行到。

  1. package cn.itcast.demo01.outputstream;
  2. import java.io.FileNotFoundException;
  3. import java.io.FileOutputStream;
  4. import java.io.IOException;
  5. /*
  6. IO流的异常处理。
  7. 无论如何,一定要保证流最终释放掉。
  8. 把close的代码放入到finally,因为finally里面的代码无论如何都会执行到。
  9. */
  10. public class Demo04OutputStreamException {
  11. public static void main(String[] args) {
  12. FileOutputStream fos = null;
  13. try {
  14. //创建字节输出流对象
  15. fos = new FileOutputStream("c.txt");
  16. //调用方法写入
  17. fos.write(100);//d
  18. } catch(IOException e) {
  19. e.printStackTrace();
  20. } finally {
  21. //释放资源
  22. try {
  23. if(fos != null) {
  24. fos.close();
  25. }
  26. } catch(IOException e) {
  27. e.printStackTrace();
  28. }
  29. }
  30. }
  31. }

3.5 多个流对象的异常处理

shift + alt + z 直接生成try…catch…

先定义个工具类

package cn.itcast.demo01.outputstream;

import java.io.Closeable;
import java.io.IOException;

/*
    定义的工具类,里面的方法可以释放IO流对象。

    所有的IO流都实现了一个接口,这个接口叫做Closeable

    并且这个接口中有一个方法,可以释放资源。
    void close() 
 */
public class IOUtils {
    public static void close(Closeable... cs) {
        //遍历c
        for(Closeable c : cs) {
            //把每个c给释放掉
            try {
                if(c != null) {
                    c.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

关闭方法

package cn.itcast.demo01.outputstream;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/*
    多个流对象的异常处理。
    shift + alt + z
 */
public class Demo05OutputStreamException {
    public static void main(String[] args) {
        FileOutputStream fos1 = null;
        FileOutputStream fos2 = null;
        try {
            //创建两个流对象
            fos1 = new FileOutputStream("d.txt");
            fos2 = new FileOutputStream("e.txt");

            //向这两个文件中写数据.
            fos1.write(65);
            fos2.write(66);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //释放资源
            /*
            try {
                if(fos1 != null) {
                    fos1.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if(fos2 != null) {
                    fos2.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
            */
            IOUtils.close(fos1, fos2);
        }
    }
}

3.6 追加写以及换行写

如果要写换行,需要使用换行符。
windows:\r\n
linux:\n
mac:\r

文件续写:
调用有两个参数的构造方法创建对象。
FileOutputStream(File file, boolean append):如果第二个参数是true表示续写。
FileOutputStream(String name, boolean append) :如果第二个参数是true表示续写。

3.7 字节输入流InputStream

InputStream 是所有字节输入流的顶层父类。这个类是一个抽象类。
一般使用它的子类FileInputStream。
这个类的作用是用来读取,可以将文件中的字节读取到java程序中。

构造方法:
FileInputStream(File file):传递一个File类型的文件名。如果要读取的文件不存在,就会报错。
FileInputStream(String name): 传递一个字符串类型的文件名。如果读取结束返回-1.


其他方法:
int read(): 一次读取一个字节。会将读取到的字节返回。如果读取结束,返回的是-1
使用这个read方法一次只能读取一个字节。如果读取中文,就会产生问题。因为中文占两个字节。
他会把这两个字节拆开读取并转换。
int read(byte[] b): 一次读取一个字节数组。 返回值为读取到的个数。如果读取结束返回-1.

字节流不建议操作中文,因为可能会出现各种奇妙的问题。
如果操作中文,要使用字符流。

使用FileInputStream去文件中读取字节。
采用一次读取一个字节的方式。
步骤:
1. 创建FileInputStream对象。
2. 调用read方法进行读取。
3. 释放资源。

一次读一个字节的原理
一次读取一个字节原理.png
一次读一个字节数组
一次读取一个字节数组.png

3.8 一次读取一个字节的方式复制文件

一次读取一个字节复制文件.png

package cn.itcast.demo02.inputstream;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/*
    字节流复制文件。
    一次读取一个字节的方式。
    文件复制其实就是一边读一边写。每读取到一个字节,就把读取到的字节写入到文件中。

    思路:
        1. 创建输入流FileInputStream,用来读取。
        2. 创建输出流FileOutputStream, 用来写入
        3. 开始读取,一次读取一个字节。
        4. 每读取到一个字节,就把读取到的这个字节写入到目的地文件中。
        5. 释放资源
 */
public class Demo01Copy {
    public static void main(String[] args) throws IOException {
        //创建输入流FileInputStream,用来读取。
        FileInputStream fis = new FileInputStream("aa.jpg");
        //创建输出流FileOutputStream,用来写入
        FileOutputStream fos = new FileOutputStream("dest01.jpg");
        //开始读取,一次读取一个字节。
        int i;//保存每次读取到的字节
        while((i = fis.read()) != -1) {
            //每读取到一个字节,就把读取到的这个字节写入到目的地文件中。
            fos.write(i);
        }

        //释放资源
        fos.close();
        fis.close();

    }
}

3.9 一次读取一个字节数组的方式复制文件

package cn.itcast.demo02.inputstream;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/*
    一次读取一个字节数组的方式复制文件,效率高。

    思路:
        1. 创建FileInputStream用来读取。
        2. 创建FileOutputStream用来写入。
        3. 开始读取,一次读取一个字节数组。
        4. 每读取一次字节数组,就把这个读取到的字节数组写入到文件中。
        5. 释放资源。

 */
public class Demo02Copy {
    public static void main(String[] args) throws IOException {
        //创建FileInputStream用来读取。
        FileInputStream fis = new FileInputStream("aa.jpg");
        //创建FileOutputStream用来写入。
        FileOutputStream fos = new FileOutputStream("dest02.jpg");

        //开始复制
        //开始读取,一次读取一个字节数组。
        byte[] bArr = new byte[1024]; //定义字节数组用来保存读取到的字节
        int len;//用来保存每次读取到的字节的个数。

        //循环读取
        while((len = fis.read(bArr)) != -1) {
            //读取到的内容会放入到bArr这个字节数组中。
            //每读取一次字节数组,就把这个读取到的字节数组写入到文件中。
            fos.write(bArr, 0, len);//读取几个,就写几个
        }

        //释放资源
        fos.close();
        fis.close();
    }
}

4 缓冲流

字节缓冲流根据流的方向,共有2个
 写入数据到流中,字节缓冲输出流 BufferedOutputStream
 读取流中的数据,字节缓冲输入流 BufferedInputStream
它们的内部都包含了一个缓冲区,通过缓冲区读写,就可以提高了IO流的读写速度

4.1 BufferedOutputStream字节输出缓冲流

BufferedOutputStream 字节输出缓冲流,用来进行写入。
特点:
效率高,因为内部有一个缓冲区。

构造方法
BufferedOutputStream(OutputStream out): 传递一个字节输出流。

BufferedOutputStream的使用
1. 创建一个FileOutputStream对象。
2. 创建BufferedOutputStream对象。
3. 调用方法,进行写入。
4. 释放资源

其实缓冲流本身并不具备读或者写的功能,他的作用其实是为其他流提供加速。

package cn.itcast.demo03.bufferedstream;

import java.io.BufferedOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo01BufferedOutputStream {
    public static void main(String[] args) throws IOException {
        //创建一个FileOutputStream对象。
        FileOutputStream fos = new FileOutputStream("h.txt");
        //创建BufferedOutputStream对象。
        BufferedOutputStream bos = new BufferedOutputStream(fos);//表示要对fos这个流进行加速。
        //调用方法,进行写入。
        bos.write("今天天气不错, 孔子东游".getBytes());

        //释放资源。
        bos.close();
    }
}

4.2 BufferedInputStream 字节输入缓冲流

BufferedInputStream 字节输入缓冲流,可以读取数据。
内部有一个缓冲区,可以进行加速,所以他的效率比较高。

构造方法:
BufferedInputStream(InputStream in):传递一个字节输入流。可以传递子类FileInputStream。

使用步骤:
1. 创建一个FileInputStream对象
2. 创建BufferedInputStream,并且在构造方法中传递FileInputStream对象,表示要对这个流进行加速。
3. 调用方法,进行读取。
4. 释放资源。

package cn.itcast.demo03.bufferedstream;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;


public class Demo02BufferedInputStream {
    public static void main(String[] args) throws IOException {
        //创建一个FileInputStream对象
        FileInputStream fis = new FileInputStream("source03.txt");

        //创建BufferedInputStream,并且在构造方法中传递FileInputStream对象,表示要对这个流进行加速。
        BufferedInputStream bis = new BufferedInputStream(fis);//表示要对fis这个流进行加速。

        //调用方法,进行读取。
        //一次读取一个字节
        /*
        int i;
        while((i = bis.read()) != -1) {
            //把读取到的内容进行打印
            System.out.print((char)i);
        }
        */

        //一次读取一个字节数组。
        byte[] bArr = new byte[1024];
        int len;
        while((len = bis.read(bArr)) != -1) {
            System.out.println(new String(bArr, 0, len));//打印
        }

        //释放资源
        bis.close();

    }
}

5 四种方式复制文件的效率比较。

四种方式复制文件,并比较效率。
使用基本字节流一次读取一个字节复制文件 7091ms
使用基本字节流一次读取一个字节数组复制文件 18ms
使用缓冲流一次读取一个字节复制文件 145ms
使用缓冲流一次读取一个字节数组复制文件 6ms

package cn.itcast.demo03.bufferedstream;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class Demo03Copy {
    public static void main(String[] args) throws IOException {
        //复制之前记录一下时间
        long start = System.currentTimeMillis();
        //开始复制
        //copyFile1("aa.jpg", "copytest01.jpg");
        //copyFile2("aa.jpg", "copytest02.jpg");
        //copyFile3("aa.jpg", "copytest03.jpg");
        copyFile4("aa.jpg", "copytest04.jpg");


        //记录时间
        long end = System.currentTimeMillis();

        System.out.println(end - start);

    }

    /*
     * 使用缓冲流一次读取一个字节数组
     */
    public static void copyFile4(String source, String dest) throws IOException {
        //创建BufferedInputStream,用来读取
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source));
        //创建BufferedOutputStream,用来写入
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest));

        //一次读取一个字节数组。
        byte[] bArr = new byte[1024]; 
        int len;

        //循环读取
        while((len = bis.read(bArr)) != -1) {
            bos.write(bArr, 0, len);//读取几个,就写几个
        }

        //释放资源
        bos.close();
        bis.close();
    }


    /*
     * 使用缓冲流一次读取一个字节复制文件
     */
    public static void copyFile3(String source, String dest) throws IOException {
        //创建BufferedInputStream,用来读取
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source));
        //创建BufferedOutputStream,用来写入
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(dest));
        //开始读取,每读取一个字节,就把读取到的字节写入到文件中
        int i;
        //一次读取一个字节
        while((i = bis.read()) != -1) {
            //写入到文件
            bos.write(i);
        }

        //释放资源
        bos.close();
        bis.close();
    }

    /*
     * 基本的字节流一次读取一个字节数组
     */
    public static void copyFile2(String source, String dest) throws IOException {
        FileInputStream fis = new FileInputStream(source);
        FileOutputStream fos = new FileOutputStream(dest);

        byte[] bArr = new byte[1024]; 
        int len;

        //循环读取
        while((len = fis.read(bArr)) != -1) {
            fos.write(bArr, 0, len);//读取几个,就写几个
        }

        //释放资源
        fos.close();
        fis.close();
    }


    /*
     * 使用基本字节流一次读取一个字节复制文件
     */
    public static void copyFile1(String source, String dest) throws IOException {
        //创建字节输入流用来读取
        FileInputStream fis = new FileInputStream(source);
        //创建字节输出流用来写入
        FileOutputStream fos = new FileOutputStream(dest);
        //开始复制
        int i;
        //一次读取一个字节
        while((i = fis.read()) != -1) {
            //写入到文件
            fos.write(i);
        }
        //释放资源
        fos.close();
        fis.close();
    }
}

5 复制多级文件夹

复制文件夹,如果这个文件夹中还有子文件夹,也可以复制过去。

源文件夹:
d:\source
aa.txt
bb.doc
cc文件夹
目标文件夹:
d:\dest
要把source文件夹复制到dest下面,source文件夹下面的所有的东西也会过去。

复制之后source里面的所有的东西都会放到下面文件夹中。
结果路径:d:\dest\source



思路:
1. 定义方法,用来将源文件夹中所有的东西复制到目标文件夹。
2. 先根据源文件夹和目标文件夹拼接一个结果路径。
3. 创建这个结果路径对应的文件夹。
4. 拿到源文件夹中的所有文件和文件夹。然后遍历。
5. 如果遍历到的是一个文件夹。递归调用把当前遍历到的这个文件夹作为源复制到结果文件夹中
如果遍历到的是一个文件,就复制文件。(使用输入流输出流一边读一边写。)

package cn.itcast.demo04.test;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

/*
    复制文件夹,如果这个文件夹中还有子文件夹,也可以复制过去。

    源文件夹:
        d:\source
                 aa.txt
                 bb.doc
                 cc文件夹
    目标文件夹:
        d:\dest
    要把source文件夹复制到dest下面,source文件夹下面的所有的东西也会过去。

    复制之后source里面的所有的东西都会放到下面文件夹中。
    结果路径:d:\dest\source



    思路:
        1. 定义方法,用来将源文件夹中所有的东西复制到目标文件夹。
        2. 先根据源文件夹和目标文件夹拼接一个结果路径。
        3. 创建这个结果路径对应的文件夹。
        4. 拿到源文件夹中的所有文件和文件夹。然后遍历。
        5. 如果遍历到的是一个文件夹。递归调用把当前遍历到的这个文件夹作为源复制到结果文件夹中
           如果遍历到的是一个文件,就复制文件。(使用输入流输出流一边读一边写。)

 */
public class Demo01CopyTest {
    public static void main(String[] args) throws IOException {
        copyDirToDir(new File("d:\\source"), new File("d:\\dest"));
    }

    /*
     * 定义方法,用来将源文件夹中所有的东西复制到目标文件夹。
     */
    public static void copyDirToDir(File source, File dest) throws IOException {
        // 先根据源文件夹和目标文件夹拼接一个结果路径。
        //父路径是目标文件夹。 子路径是源文件夹的名字
        File result = new File(dest, source.getName());
        //创建这个结果路径对应的文件夹。
        result.mkdirs();
        //拿到源文件夹中的所有文件和文件夹
        File[] files = source.listFiles();
        //遍历
        for(File thisFile : files) {
            //thisFile表示的是源文件夹中的每一个文件或者文件夹。
            //如果当前遍历到的是一个文件夹, 源文件夹就是当前遍历到的文件夹, 目的地文件夹就是结果文件夹result
            if(thisFile.isDirectory()) {
                copyDirToDir(thisFile, result);
            } else {
                //如果是一个文件,就要复制文件
                //创建输入流
                FileInputStream fis = new FileInputStream(thisFile);
                //拼接一个最终复制过去文件的路径
                File destFile = new File(result, thisFile.getName());
                //创建一个输出流
                FileOutputStream fos = new FileOutputStream(destFile);

                //开始读写
                //一次读取一个字节数组
                byte[] bArr = new byte[1024];
                int len;
                while((len = fis.read(bArr)) != -1) {
                    fos.write(bArr, 0, len);
                }
                //释放资源
                fos.close();
                fis.close();
            }
        }
    }
}