Java中流

120 阅读12分钟

流的分类

按照流的方向分类

  • 输入流:数据流向是输入源到程序(InputStream,Reader结尾的流)
  • 输出流:数据流向是程序到目的地(OutputStream,Writer结尾的流)

按处理的数据单元分类

  • 字节流:以字节为单位获取数据,命名一般以Stream为结尾
  • 字符流:以字符为单位获取数据,命名一般是Reader/Writer为结尾

按处理的对象不同分类

  • 节点流:可以直接从目的地或者数据源中读取数据(FileInputStream,FileReader,DataInputStream)
  • 处理流:不直接连接到数据源或者目的地,是处理流的流,通过对其他流的处理来提高程序性能,如BufferedInputStream,BufferedReader等,处理流也叫包装流。

JavaIO四大基石

  • 字节流接口InputStream,OutputStream
  • 字符流:Reader,Writer

通过字节缓冲区进行文件的复制

package com.itbaizhan;

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

public class TestFileByteBuffer {

    public static void main(String[] args) {
        final long l = System.currentTimeMillis();
        copyFile("d:/1.png","d:/2.png");
        final long l1 = System.currentTimeMillis();
        System.out.println(l1-l);

    }

    /**
     *
     * @param source 源文件的路径
     * @param destination 目标文件的路径
     */
    public static void copyFile(String source,String destination){
        //后开先关,try-with-source按照IO流被创建的顺序逆序关闭
        try(FileInputStream fis = new FileInputStream(source);
            FileOutputStream fos = new FileOutputStream(destination)) {
            int temp;
            //单个读取比较慢,添加一个缓冲区,通过缓冲区,很快,使用前5968ms,使用后0ms
            byte[] buffer = new byte[1024];
            while((temp = fis.read(buffer)) != -1){
                //temp此时获得是数组的长度
                fos.write(buffer,0,temp);
            }
            fos.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}

文件字节流

  • FileInputStream通过字节的方式读取文件,适合读取所有类型的文件(图像、视频、文本、文件等)
try(FileInputStream fileInputStream = new FileInputStream("d:/a.txt")) {
    StringBuilder stringBuilder = new StringBuilder();
    int temp = 0;
    while((temp = fileInputStream.read()) != -1){
        char temp1 = (char) temp;
        stringBuilder.append(temp1);
    }
    System.out.println(stringBuilder);
} catch (IOException e) {
    throw new RuntimeException(e);
}
  • FileOutputStream通过字节的方式写数据到文件中,适合所有类型的文件(图像、视频、文本文件等)
//构造函数有两个参数,第二boolean类型的参数:true代表在文件末尾添加内容,false表示覆盖文件中的内容,默认为false
try(FileOutputStream fileOutputStream = new FileOutputStream("d:/a.txt",true)) {
    String str = "LiXinYan";
    fileOutputStream.write(str.getBytes());
    //刷新,将内存中的值写入文件
    fileOutputStream.flush();

} catch (IOException e) {
    throw new RuntimeException(e);
}

通过字节缓冲区提高效率

通过创建一个指定长度的数组作为缓冲区,以此来提高IO流的读写效率。该方式适用于读取较大文件时的缓冲区定义。注意:缓冲区的长度一定是2的整数幂。一般情况下1024长度较为合适。

package com.itbaizhan;

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

public class TestFileByteBuffer {

    public static void main(String[] args) {
        final long l = System.currentTimeMillis();
        copyFile("d:/1.png","d:/2.png");
        final long l1 = System.currentTimeMillis();
        System.out.println(l1-l);

    }

    /**
     *
     * @param source 源文件的路径
     * @param destination 目标文件的路径
     */
    public static void copyFile(String source,String destination){
        //后开先关,try-with-source按照IO流被创建的顺序逆序关闭
        try(FileInputStream fis = new FileInputStream(source);
            FileOutputStream fos = new FileOutputStream(destination)) {
            int temp;
            //单个读取比较慢,添加一个缓冲区,通过缓冲区,很快,使用前5968ms,使用后0ms
            byte[] buffer = new byte[1024];
            while((temp = fis.read(buffer)) != -1){
                //temp此时获得是数组的长度
                fos.write(buffer,0,temp);
            }
            fos.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}

缓冲字节流

  • BufferedInputStream
  • BufferedOutputStream

Java缓冲流本身不具有IO流的读取和写入功能,只是在别的流(节点流和其他处理流)上加上缓冲功能提高效率,就像是把别的的流包装起来一样,因此缓冲流是一种处理流(包装流)

BufferedInputStream和BufferedOutputStream这两个流是缓冲字节流,通过内部缓存数组来提高操作流的效率。

使用缓冲流来实现文件的高效率复制

package com.itbaizhan;

import java.io.*;

public class TestFileBufferStream {

    public static void main(String[] args) {

        final long l = System.currentTimeMillis();
        copyFile("d:/2.png","d:/3.png");
        final long l1 = System.currentTimeMillis();
        System.out.println(l1-l);

    }
    public static void copyFile(String source,String destination){
        try(FileInputStream inputStream = new FileInputStream(source);
            final FileOutputStream outputStream = new FileOutputStream(destination);
            //通过BufferStream来进行读取,缓存区的大小默认是8192字节
            final BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
            final BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
            int temp = 0;
            while((temp = bufferedInputStream.read()) != -1){
                bufferedOutputStream.write(temp);
            }
            bufferedOutputStream.flush();

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}

文件字符流

前面介绍的文件字节流可以处理所有的文件,如果我们处理的是文本文件,也可以使用文件字符流,它以字符为单位进行操作

  • FileReader
public static void main(String[] args) {
    try(FileReader fileReader = new FileReader("d:/a.txt")) {
        StringBuilder stringBuilder = new StringBuilder();
        int temp;
        while((temp = fileReader.read()) != -1){
            stringBuilder.append((char)temp);
        }
        System.out.println(stringBuilder);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
  • FileWriter
package com.itbaizhan;

import java.io.FileWriter;
import java.io.IOException;

public class TestFileWriter {

    public static void main(String[] args) {
        //true:开启追加
        try(FileWriter fileWriter = new FileWriter("d:/aa.txt",true)){
            fileWriter.write("破绽,稍纵即逝\r\n");
            fileWriter.write("随蝴蝶一起消散吧,旧日的幻影\r\n");
            fileWriter.flush();
        }catch (IOException e){

        }
    }

}

缓冲字符流

BufferedReaderBufferedWriter增加了缓存机制,大大提高了读写文本文件的效率。

  • 字符输入缓冲流BufferedReader
package com.itbaizhan;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class TestBufferedReader {

    public static void main(String[] args) {
        try(final BufferedReader bufferedReader = new BufferedReader(new FileReader("d:/aa.txt"))){
            StringBuilder stringBuilder = new StringBuilder();
            String temp;
            while((temp = bufferedReader.readLine()) != null){
                stringBuilder.append(temp);
            }
            System.out.println(stringBuilder);
        }catch (IOException e){
            e.printStackTrace();
        }
    }

}
  • 字符输出缓冲流BufferedWriter
package com.itbaizhan;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class TestBufferedWriter {

    public static void main(String[] args) {
        try(final BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("d:/aaa.txt"))){
            bufferedWriter.write("今日听君歌一曲");
            bufferedWriter.newLine();
            bufferedWriter.write("暂凭杯酒长精神");
            bufferedWriter.newLine();
            bufferedWriter.write("感时花溅泪");
            bufferedWriter.newLine();
            bufferedWriter.write("恨别鸟惊心");
            bufferedWriter.flush();
            //执行close()方法会自动flush
        }catch (IOException e){
            e.printStackTrace();
        }
    }

}
  • 为文件中的内容添加行号
package com.itbaizhan;

import java.io.*;

public class TestLineNumber {

    public static void main(String[] args) {
        try(BufferedReader br = new BufferedReader(new FileReader("d:/aaa.txt"));
            BufferedWriter bw = new BufferedWriter(new FileWriter("d:/a2.txt"))){
            int i = 1;
            String temp;
            while((temp = br.readLine()) != null){
                bw.write(i+"."+temp);
                bw.newLine();
                i++;
            }
            bw.flush();
        }catch(IOException e){
            e.printStackTrace();
        }
    }

}

转换流

InputStreamReaderOutputStreamWriter用来实现将字节流转化成字符流

通过转换流解决乱码

ANSI不是一种编码,而是对于对应的文字采取相对应的编码,比如中文就是GBK

package com.itbaizhan;

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

/**
 * 通过转换流解决乱码
 */
public class TestInputStreamReader {

    public static void main(String[] args) {
        try(FileInputStream fileInputStream = new FileInputStream("d:/a2.txt");
            //字节流转换到字符流,如果文件设置为ANSI中文就是GBK编码格式,通过添加参数来改变编码格式,
            InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream,"GBK")){
            StringBuilder stringBuilder = new StringBuilder();
            int temp = 0;
            while((temp = inputStreamReader.read()) != -1){
                stringBuilder.append((char) temp);
            }
            System.out.println(stringBuilder);
        }catch (IOException e){
            e.printStackTrace();
        }
    }

}

通过字节流读取文本并添加行号

public static void main(String[] args) {
    //创建字符输入缓冲流、输入字节到字符转换流、文件字节输入流对象
    try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("d:/a2.txt"),"GBK"));
        //创建字符输出缓冲流、输出字符到字节转换流、文件字节输出对象
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("d:/a4.txt")))
    ){
        int i = 1;
        String temp;
        while ((temp = bufferedReader.readLine()) != null){
            bufferedWriter.write(i+" "+temp);
            bufferedWriter.newLine();
            i++;
        }
        bufferedWriter.flush();

    }catch (IOException e){
        e.printStackTrace();
    }

通过转换流来实现键盘输入屏幕输出

package com.itbaizhan;

import java.io.*;

/**
 * 接受键盘输出的流并输出
 */
public class TestKeyboard {

    public static void main(String[] args) {
        //System.in返回一个InputStream对象,创建键盘输入流对象
        try(BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
            //System.out返回一个OutputStream对象,创建屏幕输出的流对象
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out))
        ){
            while (true){
                bw.write("请输入:");
                bw.flush();
                String input = br.readLine();
                if(input.equals("exit") || input.equals("quit")){
                    bw.write("Bye Bye!");
                    bw.flush();
                    break;
                }
                bw.write(input);
                bw.newLine();
                bw.flush();
            }

        }catch (IOException e){

        }

    }

}

