• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

JAVA IO流使用之字节流、字符流、缓冲流、转换流、打印流、序列化流(长文预警!)

java 来源:We Der 9次浏览

学习IO流

以下均为个人手写整理,如果有什么纰漏,希望可以帮忙指出~
个人博客文章地址

一、IO流概念

我们把计算机中,数据的传输(比如复制粘贴)看作是数据的流动,IO流:
I即是Input,输入流;O即是Output,输出流。
我们可以站在电脑内存的角度去理解这个输入输出的意思。
Input输入流:我们可以理解为把磁盘中的数据输入到内存中;
Output输出流:同样的,就是把内存的数据输出到磁盘中。

Java 中的IO流:Java 对于IO流的操作,Jvm不是直接操作我们的磁盘
流程为:代码 ->Jvm(运行在内存中) -> Os(当前操作系统) -> 进行文件操作

二、字节流

字节流,顾名思义,就是传输的都是一个个的字节,而我们所有的文件在存储中都是一个个的字节组成的。故字节流可以传输一切文件。

2.1 字节输出流(OutputStream)

将指定的数据输出到磁盘中,其有很多子类,以下用FileOutputStream举例

FileOutputStream(File file) :指定一个File文件作为输出数据的目的地
FileOutputStream(String name) : 直接指定路径作为输出数据的目的地
如果指定的文件不存在,则会直接自动创建该文件
另外
输出数据到文件(输出流)方法 write()有以下几种
write(int b) :将指定的字节写入
write(byte[] b) :将指定数组写入,效率高
write(byte[] b, int off, int len) :将指定数组写入,off为改数组写入开始点(偏移量),len为长度。效率高,并且灵活

作用:调用write()方法,写入一个字节

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

public class UseIO { 
    public static void main(String[] args) throws IOException { 
        //指定输入的文件路径到当前文件夹(相对路径)
        FileOutputStream Os = new FileOutputStream("test.txt");

        Os.write(65);//写入的是字节

        Os.close();
    }
}

结果:

注意:

1.使用完输入输出流,一定要记得使用close()方法释放资源,以免造成系统资源的浪费
2.这种方法会将当前文件中的原数据进行覆盖,如果希望保留文件原来的数据,只是在文件后面追加数据,我们可以使用

FileOutputStream(File file, boolean append) : 创建文件输出流以写入由指定的 File对象表示的文件。
FileOutputStream(String name, boolean append) : 创建文件输出流以指定的名称写入文件。
例如上述例子中创建输出流改为
FileOutputStream Os = new FileOutputStream(“test.txt”, true);
即可实现追加数据

3.我们写入的是字节,系统的文件查看器,会参照Ascil表,将输入的字节转为字符,而65对应的就是字母A。那么怎么才能得到65呢?我们可以直接使用String中的getBytes()方法获取字节。请看:

public class UseIO { 
    public static void main(String[] args) throws IOException { 
        //指定输入的文件路径到当前文件夹(相对路径)
        FileOutputStream Os = new FileOutputStream("test.txt");

        String str = "65";
        Os.write(str.getBytes());//用String中的getBytes()方法,即可获取当前字符串的字节

        Os.close();
    }
}

结果:

以下还提供了比较简单的直接保存数据方式(无需转为字节保存)

2.1.1 采用打印流直接保存输入的数据

我们平常所用的System.out.println()或者System.out.println()都是来自于
PrintStream类,构造方法如下:
PrintStream(String fileName) : 使用指定的文件名创建一个新的打印流。
我们的println方法默认是打印在控制台上的,只要我们改变一下该数据流的流向,即可得到我们想要的功能

代码:

import java.io.FileNotFoundException;
import java.io.PrintStream;

public class Print { 
    public static void main(String[] args) throws FileNotFoundException { 
        //新建一个打印流
        PrintStream ps = new PrintStream("print.txt");

        //改变打印流流向
        System.setOut(ps);

        //打印我们需要的数据,将会打印到到指定的文件中
        System.out.println("测试数据");

    }
}

效果: 控制台无显示,数据在指定的文件中显示

2.2 字节输入流(InputStream)

即是将磁盘文件输入到内存中(读取磁盘文件),相对的,以 FileInputStream示例

read() : 从输入流读取数据的一个字节,然后指针会自动移到下一个字节位置,当无数据时,返回值为-1。
read(byte[] b) : 从输入流中按字节数组取出数据,并将它们存储到字节数组 b中,效率更高 。
原理:每一次调用read()都会返回文件中的一个字节。从第一个字节开始,使用了read()后,会返回第一个字节,然后指针会自动指向下一个字节。
返回过程:磁盘->OS(操作系统)->jvm
下图以之前生成的test.txt为例

