Bootstrap

Java学习笔记——Java语言基础(十八)(IO流、字节流、字节缓冲流、字符流、字符缓冲流)

一、IO流

IO流的概述:
IO流用来处理设备之间的数据传输,Java对数据的操作是通过流的方式,用于操作流的对象都在IO包中,进行输入、输出操作。
输入:也叫作读取数据
输出:也叫作写出数据

1.1 IO流的分类

根据数据的流向:
输入流:把数据从其他设备读取到内存中的流
输出流:把数据从内存中写入其他设备上的流
根据数据的类型:
字节流:以字节为单位,读写数据的流
字符流:以字符为单位,读写数据的流

1.2 流的父类

字节输入流:InputStream
字节输出流:OutputStream
字符输入流:Reader
字符输出流:Writer

二、字节流

一切文件数据(文本、图片、视频)在存储时,都是以二进制数字的形式保存,一个一个的字节形式,那么在传输的时候也一样。字节流可以传输任意数据的文件。
无论使用什么流对象进行传输,底层传输的始终是二进制数据

2.1 字节输出流OutPutStream

字节输出流OutPutStream是所有字节输出流的超类,是一个抽象类。定义了字节输出流的基本共性方法

方法:

	public void close() :关闭此输出流并释放与此流相关联的任何系统资源。
    public void flush() :刷新此输出流并强制任何缓冲的输出字节被写出。
    public abstract void write(int b) :将指定的字节输出流。
    public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流。
    public void write(byte[] b, int off, int len) :从指定的字节数组写入len个字节,从偏移量 off 开始输出到此输出流。
2.2 文件输出流FileOutPutStream

FileOutPutStream类是文件输出流,用于将数据写出文件

写入数据的原理(内存-->硬盘) 输出
   Java程序---JVM---OS---OS调用数据的方法---把数据写入文件
2.2.1 构造方法

public FileOutputStream(File file) :创建文件输出流以写入由指定的 File对象表示的文件。
public FileOutputStream(String name) : 创建文件输出流以指定的名称写入文件。
构造方法的作用:创建一个FileOutputStream的对象,根据构造方法中传递的参数,创建一个空的文件,把FileOutputStream对象指向创建好的文件

注意:在创建一个流对象时,必须传入一个文件路径。该路径下,如果没有文件,就会创建该文件。如果有,就会将这个文件的内容清空
使用字节输出流OutPutStream中的write方法可以写入字节数据

public class Demo01 {
    public static void main(String[] args) throws IOException {
        FileOutputStream fos = new FileOutputStream("a.txt");
        //  public abstract void write(int b) :将指定的字节输出流。
        fos.write(98);
        byte[] bytes={98,99,100,101,102};
        //public void write(byte[] b) :将 b.length字节从指定的字节数组写入此输出流。
        fos.write(bytes);
        //public void write(byte[] b, int off, int len) :从指定的字节数组写入 len字节,从偏移量 off开始输出到此输出流。
        fos.write(bytes,1,2);
        /*
        那么我们想要在文件中显示100,需要写入3个字节*/
        fos.write(49);
        fos.write(48);
        fos.write(48);
        //写入字符串的方法,使用String类中的方法转为为字节数组
        String str="你好";
        fos.write(str.getBytes());
        System.out.println(Arrays.toString(str.getBytes()));//表示你好的字节数组:[-28, -67, -96, -27, -91, -67]
        //使用完流记得关闭
        fos.close();
    }
}

显示效果:
在这里插入图片描述
使用完流记得调用close方法:
(1)通知系统释放关于管理流指定文件的资源
(2)让IO流对象变成垃圾,等待垃圾回收器对其进行回收

2.2.2 追加写的构造方法

FileOutputStream(File file, boolean append) 创建一个向指定 File 对象表示的文件中写入数据的文件输出流。
FileOutputStream(String name, boolean append) 创建一个向具有指定 name 的文件中写入数据的输出文件流。
其中,第一个参数和上面的构造方法相同,指向一个文件。第二个参数是追加写的开关
true:不会覆盖原文件,继续在文件的末尾追加写数据
false:创建一个新的文件,覆盖原文件
当我们需要换行的时候,不同的系统存在不一样的情况,windows系统里可以使用“\r\n”,Linux里则为“\n”。Mac系统里为"\r"
代码演示:

public class Demo02 {
    public static void main(String[] args) throws IOException {
        //追加写 第二个参数为true 不会覆盖原文件
        FileOutputStream fos = new FileOutputStream("b.txt",true);
        for (int i = 0; i < 5; i++) {
            fos.write(("你好"+i).getBytes());
            //使用换行符
            fos.write("\r\n".getBytes());
        }
    }
}

