欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

第18章 Stream(流)类

程序员文章站 2023-12-21 13:15:52
...

1. 流Stream的概念和分类

一个流(Stream)可以理解为一个数据的序列,是从某种介质流向其他介质的数据序列,比如从程序(内存)保存数据至硬盘,从硬盘读取数据至程序(内存)。从程序向网络发送消息,从网络接收消息至程序等。

  • 按照流向分为输入流和输出流
    (1)输入流:从其他介质进入程序(内存)
    (2)输出流:从程序(内存)离开进入其他介质中
  • 按照处理单位分为字节流和字符流
    (1)字节流:处理单位为字节(byte),主要用于处理二进制数据。比如,计算机上的所有文件中的内容基本都可以看做是二进制数据。
    (2)字符流:处理单位为字符(char),主要用于处理文本数据。比如计算机上的txt文档中的内容可以看做是文本数据。
  • 按照功能分为原始流和处理流
    (1)原始流:提供基础功能的流
    (2)处理流:提供对基础功能进行功能加强的流

按照流向和单位,流的父类如下

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

这些流的子类以上述的名字作为类名后缀。
比如:

  • FileInputStream: 关于文件操作的字节输入流
  • FileInputOutputStream: 关于文件操作的字节输出流
  • FileReader: 关于文件操作的字符输入流
  • FileWriter: 关于文件操作的字符输出流

原始流在使用后要进行关闭

2. 字节流(文件流为例)

以下是代码的功能是复制c:/1.jpg至e:/1.jpg。复制的过程仅仅操作内容复制,无需更改内容,直接使用字节流完成。
可以理解为:
(1)将c:/1.jpg的数据输入至内存
(2)将输入至内存的数据输出至e:/1.jpg

