第18章 Stream(流)类

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没有参与序列化过程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容