day011 IO【字节流、高效流】

1 概述

day011 IO【字节流、高效流】

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里面的代码无论如何都会执行到。

package cn.itcast.demo01.outputstream;

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

/*
    IO流的异常处理。
    无论如何,一定要保证流最终释放掉。
    把close的代码放入到finally,因为finally里面的代码无论如何都会执行到。
 */
public class Demo04OutputStreamException {
    public static void main(String[] args) {
        
        FileOutputStream fos = null;
        try {
            //创建字节输出流对象
            fos = new FileOutputStream("c.txt");
            //调用方法写入
            fos.write(100);//d
        } catch(IOException e) {
            e.printStackTrace();
        } finally {
            //释放资源
            try {
                if(fos != null) {
                    fos.close();
                }
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
    }
}

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. 释放资源。

 

 

一次读一个字节的原理

day011 IO【字节流、高效流】

一次读一个字节数组

day011 IO【字节流、高效流】

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

day011 IO【字节流、高效流】

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