JavaImprove--Lesson10--IO流-字符流,缓冲流,转换流,打印流,数据流

一.IO流-字符流

上期在字节流的学习中,了解到字节流写字符操作表现很不好,随时面临乱码的情况,一下写完全部数据的,内存可能不足,所以对于文本操作还需要专业的操作   

而字符流就非常适合操作文本字符

FileWirte文件字符输入流

作用:以内存为基准,可以把文件的数据以字符的形式输入到内存中去

构造器:

public FileReader(File file);以文件的形式,创建字符流与其的链接

public static void main(String[] args) throws Exception {
    File file = new File("static/text/test.txt");
    FileReader rd = new FileReader(file);
    rd.close();
}

 

public FileReader(String pathName);以路径的形式,创建字符与其的链接

public static void main(String[] args) throws Exception {
    Reader rd = new FileReader("static/text/test.txt");
    rd.close();
}

 

文件字符输入流所有的方法:

public int read()每读取一个字符返回,如果没发现数据可读则返回-1

public static void main(String[] args) throws Exception {
    Reader rd = new FileReader("static/text/test.txt");
    int len;
    //每次读取一个字符
    while ((len = rd.read())!=-1){
        System.out.print((char) len);
    }
    rd.close();
}

 

public int read(char[ ] buffer);每次读取一个字符数组的长度,返回读取字符的多少,没有字符后返回-1

public static void main(String[] args) throws Exception {
    Reader rd = new FileReader("static/text/test.txt");
    int len;
    //申请每次读取的字符数组大小
    char[] buffer = new char[8];
    //每次读取一个字符
    while ((len = rd.read(buffer))!=-1){
        System.out.print(new String(buffer,0,len));
    }
    rd.close();
}

字符输入流是以字符为单位,所以不会乱码

 FileWriter文件字符输出流

作用:以内存为基准,把内存中的数据以字符的形式写出到文件

构造器:

public FileWriter(File flie)以文件的形式,与字符输出流创建管道

public static void main(String[] args) throws Exception {
    //以文件的方式创建链接
    File file = new File("static/text/test.txt");
    FileWriter fw = new FileWriter(file);
    fw.close();
}

 

public FileWriter(String pathName);以路径的形式,与字符输出流创建管道

public FileWriter(String pathName,boolean append);

public static void main(String[] args) throws Exception {
    //以路径的方式创建链接
    FileWriter fw = new FileWriter("static/text/test.txt",true);//追加的方式写出
    fw.close();
}

 

字符输出流的方法:

void writer(int  c);写出一个字符

public static void main(String[] args) throws Exception {
    //以路径的方式创建链接
    FileWriter fw = new FileWriter("static/text/test.txt",true);//追加的方式写出
    fw.write('c');
    fw.close();
}

 

void  writer(String  str);写一个字符串

public static void main(String[] args) throws Exception {
    //以路径的方式创建链接
    FileWriter fw = new FileWriter("static/text/test.txt",true);//追加的方式写出
    fw.write("abcdef");
    fw.close();
}

 

void writer(String str,int pos,int len);写出写出一个字符串的一部分

public static void main(String[] args) throws Exception {
    //以路径的方式创建链接
    FileWriter fw = new FileWriter("static/text/test.txt",true);//追加的方式写出
    fw.write("我爱中国,asd",0,4);
    fw.close();
}

 

void writer(char[ ] buffer);写出一个字符串数组

public static void main(String[] args) throws Exception {
    //以路径的方式创建链接
    FileWriter fw = new FileWriter("static/text/test.txt",true);//追加的方式写出
    char[] str = {'a','b','c'};
    fw.write(str);
    fw.close();
}

 

void writer(char[ ] buffer,int pos,int len);写出一个字符串数组一部分

 

public static void main(String[] args) throws Exception {
    //以路径的方式创建链接
    FileWriter fw = new FileWriter("static/text/test.txt",true);//追加的方式写出
    fw.write("\r\n");//写入换行符
    char[] str = {'a','b','c'};
    fw.write(str,0,2);
    fw.close();
}

 

注意点:

在使用文件输出流时,必须刷洗流或者关闭流,写出去的数据才能生效

原因:在Java写出字符时,会频繁的操作系统资源,为了减小系统资源的消耗,Java引入缓冲机制,也就是在写字符操作时,不管你操作多少次,写一个字符还是多个字符,都没有直接写入硬盘,而是写入到了内存缓冲区中了

当我们关闭流或者刷新流时,才会刷入硬盘,这样只操作一次就完成了输出

还有当缓冲区被装满时,会自动刷入一次硬盘,关闭的流就不能用了,必须重新建立链接

总结:字节流适合写入非文本文件,字符流适合写入文本文件

 二.缓冲流Buffered

 对原始流的包装形式,以提高原始流的读写数据效率

 它们并不是直接连接到数据源或数据目标,而是连接到另一个流(称为节点流或底层流),并为其提供一个缓冲区。

当从缓冲流读取数据时,它会尝试从内部缓冲区提供数据,而不是立即从底层流中读取,从而减少了从底层数据源读取数据的次数

低级流的意思是更接近于底层的流

缓冲流的工作原理:

  1. 缓冲流Buffer并不直接连接底层管道,而是将初始流做一个包装
  2. Buffer中自带一个8kb大小的缓冲区,当初始流连接上管道后,并不会直接读取到程序中,而是装满这8kb的buffer,然后程序在这个buffer中慢慢拿
  3. 所以,不管程序一次I/O需要拿多少数据,都是在buffer中拿的,而buffer就是第一I/O一次装入的,和程序的拿取无关
  4. 目的就是提高I/O效率,不让程序频繁调用系统接口,尤其避免程序每次写一个字节,然后写1024次的情况

 BufferedInputStream字节缓冲输入流

public static void main(String[] args) {
    try (
            //拿取文件管道流
            InputStream is =new FileInputStream("static/text/test.txt");
            //封装成缓冲流
            InputStream bis = new BufferedInputStream(is)
    ){
        byte[] buffer = new byte[1024];
        //循环拿取数据
        int len;
        while ((len = bis.read(buffer)) != -1){
            System.out.println(new String(buffer,0,len));
        }
    }catch (Exception e){
        System.out.println(e);
    }
}

 

可看源码,是否有8kb的缓冲区:

 如上:默认的buffer大小就是8kb,但是其实它是可以更改的,可以人为扩大的

 //封装成缓冲流
 InputStream bis = new BufferedInputStream(is,8192 * 4)

 

这段代码又将缓冲区改为了8kb * 4 就是32kb

BufferedOutputStream字节缓冲输出流

public static void main(String[] args) {
    try (
            //拿取文件管道流
            OutputStream os =new FileOutputStream("static/text/test.txt");
            //封装成缓冲流
            OutputStream bos = new BufferedOutputStream(os)
    ){
        //构造一个字节数组
        byte[] buffer = {'a','b','c','d','e'};
        bos.write(buffer);
        
    }catch (Exception e){
        System.out.println(e);
    }
}

 

BufferedReader字符缓冲输入流

BufferedReader和字节流的定义都是一样的

但是和原始流相比新增了一个方法

public  String readLine();读取一行字符回来,将读取字符按行为单位来读取,当没有字符时返回null

public static void main(String[] args) {
    try (
            //创建字符流和文件的管道连接
            Reader fd = new FileReader("static/text/test.txt");
            //封装成缓冲流
            BufferedReader bfd = new BufferedReader(fd)
            ){
        String rs=null;
        while ((rs=bfd.readLine())!=null){
            System.out.println(rs);
        }
    }catch (Exception e){
        e.printStackTrace();
    }
}

 

BufferedWriter字符缓冲输出流

字符缓冲输出流也有一个新的方法,是一个换行方法

public void newLine()换行

public static void main(String[] args) {
    try (
            //创建字符流和文件的管道连接
            Writer fw = new FileWriter("static/text/test.txt");
            //封装成缓冲流
            BufferedWriter bfd = new BufferedWriter(fw)
            ){
        bfd.write("虽然,我爱这世界");
        bfd.newLine();
        bfd.write("但是,与之相比,我更爱我的祖国");
        bfd.newLine();
    }catch (Exception e){
        e.printStackTrace();
    }
}

 

总结

由于缓冲流和不同的流使用没有什么新的特点,所以就没有过多赘述,我们只需要知道它是提供更高性能的流就好了,尽量都使用缓冲流吧

案例

将一段文本按行分类,标上序号(乱序),然后根据需要排序后,按行输出文本正确的序号

题目:

3.abcde
5.hladjdla
4.dhaldajld
9.addadad
1.dadadjald
6.adlajldjald
2.dadadkahdkad
7.poadpiadpa

要求输出:

1.dadadjald
2.dadadkahdkad
3.abcde
4.dhaldajld
5.hladjdla
6.adlajldjald
7.poadpiadpa
9.addadadd

代码展示:

public static void main(String[] args) {
    try (   //获得输入输、出流与文件建立连接
            Reader rd = new FileReader("static/text/MarchingList.txt");
            Writer wr = new FileWriter("static/text/test.txt");
            //创建buffer缓冲流封装文件流
            BufferedReader brd = new BufferedReader(rd);
            BufferedWriter bwr = new BufferedWriter(wr);
            ){
        //申请一个集合存储文件行,为什么不直接写出去,而是存在集合中?因为没有排序
        ArrayList<String> list = new ArrayList<>();
        String rs =null;
        while ((rs = brd.readLine())!= null){
            list.add(rs);
        }
        //排序,使用Collections自带的排序算法
        Collections.sort(list);
        //读取集合中排好序的行,并写出到文件
        for (String s : list) {
            bwr.write(s);
            //换行
            bwr.newLine();
        }
        bwr.flush();//刷入内存
    }catch (Exception e){
        e.printStackTrace();
    }
}

 

注意:

像上面这种例子,一次需要申请两个流(输入/输出流)时,它们两个流的路径不能指向同一个文件,否则输入流读不到数据

原因:

1.当输入流在读文件时,会上锁此文件,只要还没关闭此读写流,则不会释放锁,而此时去写文件会没有权限,所以写不进去

2.当申请完输出流和文件建立连接时,说明此时文件存在写操作,所以会上锁,当输入流在读取文件时,会因为锁没有权限,读取文件失败

所以尽量文件读取写入不要重名文件,出了问题很难找

缓冲流的性能

缓冲流的性能如何?需要测试

分别使用原始字节流,以及字节缓冲流复制一个很大的视频

测试步骤:

1.使用低级流一个一个字节的复制

2.使用低级流以数组形式的复制

3.使用缓冲高级流一个一个字节的复制

4.使用缓冲高级流以数组形式的复制

代码如下:

public static void main(String[] args) {
    //测试数据为:300MB
    //test1();//一个一个字节复制太慢了,懒得等了直接关了
    test2(); //3.258s  字节数组:低级流
    test3(); //6.815s  一个一个字节:缓冲流
    test4(); //0.538s  字节数组:缓冲流
}
//一个一个字节复制,并且使用低级流
public static void test1(){
    long t1 = System.currentTimeMillis();
    try (
            //得到流
            InputStream is = new FileInputStream("C:\\Users\\86173\\Videos\\Captures\\happyNewYear.mp4");
            //输出流
            OutputStream os = new FileOutputStream("C:\\Users\\86173\\Videos\\Captures\\happyNewYear1.mp4");
            ){
        int len;
        while ((len = is.read())!=-1){
            os.write(len);
        }
        long t2 = System.currentTimeMillis();
        System.out.println("字节输出流一个一个输出时间为:"+(t2-t1)/1000.0);
    }catch (Exception e){
        e.printStackTrace();
    }
}
//使用1kb数组复制,低级流
public static void test2(){
    long t1 = System.currentTimeMillis();
    try (
            //得到流
            InputStream is = new FileInputStream("C:\\Users\\86173\\Videos\\Captures\\happyNewYear.mp4");
            //输出流
            OutputStream os = new FileOutputStream("C:\\Users\\86173\\Videos\\Captures\\happyNewYear2.mp4");
    ){
        int len;
        byte[] buffer = new byte[1024];
        while ((len = is.read(buffer))!=-1){
            os.write(buffer,0,len);
        }
        long t2 = System.currentTimeMillis();
        System.out.println("字节输出流一个一个输出时间为:"+(t2-t1)/1000.0);
    }catch (Exception e){
        e.printStackTrace();
    }
}
//一个一个字节复制,缓冲流
public static void test3(){
    long t1 = System.currentTimeMillis();
    try (
            //得到流
            InputStream is = new FileInputStream("C:\\Users\\86173\\Videos\\Captures\\happyNewYear.mp4");
            //输出流
            OutputStream os = new FileOutputStream("C:\\Users\\86173\\Videos\\Captures\\happyNewYear3.mp4");
            //缓冲流
            BufferedInputStream bis = new BufferedInputStream(is);
            BufferedOutputStream bos = new BufferedOutputStream(os);
    ){
        int len;
        while ((len = bis.read())!=-1){
            bos.write(len);
        }
        long t2 = System.currentTimeMillis();
        System.out.println("字节输出流一个一个输出时间为:"+(t2-t1)/1000.0);
    }catch (Exception e){
        e.printStackTrace();
    }
}
//字节数组复制,缓冲流
public static void test4(){
    long t1 = System.currentTimeMillis();
    try (
            //得到流
            InputStream is = new FileInputStream("C:\\Users\\86173\\Videos\\Captures\\happyNewYear.mp4");
            //输出流
            OutputStream os = new FileOutputStream("C:\\Users\\86173\\Videos\\Captures\\happyNewYear4.mp4");
            //缓冲流
            BufferedInputStream bis = new BufferedInputStream(is);
            BufferedOutputStream bos = new BufferedOutputStream(os);
    ){
        int len;
        byte[] buffer = new byte[1024];
        while ((len = bis.read(buffer))!=-1){
            bos.write(buffer,0,len);
        }
        long t2 = System.currentTimeMillis();
        System.out.println("字节输出流一个一个输出时间为:"+(t2-t1)/1000.0);
    }catch (Exception e){
        e.printStackTrace();
    }
}

 

性能测试图测试,数据为300MB的视频复制:

 总结:

  • 当一个一个字节写入时,不管是不是缓冲流,效率都很低,但是缓冲流确实比原始流快
  • 当为字节数组时,速度有质的飞跃,在1024字节中,缓冲流比原始流快,原因是缓冲流buffer为8kb,而原始流的buffer很小(没错原始流也有buffer)
  • 当扩大字节数组大小时缓冲流提升不大,而原始流提升很大
  • 当达到某个阈值时,两个流速度等大,且增加字节数组大小增速也慢下来了,原因是,此时问题并不在内存,而是内存和硬盘之间速度不匹配的原因,也就是内存写的速度可能只要零点几毫秒(还是很快),而内存写入硬盘需要零点几秒(这里很慢),所以此时再增大字节数组效果不明显了,这时候就是硬件的原因

故:字节数组读取写入数据的确是越大越好,尤其是在8~32kb之间,两个流是一样的,并不需要特别使用那个流,而越往后就不需要增加大小了,因为硬件速度跟不上了,继续增大只会消耗更大的内存空间,没必要

在自己不确定使用字节数组大小时,推荐使用buffer缓冲流,Java官方是很关注开发者群体的,它提供的方案一般考虑了大多数情况

当然,你想自定义原始流的字节数组来写入也是可以的

 三.转换流

引入:不同编码的读取会出现乱码

如果读入的编码和文件的编码是一样的,使用字符流读取则不会出现乱码情况 

如果读取的编码和文件的编码是不一样的,使用字符流去读取则会出现乱码的情况

示例:如下有一段GBK编码的文本t2.txt

我爱中国,我爱世界
I love China,And I love the world!

使用UTF-8去读文件:

public static void main(String[] args) {
    //Idea默认编码UTF-8
    try (
            //申请一个文件读取流
            Reader fr = new FileReader("static/text/t2.txt");
            ){
        //循环读取文件
        int len;
        while ((len = fr.read())!=-1){
            System.out.print((char) len);
        }
    }catch (Exception e){
        e.printStackTrace();
    }
}

 

输出如下:

�Ұ��й����Ұ�����
I love China,And I love the world!

  由此可知,文件编码不一致,会导致读取乱码

所以,Java提供了转换流,用来按照选定编码读取文件,就不会乱码了

InputStreamReader字符输入转换流

 字符输入转换流是字符流Reader下的实现流,专门为解决读取文件时,平台编码和文件编码不一致,最终导致乱码的情况

解决思路:

先获取文件的原始字节流,再将其按真实的字符集转成字符流,这样流就不会乱码了

换个说法,在读取文件时,编码不在使用默认的平台编码,而是你传入的编码字符集

构造器:

public InputStreamReader(InputStream is,String  charset);

把原始字节流,按照指定的字符集编码成字符流

public static void main(String[] args) {
    try(
            //普通字节输入流
            InputStream is = new FileInputStream("static/text/t2.txt");
            //装入字符转换输入流:传入字节输入流,编码字符集
            InputStreamReader isr = new InputStreamReader(is,"GBK");
            ) {
        //通过字符转换输入流读取文件
        int len;
        while ((len = isr.read())!=-1){
            System.out.print((char) len);
        }
    }catch (Exception e){
        e.printStackTrace();
    }
}

 

使用字符转换输入流读取效果:

我爱中国,我爱世界
I love China,And I love the world!

OutputStreamWriter字符转换输出流

字符抓换输出流的思想和输入流一样,就是按照自定义的字符集进行编码,然后输出到文件

解决思路:

获取字节输出流,然后指定字符集编码成字符输出流,写出去的字符就会使用此字符集

构造器:

public OutputStreamWriter(OutputStream os,String charset);

把原始的字节输出流,按照指定字符集转换为字符输出流

public static void main(String[] args) {
    try (
            //获得原始字节输出流
            OutputStream os = new FileOutputStream("static/text/t2.txt");
            //装入字符转换输出流
            OutputStreamWriter osw = new OutputStreamWriter(os,"GBK");
            ){
        //写出一个文本
        String rs = "我爱中国";
        osw.write(rs);
        
    }catch (Exception e){
        e.printStackTrace();
    }
}

 

四.打印流Print

打印流,顾名思义就是打印出去,所以打印流只有输出流,没有输入流

而打印流有两个实现类,PrintStream,PrintWriter

PrintStream是OutputStream字节输出流的实现类,而PrintWriter则是Writer字符输出流的实现类

作用:

打印流可以更加方便的将数据打印出去,更高效,能实现参数是什么,打印的就是什么

写出方法writer写出97可能写出的是a,打印流print就是97,所以更高效

PrintStream

构造器:

public PrintStream(OutputStream/File/Path );打印流可以通向输出流,文件,路径,也是一个高级流

public PrintStream(path,boolean autoFlush,String encode);第一个参数是路径,第二个参数是自动刷新写入文件,第三个参数是指定字符集

方法:

public void println(数据类型)打印任何数据出去

public static void main(String[] args) {
    try(
            //创建一个打印输出流
            PrintStream ps =new PrintStream("static/text/t2.txt");
            ){
        ps.println("我爱中国");
        ps.println(97);
        ps.println('a');
    }catch (Exception e){
        e.printStackTrace();
    }
}

 

PrintWriter

构造器:

public PrintWriter(OutputStream/File/Path );打印流可以通向输出流,文件,路径,也是一个高级流

public PrintWriter(path,boolean autoFlush,String encode);第一个参数是路径,第二个参数是自动刷新写入文件,第三个参数是指定字符集

方法:

public void println(数据类型)打印任何数据出去

public static void main(String[] args) {
    try(
            //创建打印流
            PrintWriter pw = new PrintWriter("static/text/t2.txt");
            ){
        pw.println("我爱中国");
        pw.println(97);
        pw.println('a');
    }catch (Exception r){
        r.printStackTrace();
    }
}

 

总结:

打印数据的功能上是一摸一样的:都是方便,性能高效(核心优势)

PrintStream继承自字节流,可以写出字节的方法

PrintWriter继承自字符流,可以写出字符的方法

输出语句重定向

System.out.println()这是一句输出内容到控制台中

输出语句的重定向可以将此语句输出到文件中

因为在out中,也接入了一个流,就是连接控制台的管道流;所以只需要改流到文件中,就可以输出到文件中

public static void main(String[] args) {
    try (
            //控制台输出到文件的位置
            OutputStream is = new FileOutputStream("static/text/t2.txt",true);
            //装载如输出流
            PrintStream ps = new PrintStream(is);
            ){
        //装入打印流,更改输出源为文件t2.txt
        System.setOut(ps);
        System.out.println("我爱中国");
    }catch (Exception e){
        e.printStackTrace();
    }
}

 

如上:

System.out.println("我爱中国");这句话就输出到文件中了

 五.数据流

 数据流是一种特殊的流,在前面学习的流,不管是字节流,字符流,还是其它流,读取和写入的时候都是以String类型或byte类型的

而数据流就如其名字一样,可以写出数据和数据类型的,会带有其数据类型,如int,boolean,double类型等

但是写出的文件,我们可能看不懂,毕竟有类型在,要想看必须使用读入流读取后才能看到

DataOutputStream数据字节输出流

构造器:

public DataOutputStream(OutputStream os);创建数据输出流,包装基础输出流

方法:

public final void writeByte(int v);输出byte的数据类型

public final void writeInt(int v);输出int型数据

public final void writeDouble(double v)输出double类型的数据到文件

public final void writeUTF(String str)输出一个字符串到文件

public static void main(String[] args) {
    try (
            //低级文件字节输出流
            OutputStream os = new FileOutputStream("static/text/t3.txt");
            //装载到数据输出流中
            DataOutputStream dos = new DataOutputStream(os);
            ){
        dos.writeByte(97); //字节
        dos.writeInt(1);  //整型
        dos.writeBoolean(true);//布尔型
        dos.writeDouble(98.1);//双精度浮点型
        dos.writeUTF("我爱中国");//字符串
    }catch (Exception e){
        e.printStackTrace();
    }
}

 

写入后文件内容如下:

 虽然看不懂,也不需要去解码,因为带了类型,所以解码不出来的,本来也不是给我们看的,而是数据读入流去读取的

使用读取流读取后就可以输出看到,但是读取的时候,注意类型对应,先存入的是字节型,读取的时候也应该是字节型先读取

DataInputStream数据字节输入流

构造器:

public DataInputStream(InputStream is);创建数据输出流,包装基础输出流

方法:

public final void readByte();读取byte的数据类型

public final void readInt(int v);读取int型数据

public final void readDouble(double v)读取double类型的数据到文件

public final void readUTF(String str)读取一个字符串到文件

public static void main(String[] args) {
    try (
            //低级文件字节输出流
            InputStream is = new FileInputStream("static/text/t3.txt");
            //装载到数据输出流中
            DataInputStream dis = new DataInputStream(is);
    ){
        System.out.println(dis.readByte()); //字节
        System.out.println(dis.readInt());  //整型
        System.out.println(dis.readBoolean()); //布尔型
        System.out.println(dis.readDouble()); //双精度浮点型
        System.out.println(dis.readUTF()); //字符串
    }catch (Exception e){
        e.printStackTrace();
    }
}

 

输出如下:

 

posted @ 2024-04-23 22:30  回忆也交给时间  阅读(5)  评论(0编辑  收藏  举报