字符输出流PrintWriter

在Java的IO中专门提供了用于字符输出的对象PrintWriter。该对象具有自动刷新缓冲字符输出流,特点是可以按行写出字符串,并可以通过println();方法实现自动换行。

package com.itbaizhan;

import java.io.IOException;
import java.io.PrintWriter;

public class TestPrintWriter {

    public static void main(String[] args) {
        try(PrintWriter printWriter = new PrintWriter("d:/sxt.txt")){
            printWriter.print("abc");
            printWriter.print("def");
            //换行输出流
            printWriter.println("oldLU");
            printWriter.println("Linxinyan");
            printWriter.flush();

        }catch (IOException e){
            e.printStackTrace();
        }
    }

}

通过字符输出流添加行号

package com.itbaizhan;

import java.io.*;

public class TestLineNumberInPrintWriter {

    public static void main(String[] args) {
        try(
                BufferedReader bufferedReader = new BufferedReader(new FileReader("d:/aaa.txt"));
                PrintWriter pw = new PrintWriter("d:/linenumber.txt")){
            int i = 1;
            String temp;
            while((temp = bufferedReader.readLine()) != null){
                //输出内容并带有换行效果
                pw.println(i +" "+ temp);
                i++;
            }
            //刷新
            pw.flush();
        }catch (IOException e){
            e.printStackTrace();
        }
    }

}

数据流

  • DataInputStream
  • DataOutputStream

数据流将”基本数据类型与字符串类型“作为数据源,从而允许程序以机器无关的方式从底层输入输出流中操作Java基本数据类型与字符串。 DataInputStream和DataOutputStream提供了可以存取与机器无关的所有Java基础类型数据(如:int、double、String等)的方法。

package com.itbaizhan;

import java.io.*;

public class TestDataStream {

    public static void main(String[] args) {
        try(
                DataOutputStream dos = new DataOutputStream(new FileOutputStream("d:/data"));
                DataInputStream dis = new DataInputStream(new FileInputStream("d:/data"))
            ){
            //写入以下数据
            dos.writeChar('a');
            dos.writeInt(10);
            dos.writeDouble(Math.random());
            dos.writeBoolean(true);
            dos.writeUTF("北京尚学堂");
            dos.flush();
            //读取数据,顺序必须和写入一样,不然会出错
            System.out.println("Char:"+dis.readChar());
            System.out.println("Int:"+dis.readInt());
            System.out.println("Double:"+dis.readDouble());
            System.out.println("Boolean:"+dis.readBoolean());
            System.out.println("String:"+dis.readUTF());


        }catch (IOException e){
            e.printStackTrace();
        }
    }

}

对象流

  • ObjectInputStream
  • ObjectOutputStream