public class Test1 {
    public static void main(String[] args) {
        
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("c:/1.jpg");// 创建输入流读取源文件内容
            fos = new FileOutputStream("e://1.jpg");// 创建输出流写出文件内容
            byte[] b = new byte[2048]; //2KB区域,一次读取源文件中的2KB数据
            // 从源文件中读取2KB数据至b数组中
            int len = fis.read(b);   
            /*  len表示的是在源文件中读取字节长度 
            len是-1表示读取结束。最后一次读取不一定读了2KB数据,
            需要用len确定读了多少数据,写出时以免写出冗余
            */
            while(len != -1) {      
                //将读取到b数组中的数据写出至目标文件
                //第二个参数表示偏移量,从数组的下标几开始输出
                //第三个参数表示输出字节个数
                fos.write(b, 0, len);
                fos.flush(); //将数据真正刷出至目标文件中
                //再次读入下一组数据至b中
                len = fis.read(b);
            }   
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭流
            try {
                if(fis != null) {
                    fis.close();
                }
                if(fos != null) {
                    fos.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

3. 字符流(文件流为例)

读取c:/1.txt文档中的内容,将其中所有的字符'哈'替换为'*',并输出至e:/1.txt中。其原理与文件复制一致,不过操作涉及对内容的修改,需要在内存中完成对字符的替换,所以使用字符流进行操作

public class Test2 {

    public static void main(String[] args) {
        FileReader fr = null;
        FileWriter fw = null;
        
        try {
            fr = new FileReader("D://1.txt");
            fw = new FileWriter("E://1.txt");
            char[] c = new char[1024]; //一次读取1024个字符,字符流一次处理一个char,所以用char数组不使用byte数组
            int len = fr.read(c);  //将文本文件中的文字读入的c数组中
            while(len != -1) {
                String temp = new String(c); //利用字符数组c构造成一个字符串
                temp = temp.replaceAll("哈", "*"); //将字符串中的“哈”替换为“*”
                c = temp.toCharArray();//将替换后的字符串变回字符数组
                fw.write(c, 0, len);//将替换后的字符数组写出至目标位置
                fw.flush();
                len = fr.read(c); //继续读取下一组字符
            }   
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fr != null) {
                    fr.close();
                }
                if(fw != null) {
                    fw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

4. 处理流(缓冲流为例)

处理流本质上不提供任何功能,它提供了对原始流功能的加强。
以字符缓冲输入流为例,原始的字符输入流只能以字符数组 (char[])读取数据,如果现在提出要读取txt文件中整行数据,原始的字符输入流没有相应的方法完成这个功能,但利用缓冲流提供的方法,可以容易的达到目的。
示例:以行(line)为单位,操作txt文档的复制

public class Test1 {

    public static void main(String[] args) {
        FileReader fr = null; //原始的字符输入流
        BufferedReader br = null; //缓冲字符输入流
        
        FileWriter fw = null; //原始的字符输出流
        BufferedWriter bw = null; //缓冲字符输出流
        
        try {
            fr = new FileReader("d://1.txt");
            br = new BufferedReader(fr);    
            
            fw = new FileWriter("d://2.txt");
            bw = new BufferedWriter(fw);
                
            String line = br.readLine(); //读取源文件中的一行文本
            while(line != null) { //如果不为null则认为读取到了文本
                System.out.println(line); //在控制台上显示文本
                bw.write(line); //在目标文件中写该行文本
                bw.newLine(); //目标文件新起一行
                bw.flush(); //刷入目标文件
                line = br.readLine(); //读取源文件的下一行文本
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fr != null) {
                    fr.close();
                }
                if(fw != null) {
                    fw.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

5. 序列化

序列化是将对象以二进制数据的形式转储。比如将系统中的二进制数据暂时保存。

5.1 可被序列化的类

一个类如果可以被序列化,它需要实现java.io.Serializable接口,并生成一个常量值的序列码,这个序列码可以理解为是某个类的身份证号。

public class Student implements Serializable{   
    private static final long serialVersionUID = 4188262972381648549L;
    
    private int sno;
    private String sname;
    private Date birthday;
    
    public Student() {}
    
    public Student(int sno, String sname, Date birthday) {
        this.sno = sno;
        this.sname = sname;
        this.birthday = birthday;
    }
    public int getSno() {
        return sno;
    }
    public void setSno(int sno) {
        this.sno = sno;
    }
    public String getSname() {
        return sname;
    }
    public void setSname(String sname) {
        this.sname = sname;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
}

如果类中的某个属性需要是其他类的对象并且也需要序列化,那么这个所谓的“其他类”也需要实现java.io.Serializable接口。比如上面这个类中的Date birthday属性,java.util.Date类也实现了java.io.Serializable接口,系统中大部分的常用类都实现了java.io.Serializable接口,比如包装类,集合类等。

5.2 序列化

将对象从内存序列化至其他介质,本章示例将其序列化至c:/1.abc文件中

public class Test1 {

    /**
     * 序列化操作   将对象从内存转入其他介质中
     * @param args
     */
    public static void main(String[] args) {
        Student s1 = new Student(101, "赵四", new Date());
        Student s2 = new Student(102, "哈哈", new Date());
        
        //序列化操作:   将s1对象保存在c:/1.abc文件中
        FileOutputStream fos = null;
        ObjectOutputStream oos = null;  
        try {
            fos = new FileOutputStream("c:/1.abc");
            oos = new ObjectOutputStream(fos);//典型的处理流    操作oos相当于操作了fos
            
            oos.writeObject(s1); //将s1对象写入到fos对应的文件中
            oos.writeObject(s2); //将s2对象写入到fos对应的文件中
            oos.flush();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            try {
                if(fos != null) {
                    fos.close();//关闭原始流即可
                }   
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}
5.3 反序列化

将对象从文件中读取回内存

public class Test2 {

    /**
     * 反序列化:将对象从其他介质转入到内存中
     * @param args
     */
    public static void main(String[] args) {
        FileInputStream fis = null;
        ObjectInputStream ois = null;
        
        try {
            fis = new FileInputStream("c:/1.abc");
            ois = new ObjectInputStream(fis);
            
            Student s1 = (Student)ois.readObject();
            Student s2 = (Student)ois.readObject();
            System.out.println(s1.getSno()+"\t"+s1.getSname()+"\t"+s1.getBirthday());
            System.out.println(s2.getSno()+"\t"+s2.getSname()+"\t"+s2.getBirthday());
            
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }  finally {
            try {
                if(fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
}
5.4 transient关键字

该关键字用于修饰属性,被这个关键字修饰的属性不参与序列化和反序列化。比如
将Student类的birthday属性加上transient关键字修饰

public class Student implements Serializable{   
    private static final long serialVersionUID = 4188262972381648549L;
    
    private int sno;
    private String sname;
    private transient Date birthday;
    
    public Student() {}
    
    public Student(int sno, String sname, Date birthday) {
        this.sno = sno;
        this.sname = sname;
        this.birthday = birthday;
    }
    public int getSno() {
        return sno;
    }
    public void setSno(int sno) {
        this.sno = sno;
    }
    public String getSname() {
        return sname;
    }
    public void setSname(String sname) {
        this.sname = sname;
    }
    public Date getBirthday() {
        return birthday;
    }
    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }
}

再次进行测验会发现序列化后,反序列化回来的数据中birthday都是null。相当于birthday没有参与序列化过程。

上一篇:

下一篇: