字节流 字符流 转换流 缓冲流

1 IO流概述

IO流用来处理设备间数据传输,按流向分为输入流和输出流,按操作类型分为字节流和字符流

输入:Input,读操作,硬盘数据读取到内存,从文件到程序
输出:Output,写操作,内存数据存储到硬盘,从程序到文件

字节流:可以处理任意文件,因为计算机数据都是以字节形式存储的。
字符流:专门处理文本文件,自动查询编码表。

转换流:
InputStreamReader 字节转字符
OutputStreamWriter 字符转字节
规律: 构造方法中接收的都是字节流,都是用字符流的方法操作字节流

打印流:

IO流继承关系

2 字节流和字符流

2.1 字节输入流FileInputStream

FileInputStream(File file):      传入文件的File对象,创建字节输入流
FileInputStream(String name):   传入文件路径的String对象,创建字节输入流

int read():             从输入流中读取数据的下一个字节,返回-1表示文件结束
int read(byte[] b):           从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。到达文件末尾则返回 -1。
int read(byte[] b, int off, int len):    将输入流中最多 len 个数据字节读入 byte 数组。参数:数组,读取位置,读取长度

2.2 字符输入流FileReader

FileReader(String fileName):     传文件路径的String对象,创建字符输入流
FileReader(File file):        传File对象,创建字符输入流
FileReader(FileDescriptor fd):    程序打开文件或者创建新文件时,内核向进程返回一个文件描述符,文件描述符概念往往只适用于UNIX、Linux这样的操作系统。

int read();                读取单个字符
int read(char[] c);            将字符读取到数组中,并返回读取的个数
int read(char[] c, int off, int len);    将字符读入数组的某一部分

注意:read()方法返回值问题!

参考:
https://blog.csdn.net/lamyourangle/article/details/51510741
http://blog.sina.com.cn/s/blog_9e351f9b01015kgp.html
https://blog.csdn.net/qijingwang/article/details/79742561

2.3 字节输出流FileOutputStream

FileOutputStream(File file):             传入文件的File对象,创建字节输出流
FileOutputStream(String name):          传入文件路径的String对象,创建字节输出流
FileOutputStream(File file, boolean append):     以续写的方式写入,而不是覆盖
FileOutputStream(String name, boolean append):  以续写的方式写入,而不是覆盖

void write(char c):             写入一个字符,如write(97)或write('a')
void write(byte[] b):             将 b.length 个字节从指定的 byte 数组写入此输出流
void write(byte[] b, int off, int len):      将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。

2.4 字符输出流FileWriter

FileWriter(File file):             传入文件的File对象,创建字符输出流
FileWriter(String name):             传入文件路径的String对象,创建字符输出流
FileWriter(File file, boolean append):      以续写的方式写入,而不是覆盖
FileWriter(String name, boolean append):   以续写的方式写入,而不是覆盖

void write(char c);               写入一个字符,如write(97)或write('a')
void write(char[] cbuf);             写入字符数组
void write(String str);             写入字符串
abstract void write(char[] cbuf, int off, int len);  写入字符数组的某一部分,开始索引,写入数量
void write(String str, int off, int len);        写入字符串的某一部分

字符输出流写数据时,必须要运行刷新功能flush()
flush方法和close方法区别
flush()方法:将流中的缓冲区数据刷新到目的地中,刷新后,流还可以继续使用,只有字符流才需要刷新
close()方法:关闭流,释放资源,关闭后不能再写出,如果是带缓冲区的流对象的close()方法,关闭前会将缓冲区中的数据先刷新到目的地

3 转换流

3.1 转换流概述

转换流的好处:
1 字节流能通过转换流使用字符流的一些方法简化代码,如readLine方法。
2 特定场景数据只以字节流方式提供,如System.in标准输入流,就是字节流,如果想得到用户在键盘上的输入,只能以转换流将它转换为Reader以方便自己的程序读取输入。再比如Socket里的getInputStream()很明显只提供字节流,想直接用,就需要套个InputStreamReader()来读取网络传输的字符。
总结:转换流其实是弥补在某些时候字节流不能直接处理字符数据的缺陷。

3.2 InputStreamReader

使用指定字符编码表读取字节,并解码为字符。
使用的字符集可以用名称指定或显式给定,或者接受平台默认字符集。在读取指定的编码的文件时,一定要指定编码格式,否则会使用本地默认码表读取,可能发生乱码现象。

InputStreamReader(InputStream in) 接收所有的字节输入流
InputStreamReader(InputStream in,String charsetName) 传递编码表的名字

int read(char[] c);            
int read(char[] c, int off, int len);    

3.3 OutputStreamWriter

使用指定字符编码表将字符串按转成字节,再使用字节流写出去
当调用OutputStreamWriter对象的write方法时,会拿着字符到指定的码表中查询对应编码值,然后转成字节数存放到OutputStreamWriter缓冲区。最后调用刷新功能,或关闭流,或缓冲区存满后,会把缓冲区字节数据使用字节流写入文件。

OutputStreamWriter(OuputStream out)接收所有的字节输出流 
OutputStreamWriter(OutputStream out, String charsetName); charsetName 传递编码表名字

write(int c)
write(char cbuf[], int off, int len)
write(String str, int off, int len)

3.4 转换流和字符流的区别

InputStreamReader和OutputStreamWriter是字符和字节的桥梁,也可以称之为字符转换流。字符转换流原理:字节流+编码表。
FileWriter和FileReader:作为子类,仅作为操作字符文件的便捷类存在。当使用默认编码表操作字符文件时,可以直接用子类完成操作,简化代码。
以下三句话功能相同
  InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"));//默认字符集。
  InputStreamReader isr = new InputStreamReader(new FileInputStream("a.txt"),"GBK");//指定GBK字符集。
  FileReader fr = new FileReader("a.txt");//默认字符集。特定场景简便用法

4 缓冲流

包括:
字节输入/输出缓冲流BufferedInputStream和BufferedOutputStream
字符输入/输出缓冲流BufferedReader和BufferedWriter

void newLine() 文本中换行,\r\n也是文本换行,平台无关,Windows下\r\n,Linux下\n

BufferedReader特有方法:String readLine() 读取一行,不包含任何行终止符,如果已到达流末尾,则返回 null

注意:

1 输入缓冲流缓冲区数组必须≥文本字节数,否则读取到残缺数据,显示乱码,见示例5.5

2 输出缓冲流写入完成后,必须close才会将缓冲区数据写入到文件中!!!

readLine()注意事项:

readLine()只有遇到回车(\r)或换行符(\n)才会返回读取结果,这就是“读取一行”的意思,且返回的读取内容中并不包含换行符或者回车符;
当realLine()读取到的内容为空时,并不会返回 null,而是会一直阻塞,只有当读取的输入流发生错误或者被关闭时,readLine()方法才会返回null。

即如果字符缓冲流的数据源是动态的,例如使用socket之类的数据流时,需要程序在输入的内容后添加换行符,以免等待阻塞

参考:

https://blog.csdn.net/neon_z/article/details/53707170

https://blog.csdn.net/swingline/article/details/5357581

5 IO流应用示例及细节问题

5.1 字节流读字节/字节数组、字符流读字节/字节数组