我们前边学的数据流只能实现对基本数据类型和字符串类型的读写,并不能读取对象(字符串除外),如果要对某个对象进行读写操作,我们需要学习一对新的处理流。

处理基本数据类型的数据

package com.itbaizhan;

import java.io.*;

public class TestObjectStream {

    public static void main(String[] args) {
        try(ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("d:/data2"));
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("d:/data2"))){

            outputStream.writeInt(10);
            outputStream.writeChar('a');
            outputStream.writeBoolean(true);
            outputStream.writeDouble(Math.random());
            outputStream.writeUTF("北京故宫博物院");
            outputStream.flush();

            System.out.println(inputStream.readInt());
            System.out.println(inputStream.readChar());
            System.out.println(inputStream.readBoolean());
            System.out.println(inputStream.readDouble());
            System.out.println(inputStream.readUTF());

        }catch (IOException e){
            e.printStackTrace();
        }
    }

}

Java对象的序列化和反序列化

对象----序列化---->二进制数据

二进制数据----反序列化---->对象

序列化和反序列化是什么

当两个进程远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。比如,我们可以通过http协议发送字符串的信息;我们也可以在网络上直接发送Java对象。发送方需要把这个Java对象转换为字节序列,才能在网络上传送,接收方则需要把字节序列恢复为Java对象才能正常读取。

把Java对象转为字节序列的过程称为对象的序列化。把字节序列恢复为Java对象的过程称为字节的反序列化。

序列化涉及的类和接口

ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中

ObjectInputStream代表对象输入流,他的readObject()方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回。

只有实现了Serializable接口的类的对象才能被序列化。Serializable接口是一个空接口,只有标记作用。

User代码:

import java.io.Serializable;

/**
 * Serializable是标识接口,其中没有任何的抽象方法
 */
public class User implements Serializable {

    private int id;
    private String username;
    private String userage;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + ''' +
                ", userage='" + userage + ''' +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getUserage() {
        return userage;
    }

    public void setUserage(String userage) {
        this.userage = userage;
    }
}

ObjectOutputStream,写入到文件中,序列化

public static void main(String[] args) {
    try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:/data3"))){
        User user = new User();
        user.setId(1);
        user.setUsername("lixinyan");
        user.setUserage("20");
        oos.writeObject(user);
        oos.flush();

    }catch (Exception e){
        e.printStackTrace();
    }
}

ObjectInputStream,读取文件中的对象,反序列化

public static void main(String[] args) {
    //创建对象字节输入流和文件字节输入流对象
    try(ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/data3"))){
        //反序列化处理
        User u = (User) ois.readObject();
        System.out.println(u.getId());
        System.out.println(u.getUserage());
        System.out.println(u.getUsername());
    }catch (Exception e){
        e.printStackTrace();
    }
}

File类在IO中的作用

当文件作为数据源或者目标时,除了可以使用字符串作为文件以及位置的指定以外,我们也可以使用File类指定。

package com.itbaizhan;

import java.io.*;

public class TestFile {

    public static void main(String[] args) {
        try(
                BufferedReader br = new BufferedReader(new FileReader(new File("d:/aaa.txt")));
                PrintWriter pr = new PrintWriter(new FileWriter(new File("d:/sxt.txt")))){

            int i = 1;
            String temp;
            while((temp = br.readLine()) != null){
                pr.println(i+","+temp);
                i++;
            }
            pr.flush();
        }catch (IOException e){
            e.printStackTrace();
        }
    }

}