文件

代码: 用输入流获取文件中的数据

import java.io.FileInputStream;
import java.io.IOException;

public class UseIO { 
    public static void main(String[] args) throws IOException { 
        //指定文件路径到当前文件夹(相对路径)
        FileInputStream Os = new FileInputStream("test.txt");

        /* 从read()方法的原理中,我们知道每次调用read(),都会使指针自动指向下一个字节, 再次使用read()将无法获取当前一个字节 所以我们需要定义一个数来接收返回的字节 */
        int data = 0;
        while ((data = Os.read()) != -1){ 
            System.out.println("返回的字节为" + data + ",转型后为" + (char)data);
        }


        Os.close();//释放资源
    }
}

结果:

2.3 使用字节输入输出流实现的文件的复制粘贴功能

因为字节流是可以传输所有类型的文件,这里我们用图片文件

该图片(pic.jpg)的字节大小:

代码: 建立字节输入和输出流将E盘的图片文件复制到D盘下

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

public class UseIO { 
    public static void main(String[] args) throws IOException { 
        // 记录开始时间
        long start = System.currentTimeMillis();

        //将文件复制到D盘,创建输入流获取文件(复制)
        FileInputStream Fis = new FileInputStream("E:\\pic.jpg");
        //创建输出流输出文件到磁盘(粘贴),指定粘贴的路径
        FileOutputStream Fos = new FileOutputStream("D:\\newPic.jpg");

        int data = 0;
        while ((data = Fis.read()) != -1){ 
            Fos.write(data);
        }





		//使用完后释放资源
        Fis.close();
        Fos.close();
        // 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("完成字节流所用复制时间:"+(end - start)+" 毫秒");

    }
}

结果:

如何提高复制效率?

前面已经讲到,我们直接用字节数组Byte[] 进行输入输出流操作,可以提高效率

read(byte[] b) : 从输入流中按字节数组取出数据,并将它们存储到字节数组 b中,效率更高 。
write(byte[] b, int off, int len) :将指定数组写入,off为改数组写入开始点(偏移量),len为长度。效率高,并且灵活

代码:

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

public class UseIO { 
    public static void main(String[] args) throws IOException { 
        // 记录开始时间
        long start = System.currentTimeMillis();

        //将文件复制到D盘
        FileInputStream Fis = new FileInputStream("E:\\pic.jpg");
        FileOutputStream Fos = new FileOutputStream("D:\\newPic.jpg");

        //用做演示,byte[]里的字节数可以填更大,2的n次方
        byte[] data = new byte[1024];

        //用作记录每次读完后,返回的字节长度,如果读完后,将返回-1
        int len = 0;

        //先对文件进行输入流中的read(byte[] b),会将读取到的字节存在data[]中,并且放回数组中的字节长度
        while ((len = Fis.read(data)) != -1){ 
            //输出流操作,将data[]中的字节输出到文件中,从data[0]开始,直到data[len]
            Fos.write(data , 0 , len);
        }

        Fis.close();
        Fos.close();
        // 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("完成字节流所用复制时间:"+(end - start)+" 毫秒");

    }
}

效果:

如果需要继续提高速度,我们就可以用到下面讲到的缓冲流

三、字符流

字符流是为了解决,我们使用字节输入输出流进行对带字符的文件,进行输入输出时,如果文件中带有中文(中文在UTF-8编码中占3个字节),使用字节流读取一个字节就输出的话,就会出现乱码的现象(只获取了1/3个字符),而字符流则是以一个字符为单位进行输入输出操作,就可以解决这种问题。

3.1 Writer/Reader(字符输出输入流)

它们子类都将以字符为单位,进行文件的输出,以下用FileWriter/FileReader进行举例,
FileWriter流与其它的流都有相似的方法,值得注意的是,它还有个
flush() 刷新该流的缓冲。
这个方法是将缓冲区的字符写入到文件中(或者直接调用close()方法也可以,该方法的存在是为了将已经写好的字符先刷到文件中,然后可以继续进行字符流的操作,而不需要关闭流)
FileReader类的使用与FileInputStream 相似,但是如果使用数组进行读取的话,传入的是字符数组 char[] ,而不是byte[]

直接用代码进行说明:

import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class UseWRS { 
    public static void main(String[] args) throws IOException { 
        //创建一个字符输出流.传入需要输出(写入)文件的名称,也可以是一个文件对象
        FileWriter fileWriter = new FileWriter("newTest.txt");
        //创建一个字符输入流.传入需要输入(读取)文件的名称,也可以是一个文件对象
        FileReader fileReader = new FileReader("newTest.txt");

        //创建一个字节输入流,用作测试对比
        FileInputStream fileInputStream = new FileInputStream("newTest.txt");

        //使用FileWriter中的write方法,将需要的字符写到缓冲区中
        fileWriter.write("测试");

        /* 这里我们测试如果没有使用flush()或者close()方法,字符是否已经写入文件中 使用了字符输入流中的read方法,读取的使用方式与FileInputStream相同 */
        System.out.println("使用flush()方法前:");
        int str1;
        while((str1 = fileReader.read()) != -1){ 
            System.out.println("一次读取到的字节:"+str1+",转换为字符"+(char)str1);
        }


        /* 这里我们测试使用flush()方法后读取到的数据,字符是否已经写入文件中 使用了字符输入流中的read方法,读取的使用方式与FileInputStream相同 */
        System.out.println("使用flush()方法后:");
        fileWriter.flush();
        int str2;
        while((str2 = fileReader.read()) != -1){ 
            System.out.println("一次读取到的字节:"+str2+",转换为字符:"+(char)str2);//一次性读取了三个字节,所以可以直接显示一个中文
        }

        //如果使用字节输入流读取该文件
        System.out.println("使用字节输入流读取后显示:");
        int str3;
        while((str3 = fileInputStream.read()) != -1){ 
            System.out.println("一次读取到的字节:"+str3+",转换为字符:"+(char)str3);//一次获取一个字节,但一个中文是三个字节,故出现乱码
        }

        //使用FileWriter中的write方法,将需要的字符写到缓冲区中
        fileWriter.write("关闭");
        //直接关闭流也会自动执行flush()
        System.out.println("直接执行close()显示:");
        fileWriter.close();
        int str4;
        while((str4 = fileReader.read()) != -1){ 
            System.out.println("一次读取到的字节:"+str4+",转换为字符:"+(char)str4);
        }

        //使用完流操作都需要close()释放资源
        fileReader.close();

    }
}

四、缓冲流

缓冲流,可以提高读写效率的效率,可以理解为是以上四种流的加强版
字节缓冲流:
BufferedInputStream ( FileInputStream的加强版)
BufferedOutputStream ( FileOutputStream的加强版)
字符缓冲流:
BufferedReader (FileReader的加强版)
BufferedWriter (FileWriter的加强版)
在使用时,我们传入的是对应的流对象,相当于给基本流穿上装备
BufferedInputStream(InputStream in) :创建一个新的缓冲输入流。
BufferedOutputStream(OutputStream out) : 创建一个新的缓冲输出流。
BufferedReader(Reader in) :创建一个 新的缓冲输入流。
BufferedWriter(Writer out) : 创建一个新的缓冲输出流。

4.1 字节缓冲流

我们直接用之前的文件复制进行示例

代码:

import java.io.*;

public class UseIO { 
    public static void main(String[] args) throws IOException { 
        // 记录开始时间
        long start = System.currentTimeMillis();

        //创建缓冲流测试效率
        BufferedInputStream Bis = new BufferedInputStream(new FileInputStream("E:\\pic.jpg"););
        BufferedOutputStream Bos = new BufferedOutputStream(new FileOutputStream("D:\\newPic.jpg"););

        //用做演示,byte[]里的字节数可以填更大
        byte[] data = new byte[1024];

        //用作记录每次读完后,返回的字节长度,如果读完后,将返回-1
        int len = 0;

        //先对文件进行输入流中的read(byte[] b),会将读取到的字节存在data[]中,并且放回数组中的字节长度
        while ((len = Bis.read(data)) != -1){ 
            //输出流操作,将data[]中的字节输出到文件中,从data[0]开始,直到data[len]
            Bos.write(data , 0 , len);
        }
		//关闭缓冲流即可释放资源
        Bis.close();
        Bos.close();

        // 记录结束时间
        long end = System.currentTimeMillis();
        System.out.println("完成缓冲字节流所用复制时间:"+(end - start)+" 毫秒");

    }
}

由之前复制实践中,未使用缓冲流所用时间:

使用缓冲流后: 由于文件比较小,所有差距比较小,但是在大文件中更为明显,可自行测试

4.2 字符缓冲流

这个主要需要了解的是,它比起普通的字符流,有着两个特有方法
readLine() : 读一行文字,并返回一个字符串。
newLine() : 写一行行分隔符,由系统属性定义符号(比方说Window系统为:\r\n Linux系统为\n)。

代码示例:

public class BuffTest { 
    public static void main(String[] args) throws IOException { 
        // 创建缓冲字符输出流对象
        BufferedWriter bw = new BufferedWriter(new FileWriter("test.txt"));
        // 创建缓冲字符输入流对象
        BufferedReader br = new BufferedReader(new FileReader("test.txt"));

        //我们先对文件进行输出(写入)数据
        bw.write("写");
        bw.newLine();//插入换行
        bw.write("数");
        bw.write("据");

        //如果暂时不需要关闭输出流,我们需要调用flush()将数据从缓冲区写入
        bw.flush();

        // 定义字符串,保存读取的一行文字
        String line = null;
        // 循环读取,读取到最后返回null
        while ((line = br.readLine())!=null) { 
            System.out.println(line);
            System.out.println("——————————");//分割显示每一行
        }
        // 释放资源
        br.close();
        bw.close();
    }
}

test文件:

运行显示:

五、转换流

转换流,就是可以转换我们数据的编码格式
例如,我们的文件保存的编码格式为GBK,如果我们用UTF-8的编码格式打开的话,将会显示乱码:
新建一个系统默认文本文件(window默认编码格式为ANSL GBK),然后在IDE(默认格式为UTF-8)打开查看会显示乱码(直接用字节流/字符流读取也会是乱码):
新建文件:

在IDE中显示出现乱码:

这时,我们就可以使用转换流来进行格式的转换,以下为方法,使用方式其实都与输入输出流差不多,只是新增了一个指定编码格式
InputStreamReader(InputStream in) : 创建一个使用默认字符集的字符流。
InputStreamReader(InputStream in, String charsetName) : 创建一个指定字符集的字符流
OutputStreamWriter(OutputStream in) : 创建一个使用默认字符集的字符流。
OutputStreamWriter(OutputStream in, String charsetName) : 创建一个指定字符集的字符流。

代码:

public class Change { 
    public static void main(String[] args) throws IOException { 
        //新建输入转换流,注意,选取的编码格式必须是文件的原格式
        InputStreamReader tsr = new InputStreamReader(new FileInputStream("test.txt"),"GBK");

        //新建一个输出流,将获取到的输入流中的数据,转换成指定的编码格式,并保存
        OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("newTest.txt"),"UTF-8");

        //与复制粘贴过程类似,读取文件中的数据,然后输出
        int len = 0;

        while((len = tsr.read()) != -1){ 
            osw.write(len);
        }

        //使用完后释放资源
        tsr.close();
        osw.close();
    }
}

结果成功读取:

六、序列化流

序列化,主要用作保存对象,并且可以将对保存后的字节数据重新转换为对象,以字节表示对象
即:对象 —> 字节文件 , 字节文件—>对象,对象转换为字节文件叫做对象的序列化,反之,则叫反序列化
序列化有什么用(参考)?
1、当你想把的内存中的对象保存到一个文件中或者数据库中时候;
2、当你想用套接字在网络上传送对象的时候;
3、当你想通过RMI传输对象的时候;
构造方法:
ObjectOutputStream(OutputStream out) : 创建一个指定OutputStream的ObjectOutputStream
writeObject (Object obj) : 将指定的对象输出到文件。
ObjectInputStream(InputStream in) : 创建一个指定InputStream的ObjectInputStream。
readObject () : 读取一个对象。

直接在代码中说明:
目录:

Person类:

import java.io.Serializable;

//对象的序列化,必须实现Serializable接口
public class Person implements Serializable { 
    /* 序列化后,如果我们对对象的文件进行更改,然后再进行反序列化操作,将会出现 ClassNotFoundException异常,找不到class文件(class文件已经被修改,序列号会改变,所以找不到) 为了防止这种情况的发生,我们可以直接固定序列号 */
    //加入序列版本号,修改class文件后,反序列化也不会出现异常
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    //加入transient属性,可以使该属性无法被序列化
    private transient String sex;


    public Person() { 
    }

    public Person(String name, int age, String sex) { 
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    @Override
    public String toString() { 
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
}

TestSer测试类:

import java.io.*;

public class TestSer { 
    public static void main(String[] args) throws IOException, ClassNotFoundException { 
        //新建序列化输出流
        ObjectOutputStream Oos = new ObjectOutputStream(new FileOutputStream("test.txt"));

        //新建序列化输入流
        ObjectInputStream Ois = new ObjectInputStream(new FileInputStream("test.txt"));

        //实例化对象
        Person person = new Person("张三",18,"男");

        //序列化对象
        Oos.writeObject(person);

        //反序列化
        Person TestPerson;
        TestPerson = (Person) Ois.readObject();

        //查看是否反序列化成功,因为sex属性已经用transient修饰,所以不会被序列化,故返回为null值
        System.out.println(TestPerson.toString());

        //关闭流
        Ois.close();
        Oos.close();
    }
}

运行结果:

序列化文件无法直接查看


版权声明:本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。
喜欢 (0)