public class Demo {
    public static void main(String[] args) throws IOException{
        //文本内容123abc你好
        int len = 0 ;
        System.out.println("本机默认编码:" + System.getProperty("file.encoding") + "\r-----");//查询本机默认编码,本地是UTF-8
        
        System.out.println("字符输入流的read()方法:");
        FileReader fileReader = new FileReader(Properities.TXT_URL);
        while((len = fileReader.read())!=-1){
            System.out.print(len + "=");//输出字符对应的编码值,“你”的Unicode编码10进制对应的就是20320
            System.out.print((char)len + ",");
        }
        System.out.println("\r-----");
        
        System.out.println("字符输入流的read(char[] c)方法:");
        FileReader fileReader2 = new FileReader(Properities.TXT_URL);//不新建一个FileReader对象,调用read方法会返回-1导致读取不到内容
        char[] ch = new char[2];
        while((len = fileReader2.read(ch))!=-1){
            System.out.print(len + "=");//len等于数组长度,fileReader2每次读取该长度内容存入数组,然后打印该内容
            System.out.print(new String(ch,0,len) + ",");//参数:数组,起始位,长度。即向ch数组中,从0位置开始,存放len长度个字符
        }
        fileReader.close();
        fileReader2.close();
        System.out.println("\r-----");
        
        System.out.println("字节输入流的read()方法:");
        FileInputStream fis = new FileInputStream(Properities.TXT_URL);        
        while((len = fis.read()) != -1){
            System.out.print(len + "=");
            System.out.print((char)len+",");//读取中文时,读取2字节汉字中的一个字节,得到的是残缺信息,可能超过超过127,强转为char类型变成扩展ASCII中的字符
        }
        System.out.println("\r-----");
        
        System.out.println("字节输入流的read(char[] c)方法:");
        //fis.getChannel().size()假如文件大小有5M,你读取了3M,那么调用available()的返回值就是2M。而getChannel().size()始终是5M
        //fis.available()返回下一次对此输入流调用的方法可以不受阻塞地从此输入流读取(或跳过)的估计剩余字节数。即输入流中当前的字节数
        //注意:如果要从网络中下载文件,网络是不稳定的,也就是说网络下载时,read()方法是阻塞的,这时用inputStream.available()获取不到文件的总大小。
        //此时就需要通过HttpURLConnection httpconn = (HttpURLConnection)url.openConnection();   
        //httpconn.getContentLength();来获取文件的大小。
        //fis.skip(2);//从输入流中跳过并丢弃n个字节的数据,有时可能和预想不一致,参考https://www.ablanxue.com/prone_7005_1.html
        FileInputStream fis2 = new FileInputStream(Properities.TXT_URL);
        byte[] b = new byte[3];//读取中文时,数组大小为什么会影响结果正确性???UTF-8中文3或4字节,此处设置为2将无法读取中文
        System.out.println(fis2.getChannel().size());
        while((len = fis2.read(b)) !=-1){
            System.out.print(len + "=");
            System.out.print(new String(b,0,len) + ",剩余字节"+fis2.available()+ "; ");
        }
        fis.close();
        fis2.close();
        
    }
}

>>>

本机默认编码:UTF-8
-----
字符输入流的read()方法:
49=1,50=2,51=3,97=a,98=b,99=c,20320=你,22909=好,
-----
字符输入流的read(char[] c)方法:
2=12,2=3a,2=bc,2=你好,
-----
字节输入流的read()方法:
49=1,50=2,51=3,97=a,98=b,99=c,228=ä,189=½,160= ,229=å,165=¥,189=½,
-----
字节输入流的read(char[] c)方法:
12
3=123,剩余字节9; 3=abc,剩余字节6; 3=你,剩余字节3; 3=好,剩余字节0;

5.2 字节流写字节/字节数组、字符流写字节/字节数组

public class Demo2 {
    public static void main(String[] args) throws IOException {
        //1 字节输出流FileOutputStream写字节
        FileOutputStream fos = new FileOutputStream(Properities.TXT_URL);
        fos.write(97);//写入小写字母a
        byte[] data = "bcd".getBytes();
        fos.write(data);
        
        //2 字节输出流FileOutputStream写字节数组
        byte[] bytes = {65,66,67,68};//大写字母ABCD
        fos.write(bytes);
        //数组内容部分写入,参数:数组,开始索引,写入数量
        fos.write(bytes, 1, 2);
        //写入字节数组的简便方式:写字符串
        fos.write("你好!".getBytes());
        fos.write("end...\r".getBytes());
        
        //3 字符输出流写文本FileWriter类
        FileWriter fw = new FileWriter(Properities.TXT_URL,true);
        fw.write(97);//写1个字符a
        fw.flush();
        char[] c = {'A','B','C','D'};//写1个字符数组
        fw.write(c);
        fw.flush();
        fw.write(c, 2, 2);//写字符数组一部分
        fw.flush();
        fw.write("你好!");//写入字符串
        fw.write("end...");//写入字符串
        fw.flush();
        
        fw.close();
        System.out.println("写入完成!!!");    
    
    }
}

写入结果:
abcdABCDBC你好!end...
aABCDCD你好!end...

5.3 字节流和字符流复制文本

aABCDCD你好!end...aABCDBC你好!end...复制5000行左右
字节流读取单个字节方式耗时大概9s,字节流读取字节数组方式耗时大概0.003s,字符流读取数组方式耗时大概0.04s

public class Demo3 {
    public static void main(String[] args) {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        long s = System.currentTimeMillis();
        long e1 = 0;
        long e2 = 0;
        long e3 = 0;
        
        try{
            fis = new FileInputStream(Properities.TXT_URL);
            fos = new FileOutputStream(Properities.TXT_URL2);
            
            //1 字节流复制文件:读取单个字节
            int len = 0 ;
            while((len = fis.read())!=-1){
                fos.write(len);
            }
            e1 = System.currentTimeMillis();
            System.out.println(e1-s);

            //2 字节流复制文件:读取字节数组
            fis = new FileInputStream(Properities.TXT_URL);//重新new对象,不然原对象.read()方法已经读取到末位,此时已经读不了原内容了
            fos = new FileOutputStream(Properities.TXT_URL2,true);//本次写入采用续写模式,不然上面单字节写入方式写入的内容被覆盖了
            fos.write("\r-----\r".getBytes());
            byte[] bytes = new byte[1024];
            while((len = fis.read(bytes))!=-1){
                fos.write(bytes, 0, len);
            }
            e2 = System.currentTimeMillis();
            System.out.println(e2-e1);
            
        }catch(IOException ex){
            System.out.println(ex);
            throw new RuntimeException("文件复制失败");
        }finally{
            try{
                if(fos!=null)
                    fos.close();
            }catch(IOException ex){
                throw new RuntimeException("释放资源失败");
            }finally{
                try{
                    if(fis!=null)
                        fis.close();
                }catch(IOException ex){
                    throw new RuntimeException("释放资源失败");
                }
            }
        }
        
        //3 字符流复制文本文件
        FileReader fr = null;
        FileWriter fw = null;
        try{
            fr = new FileReader(Properities.TXT_URL);
            fw = new FileWriter(Properities.TXT_URL2);//此处不能加true续写,因为上一个流已经关闭,java.io.IOException: Stream Closed
            char[] cbuf = new char[1024];
            int len = 0 ;
            while(( len = fr.read(cbuf))!=-1){
                fw.write(cbuf, 0, len);
                fw.flush();
            }
            e3 = System.currentTimeMillis();
            System.out.println(e3-e2);
        }catch(IOException ex){
            System.out.println(ex);
            throw new RuntimeException("复制失败");
        }finally{
            try{
                if(fw!=null)
                    fw.close();
            }catch(IOException ex){
                throw new RuntimeException("释放资源失败");
            }finally{
                try{
                    if(fr!=null)
                        fr.close();
                }catch(IOException ex){
                    throw new RuntimeException("释放资源失败");
                }
            }
        }
    }
}

5.4 转换流读数据和写数据