将程序运行两次之后,b.txt的文件内容:
在这里插入图片描述

2.3 字节输入流InPutStream

字节输入流InPutStream是字节输入流的所有类的超类,可以读取字节信息到内存中。
其中定义了字节输入流的基本共性方法
public void close() :关闭此输入流并释放与此流相关联的任何系统资源。
public abstract int read() : 从输入流读取数据的下一个字节。
public int read(byte[] b) : 从输入流中读取一些字节数,并将它们存储到字节数组 b中 。

2.4 文件字节输入流FileInPutStream

FileInPutStream是文件输入流,从文件中读取数据

读取数据的原理:硬盘---内存  输入
     Java程序---JVM---OS---OS读取数据的方法---读取文件
2.4.1 构造方法

FileInputStream(File file) : 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的 File对象 file命名。
FileInputStream(String name) : 通过打开与实际文件的连接来创建一个 FileInputStream ,该文件由文件系统中的路径名 name命名。
创建一个FileInputStream对象,会把FileInputStream对象指向构造方法中读取的文件
注意:当创建一个文件字节输入流对象时,必须传入一个文件路径,如果该文件路径不存在,就会抛出FileNotFoundException异常
使用字节输入流的read方法可以读取数据
一次读取一个字节:

public class Demo01 {
    public static void main(String[] args) throws IOException {
        //构造方法传入刚在写入的文件路径
        FileInputStream fis = new FileInputStream("a.txt");
        //public abstract int read() 读取文件中的一个字节并返回,读取到文件的末尾返回-1
        //定义一个int类型接受read方法返回的字节
        int read=0;
        read=fis.read();
        System.out.println(read);//98`public class Demo02 {
    public static void main(String[] args) throws IOException {
        //构造方法传入刚在写入的文件路径
        FileInputStream fis = new FileInputStream("a.txt");
        //定义一个byte类型的数组
        byte[] bytes=new byte[1024];
        //定义一个int类型的变量 接受读取方法返回的有效个数
        int len=0;
        //read(byte[] b) ,每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回 -1
        while ((len=fis.read(bytes))!=-1){
            /*
            String(byte[] bytes, int offset, int length)
          通过使用平台的默认字符集解码指定的 byte 子数组,构造一个新的 String。offset开始索引 length:个数
             */
            System.out.println(new String(bytes,0,len));
        }
    }
}`
        read=fis.read();
        System.out.println(read);//98
        read=fis.read();
        System.out.println(read);//99
        //使用while循环读取   read=fis.read();读取一个字节,判断是否为-1
        while ((read=fis.read())!=-1){
            System.out.print(read+" ");//100 101 102 99 100 49 48 48 
        }
    }
}

一次读取多个字节
byte类型的数组:缓冲作用,存储读取到的多个字节。

public class Demo02 {
    public static void main(String[] args) throws IOException {
        //构造方法传入刚在写入的文件路径
        FileInputStream fis = new FileInputStream("a.txt");
        //定义一个byte类型的数组
        byte[] bytes=new byte[1024];
        //定义一个int类型的变量 接受读取方法返回的有效个数
        int len=0;
        //read(byte[] b) ,每次读取b的长度个字节到数组中,返回读取到的有效字节个数,读取到末尾时,返回 -1
        while ((len=fis.read(bytes))!=-1){
            /*
            String(byte[] bytes, int offset, int length)
          通过使用平台的默认字符集解码指定的 byte 子数组,构造一个新的 String。offset开始索引 length:个数
             */
            System.out.println(new String(bytes,0,len));
        }
    }
}
2.5 文件字节流的练习

复制文件

public class CopyFileDemo01 {
    /*
    文件的复制 :明确: 数据源 数据复制的位置
    步骤:1.创建文件输入流,构造方法绑定数据源
         2.创建文件输出流,构造方法绑定写入的目的地
         3.使用字节输入流中的read方法读取文件
         4.使用字节输出流中的write方法写入目的文件
         5.释放资源
    先读取(read):InPutStream
    后写入(write):OutPutStream
     */
    public static void main(String[] args) throws IOException {
        FileInputStream fis=new FileInputStream("D:\\james\\6944620.jpg");
        FileOutputStream fos=new FileOutputStream("D:\\IDEAlei\\File\\111\\222\\333\\6944620.jpg");
        //读取一个字节 写入一个字节
        /*int len=0;
        while ((len=fis.read())!=-1){
            fos.write(len);
        }*/
        //使用数组读取字节 效率高 速度快
        byte[] bytes=new byte[1024];
        int len=0;
        while ((len=fis.read(bytes))!=-1){
            fos.write(bytes,0,len);
        }
        //释放资源(先关闭写的,后关闭读的)
        fos.close();
        fis.close();
    }
}

三、字节缓冲流

缓冲思想:字节流一次读写一个数组的速度明显比一次读写一个字节的速度快很多,这个因为加入了数组的缓冲区。
Java同样提供了一对高效字节缓冲区流BufferedOutPutStreamBufferedInPutStream
构造方法:
BufferedOutputStream(OutputStream out) 创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
BufferedInputStream(InputStream in) 一个 BufferedInputStream 并保存其参数,即输入流 in,以便将来使用
使用代码:

public class CopyFileDemo02 {
    public static void main(String[] args) throws IOException {
        long start=System.currentTimeMillis();
        //构造方法中传入FileInputStream和FileOutputStream的两个匿名对象
        BufferedInputStream bis=new BufferedInputStream(new FileInputStream("D:\\james\\6944620.jpg"));
        BufferedOutputStream bos=new BufferedOutputStream(new FileOutputStream("D:\\IDEAlei\\File\\111\\222\\333\\6944620.jpg"));
        //使用数组读取字节
        int len=0;
        byte[] bytes=new byte[1024];

        while ((len=bis.read(bytes))!=-1){
            bos.write(bytes,0,len);
        }

        //释放资源(先关闭写的,后关闭读的)
        bos.close();
        bis.close();
        long end=System.currentTimeMillis();
        System.out.println(end-start);
    }
}

可以通过在高效流和普通流做数据传输是进行时间的比较。

四、字符流

当使用字节流兑取文本文件时,可能会有一些问题出现,就是在遇到中文字符时,可能不会显示完整的字符,那是因为中文字符可能占用多个字节存储。所以Java提供了一些字符流类,以字符为单位读写数据,专门用来处理文本文件。
字符流只能读写文本文件
*

4.1 字符输出流Writer

Writer用来表示写出字符流的所有类的超类,将指定的字符信息写出到目的地,是一个抽象类。
字符输出流的基本共性方法

	public void write(int c) 写一个字符
	public void write(char[] cbuf) 写一个字符数组
	public void write(char[] cbuf,int off,int len) 写一个字符数组的 一部分
	public void write(String str) 写一个字符串
	public void write(String str,int off,int len) 写一个字符串的一部分
	void flush() 刷新该流的缓冲。
	void close() 关闭此流,但要先刷新它。
4.2 字符到文件的便捷流FileWriter

FileWriter构造时使用系统默认的字符编码和默认的字节缓冲区

4.2.1 构造方法
FileWriter(File file) : 创建一个新的 FileWriter,给定要读取的File对象。
FileWriter(String fileName) : 创建一个新的 FileWriter,给定要读取的文件的名称。

当你创建一个流对象时,必须传入一个文件路径,类似于FileOutputStream

public class Demo01 {
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("d.txt");
        fw.write('你');//一次写入一个字符
        fw.write('好');
        fw.write("\r\n");//写入换行符
        fw.write("你好啊");//写入字符串
        fw.write("\r\n");
        //一次写入一个字符数组
        char[] chars={'你','好','啊'};
        fw.write(chars);
        fw.write("\r\n");
        //一次写入字符数组的一部分 从字符数组的1索引处开始,写入2个字符
        fw.write(chars,1,2);
        fw.write("\r\n");
        //一次写入字符串的一部分 从字符串的1索引处开始写入5个字符
        fw.write("你好啊,Java",1,5);
        fw.flush();
        fw.close();
    }
}

字符写入的时候,是将数据写入到内存缓冲区中(字符转换为字节),写入完成需要使用flush方法,把内存缓冲区的数据,刷新到文件中。
close方法会关闭资源前会将内存缓冲区的数据刷新到文件中。
流的关闭和刷新:

flush:刷新缓冲区,流对象可以继续使用。
close :先刷新缓冲区,然后通知系统释放资源。流对象不可以再被使用了。

续写和换行的操作类似于FileOutputStream,构造方法增加一个参数true,打开追加写开关

FileWriter fw = new FileWriter("d.txt"true);
4.3 字符输入流Reader

Reader用来表示读取字符流的所有类的超类,可以读取字符信息到内存中,是一个抽象类。
字符输入流的基本共性方法

public void close() :关闭此流并释放与此流相关联的任何系统资源。
public int read() : 从输入流读取一个字符。
public int read(char[] cbuf) : 从输入流中读取一些字符,并将它们存储到字符数组 cbuf中 。
4.4 读取字符文件的便捷类FileReader
4.4.1 构造方法
FileReader(File file) : 创建一个新的 FileReader ,给定要读取的File对象。
FileReader(String fileName) : 创建一个新的 FileReader ,给定要读取的文件的名称。

创建一个流对象时,必须传入一个文件路径。类似于FileInputStream
使用字符数组读取: read(char[] cbuf) ,每次读取b的长度个字符到数组中,返回读取到的有效字符个数,读取到末尾时,返回 -1 ,

public class Demo01 {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("e.txt");
        //使用数组读取文件的数据
        char[] chars = new char[1024];//存储读取到的多个字符
        int len = 0;//记录的是读取的有效字符个数
        while ((len = fr.read(chars)) != -1) {
            System.out.println(new String(chars, 0, len));
        }
    }
}

五、字符缓冲流

字符缓冲输出流(BufferedWriter):将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
字符输入流(BufferedReader):从中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。

5.1 构造方法
public BufferedReader(Reader in) :创建一个使用默认大小输入缓冲区的缓冲字符输入流。
public BufferedWriter(Writer out) : 创建一个使用默认大小输出缓冲区的缓冲字符输出流。

字符缓冲流和字节缓冲流的使用没太大区别,这里使用特有方法进行读写操作
特有方法:

BufferedReader: public String readLine() : 读一行文字。
BufferedWriter: public void newLine() : 写一行行分隔符,由系统属性定义符号。
public class Demo01 {
    public static void main(String[] args) throws IOException {
        //创建一个使用默认大小输入缓冲区的缓冲字符输入流
        BufferedReader br = new BufferedReader(new FileReader("MyTest.java"));
        //创建一个使用默认大小输入缓冲区的缓冲字符输出流
        BufferedWriter bw = new BufferedWriter(new FileWriter("MyTest1.java"));
        String line=null;
        //读一行文字
        while ((line=br.readLine())!=null){
            bw.write(line);
            bw.newLine();//写一行行分隔符
            bw.flush();
        }
        //释放资源
        bw.close();
        br.close();
    }
}

六、练习

6.1 把ArrayList集合中的字符串数据存储到文本文件
public class Test01 {
    public static void main(String[] args) throws IOException {
    //创建一个ArrayList集合
        ArrayList<String> list = new ArrayList<>();
        list.add("张三");
        list.add("李四");
        list.add("王五");
        //创建一个字符输出流对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("name.txt"));
        //遍历集合
        for (String s : list) {
            //写入数据
            bw.write(s);
            bw.newLine();
            bw.flush();
        }
        bw.close();
    }
}
6.2 把文本文件中的数据存储到集合中

我们可以读取6.1中文本文件的数据存储到集合中去

public class Test02 {
    public static void main(String[] args) throws IOException {
        //创建一个集合
        ArrayList<String> list = new ArrayList<>();
        //创建一个高效的字符输入流对象
        BufferedReader br = new BufferedReader(new FileReader("name.txt"));
        String line=null;
        while ((line=br.readLine())!=null){
            list.add(line);
        }
        br.close();
        System.out.println(list);
    }
}
6.3 复制单级文件夹
public class Test03 {
    public static void main(String[] args) throws IOException {
        File sourceFile = new File("C:\\Users\\asus\\Desktop\\music222");
        File targetFile = new File("C:\\Users\\asus\\Desktop\\music222copy");
        //判断复制文件是否存在
        if (!targetFile.exists()){
            targetFile.mkdirs();
        }
        copyFolder(sourceFile,targetFile);
        System.out.println("复制完成");
    }

    private static void copyFolder(File sourceFile, File targetFile) throws IOException {
        //进行复制 遍历文件夹下的每一个文件,复制到目标文件夹中
        File[] files = sourceFile.listFiles();
        for (File file : files) {
            //判断文件
            if (file.isFile()){
                copyFile(file,targetFile);
            }
        }
    }

    private static void copyFile(File file, File targetFile) throws IOException {
        FileInputStream fis = new FileInputStream(file);
        //封装目标文件
        File newfile = new File(targetFile, file.getName());
        FileOutputStream fos = new FileOutputStream(newfile);
        int len=0;
        byte[] bytes=new byte[1024];
        while ((len=fis.read(bytes))!=-1){
            fos.write(bytes,0,len);
        }
        fos.close();
        fis.close();
    }
}
6.4 复制多级文件夹

大体代码和单级文件夹相同,只需要修改遍历的方法

 private static void copyFolder(File sourceFile, File targetFile) throws IOException {
        //进行复制 遍历文件夹下的每一个文件,复制到目标文件夹中
        File[] files = sourceFile.listFiles();
        for (File file : files) {
            //判断文件
            if (file.isDirectory()) {
                //System.out.println(file.getName());
                //创建新的文件
                File mbFile = new File(targetFile, file.getName());
                if (!mbFile.exists()){
                    mbFile.mkdirs();
                }
                //递归
                copyFolder(file,mbFile);
            }else {
                copyFile(file, targetFile);
            }
        }
    }
;