装饰器模式构建IO流体系

装饰器模式简介

装饰器模式是GOF23种设计模式中较为常用的一种模式。它可以实现对原有类的包装和装饰,使新的类具有更强的功能。

普通类和装饰器类

package com.itbaizhan.decoration;

public class Phone {

    private String name;

    public Phone(String name) {
        this.name = name;
    }

    public Phone() {
    }

    public void show(){
        System.out.println("我是"+name+"可以在屏幕上显示");
    }

}
class Decoration{
    Phone phone = new Phone();

    public Decoration(Phone phone) {
        this.phone = phone;
    }

    public void show(){
        this.phone.show();
        System.out.println("添加投影功能");
    }
}

测试装饰器和普通类的方法

package com.itbaizhan.decoration;

public class TestDecoration {

    public static void main(String[] args) {
        Phone iPhone13 = new Phone("iPhone13");
        Decoration decoration = new Decoration(iPhone13);
        iPhone13.show();
        System.out.println("===================使用装饰器===================");
        decoration.show();
    }

}

IO流体系中的装饰器模式

IO流体系中大量使用了装饰器模式,让流具有更强的功能、更强的灵活性。

image.png

显然BufferedInputStream装饰了原有的FileInputStream,让普通的FileInputStream也具备了缓存功能,提高了效率。

ApachIO介绍

FileUtils类中常用方法介绍

方法名使用说明
cleanDirectory清空目录,但不删除内容
contentEquals比较两个文件的内容是否相同
copyDirectory将一个目录内容拷贝到另一个目录,可以通过FileFilter过滤需要拷贝的文件
copyFile将一个文件拷贝到一个新的地址
copyFileToDirectory将一个文件拷贝到某个目录下
copyInputStreamToFile将一个输入流中的内容拷贝到某个文件
deleteDirectory删除目录
deleteQuietly删除文件
listFiles列出指定目录下的所有文件
openInputStream打开指定文件的输入流
readFileToString将文件内容作为字符串返回
readLines将文件内容按行返回到一个字符串数组中
size返回文件或目录的大小
write将字符串内容直接写入文件中
writeByteArrayToFile将字节数组的内容写入到文件中
writeLines将容器中的元素的toString方法返回的内容依次写入文件中
writeStringToFile将字符串内容写到文件中

读取文件中内容,并输出到控制台上,只需一行代码

public static void main(String[] args) throws IOException {
    //通过工具类可以一下读出文件中的所有方法
    String s = FileUtils.readFileToString(new File("d:/aaa.txt"), StandardCharsets.UTF_8);
    System.out.println(s);

}

使用FileUtils工具类实现目录拷贝

我们可以使用FileUtils完成目录拷贝,在拷贝过程中可以通过文件过滤器FileFilter选择拷贝内容

public static void main(String[] args) throws IOException {
    FileUtils.copyDirectory(new File("d:/aaa"), new File("d:/bbb"), new FileFilter() {
        @Override
        public boolean accept(File pathname) {
            if(pathname.isDirectory() || pathname.getName().endsWith("html")){
                return true;
            }else {
                return false;
            }
        }
    });
}

IOUtils的妙用

方法名使用方法
buffer将传入的流进行包装,变成缓冲流。并可以通过参数指定缓冲大小
closeQueitly关闭流
contentEquals比较两个流中的内容是否一致
copy将输入流中的内容拷贝到输出流中,并可以指定字符编码
copyLarge将输入流中的内容拷贝到输出流中,适合大于2G内容的拷贝
lineIterator返回可以迭代每一行内容的迭代器
read将输入流中部分内容读入到字节数组中
readFully将输入流中的所有内容读入到字节数组中
readLine读入输入流内容中的一行
toBufferedInputStream,toBufferedReader将输入转为带缓存的输入流
toByteArray,toCharArray将输入流的内容转为字节数组、字符数组
toString将输入流或者数组中的内容转化为字符串
write向流里面写入内容
writeLine向流里面写入一行内容
String s = IOUtils.toString(new FileInputStream("d:/a2.txt"), "GBK");
System.out.println(s);