public class Demo4 {
    public static void main(String[] args) throws Exception {
        readCN();
        readUTF();
        writeCN();
    }
    //1 转换流InputSteamReader读字节
    public static void readCN() throws IOException{
        InputStream in = new FileInputStream(Properities.TXT_URL);
        InputStreamReader isr = new InputStreamReader(in,"UTF-8");//不加参数UTF-8,则采用系统默认编码表
        int ch = 0;
        System.out.println("读取1");
        while((ch = isr.read())!=-1){
            System.out.print((char)ch);
        }
        isr.close();
    }
    //2 转换流InputSteamReader读字节数组
    public static void readUTF()throws IOException{
        FileInputStream fis = new FileInputStream(Properities.TXT_URL);
        InputStreamReader isr = new InputStreamReader(fis,"UTF-8");
        char[] ch = new char[1024];
        int len = isr.read(ch);
        System.out.println("\r读取2");
        System.out.print(new String(ch,0,len));
        isr.close();
    }
    
    //字符转成字节的转换流
    public static void writeCN() throws Exception {
        FileOutputStream fos = new FileOutputStream(Properities.TXT_URL);
        OutputStreamWriter osw = new OutputStreamWriter(fos,"utf-8");
        //调用转换流,把文字写出去,其实是写到转换流的缓冲区中
        osw.write("aABCDCD你好!end...");//写入缓冲区。
        osw.close();
    }
    
}

5.5 缓冲流读、写、复制

public class Demo5 {
    public static void main(String[] args)throws IOException {
        //文本内容123abc你好
        
        //1 字节输入流缓冲流BufferedInputStream
        FileInputStream fs = new FileInputStream(Properities.TXT_URL);
        //使用高效的流,把基本的流进行封装,实现速度的提升
        BufferedInputStream bis = new BufferedInputStream(fs);
        byte[] bytes = new byte[12];//123abc你好共12字节,缓冲区数组必须≥12,否则读取到残缺数据,显示乱码
        int len = 0 ;
        while((len = bis.read(bytes))!=-1){
            System.out.print(new String(bytes,0,len));
        }
        System.out.println("\r-----");
        
        //2 字节输出流缓冲流BufferedOutputStream
        FileOutputStream fos = new FileOutputStream(Properities.TXT_URL);
        BufferedOutputStream bos = new BufferedOutputStream(fos);
        bos.write(55);
        bytes = "aABCDCD你好!end...".getBytes();        
        bos.write(bytes);
        bos.write("\r".getBytes());
        bos.write(bytes, 3, 2);
        bos.close();//注意:输出缓冲流写入完成后,必须close才会将缓冲区数据写入到文件中!!!
        
        //3 字符输入流缓冲流BufferedReader
        int lineNumber = 0;
        BufferedReader bfr = new BufferedReader(new FileReader(Properities.TXT_URL));
        String line = null;
        while((line = bfr.readLine())!=null){
            lineNumber++;
            System.out.println("第" + lineNumber+"行  "+line);
        }
        bfr.close();
        
        //4 字符输出流缓冲流BufferedWriter
        FileWriter fw = new FileWriter(Properities.TXT_URL);
        BufferedWriter bfw = new BufferedWriter(fw);
        bfw.write(97);
        bfw.flush();
        bfw.write("bc123");
        bfw.flush();
        bfw.write("你好".toCharArray());//toCharArray()返回一个字符数组,该字符数组中存放了当前字符串中的所有字符
        bfw.flush();
        bfw.close();
        
        //5 字符缓冲流复制文本文件
        BufferedReader bfr2 = new BufferedReader(new FileReader(Properities.TXT_URL));    
        BufferedWriter bfw2 = new BufferedWriter(new FileWriter(Properities.TXT_URL2));
        line = null;
        while((line = bfr2.readLine())!=null){
            bfw2.write(line);//写入一行数据
            bfw2.newLine();//写入换行符
            bfw2.flush();
        }
        bfw2.close();
        bfr2.close();
    }
}

5.6 字节流和字节缓冲流复制效率对比

public class Demo6 {
    public static void main(String[] args)throws IOException {
        File src = new File(Properities.TXT_URL);
        File desc = new File(Properities.TXT_URL2);
        long s = System.currentTimeMillis();
        copy_1(src, desc);
        long e1 = System.currentTimeMillis();
        System.out.println("字节流写字节:" + (e1-s));
        copy_2(src, desc);
        long e2 = System.currentTimeMillis();
        System.out.println("字节流写字节数组:" + (e2-e1));
        copy_3(src, desc);
        long e3 = System.currentTimeMillis();
        System.out.println("字节缓冲流写字节:" + (e3-e2));
        copy_4(src, desc);
        long e4 = System.currentTimeMillis();
        System.out.println("字节缓冲流写字节数组:" + (e4-e3));
    }
    
    //4. 字节流缓冲流读写字节数组
    public static void copy_4(File src,File desc)throws IOException{
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(desc));
        int len = 0 ;
        byte[] bytes = new byte[1024];
        while((len = bis.read(bytes))!=-1){
            bos.write(bytes,0,len);
        }
        bos.close();
        bis.close();
    }
    //3. 字节流缓冲流读写单个字节
    public static void copy_3(File src,File desc)throws IOException{
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(src));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(desc));
        int len = 0 ;
        while((len = bis.read())!=-1){
            bos.write(len);
        }
        bos.close();
        bis.close();
    }
    
    //2. 字节流读写字节数组
    public static void copy_2(File src,File desc)throws IOException{
        FileInputStream fis = new FileInputStream(src);
        FileOutputStream fos = new FileOutputStream(desc);
        int len = 0 ;
        byte[] bytes = new byte[1024];
        while((len = fis.read(bytes))!=-1){
            fos.write(bytes,0,len);
        }
        fos.close();
        fis.close();
    }
    
    //1. 字节流读写单个字节
    public static void copy_1(File src,File desc)throws IOException{
        FileInputStream fis = new FileInputStream(src);
        FileOutputStream fos = new FileOutputStream(desc);
        int len = 0 ;
        while((len = fis.read())!=-1){
            fos.write(len);
        }
        fos.close();
        fis.close();
    }
}

>>>

文本内容为aaABCDCD你好!end...aabcdABCDBC你好!end...55000行的测试结果:

字节流写字节:72995
字节流写字节数组:105
字节缓冲流写字节:70
字节缓冲流写字节数组:33

6 IO流选择及异常处理

6.1 IO流选择

源设备:
  硬盘:文件 File开头。
  内存:数组,字符串。
  键盘:System.in;
  网络:Socket
目的设备:
  硬盘:文件 File开头。
  内存:数组,字符串。
  屏幕:System.out
  网络:Socket
额外功能:
  需要转换吗?转换流:InputStreamReader OutputStreamWriter
  是否高效?缓冲区对象:BufferedXXX

6.2 IO流异常处理

1. 保证流对象变量作用域足够
2. catch里面,怎么处理异常?
  输出异常的信息,看哪里出现问题
  停下程序,重新尝试
3. 如果流对象建立失败了,需要关闭资源吗?
  new 对象的时候失败了,没有占用系统资源
  释放资源的时候,对流对象判断null,如果变量不是null,即对象建立成功,需要关闭资源

public class FileOutputStreamDemo {
    public static void main(String[] args) {
        //try 外面声明变量,try 里面建立对象
        FileOutputStream fos = null;
        try{
            fos = new FileOutputStream("s:\\a.txt");
            fos.write(100);
        }catch(IOException ex){
            System.out.println(ex);
            throw new RuntimeException("文件写入失败,重试");
        }finally{
            try{
                if(fos!=null)
                  fos.close();
            }catch(IOException ex){
                throw new RuntimeException("关闭资源失败");
            }
        }
    }
}

7 打印流

打印流
不抛IO异常,只操作数据目的,不操作数据源
应用:在点对点通信,可以使用打印流将数据打印到另一台主机;在互联网的应用中,可以使用打印流将数据打印到客户端浏览器

PrintWriter:接收字节输出流或字符输出流,接收File对象或String文件名路径

猜你喜欢

转载自www.cnblogs.com/createtable/p/10657588.html