美文网首页Java 杂谈
字节流、字符流及其转换、比较与使用场景

字节流、字符流及其转换、比较与使用场景

作者: AmorFatiYJ | 来源:发表于2017-12-22 10:39 被阅读637次

    本文包含以下知识点:

    • [x] 字节流OutputStream和InputStream类及其子类
    • [x] 字符流Writer和Reader类及其子类
    • [x] 转换流OutputStreamWriter和InputStreamReader
    • [x] 代码示例
    • [x] 字节流、字符流的比较
    • [x] 各类的使用场景

    在程序中所有的数据都是以流的方式进行传输和保存的。程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来的时候,就要使用输出流完成

    在java.io包中操作文件内容的主要有两大类:字节流、字符流。两类都分为输入和输出操作。输入流基本方法是读,输出流基本方法是写。

    大致操作流程:(以File为例)

    1. 使用File类打开一个文件;
    2. 通过字节流或字符流的子类,指定输出的位置;
    3. 进行读/写操作;
    4. 关闭输入/输出。

    java中的流是对字节序列的抽象,流中保存的实际上全都是字节文件。

    字节流

    字节流处理的基本单元为1个字节(byte),操作字节和字节数组,通常用来处理二进制数据。

    面向字节流的OutputStream和InputStream是一切的基础。均为抽象类,实际使用的是它们的一系列子类。

    字节输出流OutputStream

    类与方法定义

    抽象类,定义如下:

    public abstract class OutputStream
    implements Closeable, Flushable {}
    //Closeable表示可关闭,Flushable表示刷新,清空内存中的数据
    

    方法有:

    1.将一个字节数据写入数据流
    public abstract void write(int b) throws IOException;
    
    2.将一个byte数组写入数据流
    public void write(byte b[]) throws IOException 
    
    3.将指定byte数组中从偏移量off开始的len个字节写入数据流
    public void write(byte b[], int off, int len) throws IOException 
    
    4.刷新缓冲区
    public void flush() throws IOException {}
    5.关闭输出流
    public void close() throws IOException {}
    

    要想使用此类及以上方法,必须通过子类实例化对象。

    继承OutputStream的子类

    images

    FileOutputStream

    文件输出流,是向File或FileDescriptor输出数据的一个输出流。

    FileOutputStream类的构造方法有:

    1.创建一个文件输出流,向指定的File对象输出数据(写入)

    public FileOutputStream(File file) throws FileNotFoundException 
    //字节写到文件的开始,输出会进行覆盖
    
    public FileOutputStream(File file, boolean append) throws FileNotFoundException
    //如果append设为true,字节将写到文件末尾
    

    2.创建一个文件输出流,向指定名称的文件输出数据(写入到指定名称的文件中)

     public FileOutputStream(String name) throws FileNotFoundException
     //输出会进行覆盖
      
     public FileOutputStream(String name, boolean append)
     throws FileNotFoundException
     //如果append设为true,字节将写到文件末尾
    

    3.创建一个文件输出流,向指定的文件描述器输出数据,该文件描述符表示文件系统中一个实际文件的现有连接。

     public FileOutputStream(FileDescriptor fdObj)
    

    OutputStream的其他子类

    1.ObjectOutputStream(对象输出流)

    对象输出流将Java对象的原始数据类型和对象图写入输出流。只有支持可序列化接口的对象才可以写入到输出流。

    2.ByteArrayOutputStream(字节数组输出流)

    该类实现了一个以字节数组形式写入数据的输出流,缓冲区会随着数据的写入而自动扩大。可以用toByteArray()和toString()检索数据。

    构造方法有:

    (1)创建一个新的字节数组输出流

    public ByteArrayOutputStream() {
            this(32);}
    //缓冲区容量最初是32字节,如果需要,大小会增加
    

    (2)创建一个新的字节数组输出流,并带有制定大小字节的缓冲区容量

    public ByteArrayOutputStream(int size) 
    

    3.PipedOutputStream(管道输出流)

    管道输出流可以连接到管道输入流来创建通信管道,它是这个管道的发送端。

    一个线程通过管道输出流写入(发送)数据,另一个线程通过管道输入流读取数据,这样可实现两个线程之间的通讯。

    不建议尝试从单个线程使用这两个管道流对象,因为它可能会造成线程死锁。

    构造方法有:

    (1)创建连接到指定管道输入流的管道输出流

    public PipedOutputStream(PipedInputStream snk)  throws IOException
    

    (2)创建一个管道输出流,该输出流尚未连接到管道输入流

    public PipedOutputStream()
    

    4.FilterOutputStream

    其子类有DataOutputStream、BufferedOutputStream、PrintStream。

    代码示例:将序列化对象写入文件

    创建文件,输出字节的代码示例:

    public class Test {
        public static void main(String[] args) {
            try {
                File f = new File("E:/test1.txt");
                //如果文件不存在,会自动创建出来
                FileOutputStream fs = new FileOutputStream(f);
                //如果想在文件中执行追加,构造方法中append设为true
                String str = "Hello World!";
                byte[] b = str.getBytes();
                //因为是字节流,先将字符串转化为字节数组
                fs.write(b);
                //这里也可以一个字节一个字节进行输出
                //for(int i=0;i<b.length;i++){fs.write([i]);}
                fs.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
    

    将序列化对象写入文件,会用到字节输出流:

    /**
     * 将序列化对象存储到文件
     */
    public class Test {
        public static void main(String[] args) {
            //创建一个Use对象
            User user = new User(1, "amy");
            //创建一个List对象
            List<String> list = new ArrayList<String>();
            list.add("hello");
            list.add("I am");
            list.add("amy");
            try {
    
                FileOutputStream fs = new FileOutputStream("E:/test.ser");
                //如果文件不存在,会自动创建出来
                ObjectOutputStream os = new ObjectOutputStream(fs);
                //ObjectOutputStream能让写入对象,但无法直接连接文件,所以需要参数指引
                os.writeObject(user);
                os.writeObject(list);
                //写入对象
                os.close();
                //关闭ObjectOutputStream
            } catch (IOException ex) {
                ex.printStackTrace();
            }catch (ClassNotFoundException ex) {
                ex.printStackTrace();
        }
    }
    public class User implements Serializable {
        //User类实现序列化
        private int id;
        private String name;
        public User(int id, String name) {
            this.id = id;
            this.name = name;
        }
    }
    

    如下图所示

    image

    字节输入流InputStream

    类与方法定义

    抽象类,定义如下:

    public abstract class InputStream implements Closeable
    

    方法有:

    1.读取一个字节无符号填充到int的低8位,高8位补零,读取到-1结束
    public abstract int read() throws IOException;
    //read()返回读取的字节或-1(-1表示读取结束)
    
    2.将当前输入流中b.length个字节读入到byte数组中,实际读取的字节数作为整数返回
     public int read(byte b[]) throws IOException 
    
    3.将输入流中len个字节数据读入一个字节数组中,从数组的off位置开始存放len长度的数据
    public int read(byte b[], int off, int len) throws IOException 
    //带有参数的read方法返回的是读取字节数或-1,内部也在调用read(),读取字节
    
    4.从该输入流中跳过或忽略n字节
    public long skip(long n) throws IOException 
    
    5.返回这个输入流中可读(或跳过)的字节数估计值,即可取得输入文件的大小
    public int available() throws IOException 
    
    6.关闭输入流
    public void close() throws IOException
    

    要想使用此类及以上方法,必须通过子类实例化对象。

    继承InputStream的子类

    image

    FileInputStream

    文件输入流,从文件系统中的文件读取输入字节。

    FileInputStream的构造方法有:

    1.创建一个输入文件流,从指定的File对象读取数据

    public FileInputStream(File file) throws FileNotFoundException
    

    2.创建一个输入文件流,从指定名称的文件读取数据

    public FileInputStream(String name) throws FileNotFoundException
    

    3.创建一个输入文件流,从指定的文件描述器读取数据

     public FileInputStream(FileDescriptor fdObj)
    

    InputStream的其他子类

    1.ObjectInputStream(对象输入流)

    2.ByteArrayInputStream(字节数组输入流)

    把内存的一个缓冲区作为InputStream使用。

    构造方法有:

    (1)创建一个字节数组输入流,使用buf作为它的缓冲区数组,从缓冲区数组中读取数据

    public ByteArrayInputStream(byte buf[])
    

    (2)创建一个字节数组输入流,从指定字节数组中读取数据

    public ByteArrayInputStream(byte buf[], int offset, int length)
    //offset表示读取缓冲区中第一个字节的偏移
    //length表示从缓冲区读取的最大字符数
    

    3.PipedInputStream(管道输入流)

    管道输入流是一个通讯管道的接收端。

    构造方法有:

    (1)创建连接到指定管道输出流的管道输入流

     public PipedInputStream(PipedOutputStream src) throws IOException
     
    public PipedInputStream(PipedOutputStream src, int pipeSize)
            throws IOException
             //使用指定的管道大小作为管道的缓冲
    

    (2)创建一个管道输入流,该输出流尚未连接到管道输出流

    public PipedInputStream()
    
    public PipedInputStream(int pipeSize)
    //使用指定的管道大小
    

    4.SequenceInputStream(序列输入流)

    此类允许应用程序把几个输入连续地合并起来,它从一个有序的输入流集合开始,每个输入流依次被读取到文件的末尾,直到到达最后一个输入流的文件末尾。

    构造方法有:

    (1)参数中枚举生成运行时类型为输入流的对象。新创建一个序列输入流,通过读取由枚举产生的输入流来初始化它。

    public SequenceInputStream(Enumeration<? extends InputStream> e) 
    

    (2)新创建一个序列输入流,按参数顺序读取输入流s1,s2来初始化它。

    public SequenceInputStream(InputStream s1, InputStream s2) 
    

    5.StringBufferInputStream

    不推荐使用,此类不能将字符正确地转换为字节。从一个串创建一个流的最佳方法是采用StringReader类。

    6.FilterInputStream

    其子类有LineNumberInputStream(java8中已不建议使用)、DataInputStream、BufferedInputStream、PushbackInputStream。

    代码示例:解序列化

    读取文件test1.txt内容的代码示例:

    public class Test {
        public static void main(String[] args) {
            try {
                File f = new File("E:/test1.txt");
                //如果文件不存在,就会抛出异常
                FileInputStream fs = new FileInputStream(f);
                byte[] b = new byte[(int) f.length()];
                //文件中内容都读到此数组中,数组大小由文件决定
                //在不知道输入文件大小时,可用while循环
                int temp = 0;
                int len = 0;
                while ((temp = fs.read()) != -1) {
                    //-1为文件读完的标志
                    b[len] = (byte) temp;
                    len++;
                }
                //可以直接fs.read(b);读取内容
                //也可以一个一个读取:for(int i=0;i<b.length;i++){b[i]=(byte)fs.read()};
                fs.close();
                //关闭输出流
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
    

    把序列化对象从文件中读取出来,会用到字节输入流(与上文把序列化对象写入文件关联):

    /**
     * 把序列化对象从文件test.ser中读取出来
     * 读取的时候,读取顺序一定要与写入顺序相同
     */
    public class Test {
        public static void main(String[] args) {
            try {
                FileInputStream fs = new FileInputStream("E:/test.ser");
                //如果文件不存在,会抛出异常
                ObjectInputStream os = new ObjectInputStream(fs);
                //ObjectOutputStream知道如何提取对象,但是要靠链接的stream提供文件存取
                User a1 = (User) os.readObject();
                //readObject()的返回类型是Object,要转换为原来的User类型
                System.out.println("user的内容为");
                System.out.println(a1.getId());
                System.out.println(a1.getName());
                List a2 = (List) os.readObject();
                //读取顺序与读入顺序一致
                System.out.println("list的内容为");
                System.out.println(a2);
                os.close();
                //关闭ObjectOutputStream,FileInputStream会自动跟着关闭
            } catch (IOException ex) {
                ex.printStackTrace();
            } catch (ClassNotFoundException ex) {
                ex.printStackTrace();
            }
        }
    }
    

    如下图所示:

    image

    字符流

    字符流处理的基本单元为2个字节的Unicode字符,操作字符、字符数组或字符串,它通常用来处理文本数据。

    java提供了Writer、Reader两个专门操作字符流的类。表示以Unicode字符为单位往stream中写入或读取信息。

    字符输出流Writer

    类与方法定义

    抽象类,定义如下:

    public abstract class Writer implements Appendable, Closeable, Flushable 
    

    常用方法有:

    1.写一个字符
    public void write(int c) throws IOException
    
    2.写一个字符数组
    public void write(char cbuf[]) throws IOException 
    
    abstract public void write(char cbuf[], int off, int len) throws IOException;
    //从cbuf[]的off位置开始,写入字符数为len的字符数组
    
    3.写入一个字符串
    public void write(String str) throws IOException 
    
     public void write(String str, int off, int len) throws IOException 
     //从字符串str的off位置开始,写入的字符数为len
    
    4.将指定的字符序列caq追加到该writer
    public Writer append(CharSequence csq) throws IOException 
    
    //追加从指定序列的start位置开始,至end位置的字符序列
    public Writer append(CharSequence csq, int start, int end) throws IOException 
    
    5.将指定字符追加到该writer
    public Writer append(char c) throws IOException
    
    6.强制性清空缓存
    abstract public void flush() throws IOException;
    
    7.关闭输出流
    abstract public void close() throws IOException;
    

    要想使用此类,必须通过子类实例化对象,使用子类。

    继承Writer的子类

    image

    FileWriter

    如果是向文件中写入内容,应该使用FileWriter子类。与FileOutputStream对应。

    ++FileWriter继承自OutputStreamWriter,OutputStreamWriter继承自Writer++

    构造方法:

    1.构造给定File对象的FileWriter(文件写入器对象)

    public FileWriter(File file) throws IOException
    
    //如果apped设为true,数据将写到文件的末尾而不是开始
    public FileWriter(File file, boolean append) throws IOException
     
    

    2.构造给定文件名的FileWriter

    public FileWriter(String fileName) throws IOException
     
    //如果append设为true,数据将写到文件的末尾
    public FileWriter(String fileName, boolean append) throws IOException
    

    3.构造与文件描述器相关联的FileWriter

    public FileWriter(FileDescriptor fd) 
    

    Writer的其他子类

    1.BufferedWriter

    2.CharArrayWriter
    与ByteArrayOutputStream对应

    3.PipedWriter
    与PipedOutputStream对应

    4.FilterWriter

    5.StringWriter

    代码示例:将字符串写入文本文件

    public class Test {
        public static void main(String[] args) {
            try {
                FileWriter fw = new FileWriter("E:/test2.txt");
                //文件如果不存在就会被创建
                //如果是在文件中追加内容,构造方法中append的值设为true
                BufferedWriter bw = new BufferedWriter(fw);
                bw.write("Hello,World");
                //这里以字符作参数
                bw.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
    

    字符输入流Reader

    类与方法定义

    抽象类,定义如下:

    public abstract class Reader implements Readable, Closeable 
    

    常用方法有:

    1.读取单个字符
    public int read() throws IOException
    
    2.将内容读入字符数组,返回读入的长度,如果到达输入流末端,返回-1
    public int read(char cbuf[]) throws IOException 
    
    //len表示要读取的最大字符数
    //将输入流中len个字符数据读入一个字符数组中,从数组的off位置开始存放len长度的数据
    abstract public int read(char cbuf[], int off, int len) throws IOException;
    
    3.关闭输入流,并释放与之关联的任何系统资源
    abstract public void close() throws IOException;
    

    要想使用此类,必须通过子类实例化对象,使用子类。

    继承Reader的子类

    image

    FileReader

    如果要从文件中读取内容,可以直接使用FileReader子类。FileReader与FileInputStream对应。

    ++FileReader继承自InputStreamReader,InputStreamReader继承自Reader++。

    构造方法:

    1.创建一个新的FileReader,从指定的File对象读取数据

    public FileReader(File file) throws FileNotFoundException
    

    2.创建一个新的FileReader,从指定名称的文件中读取数据

    public FileReader(String fileName) throws FileNotFoundException
    

    3.创建一个新的FileReader,从指定的文件描述器读取数据

    public FileReader(FileDescriptor fd)
    

    Reader的其他子类

    1.BufferedReader

    2.CharArrayReader

    与ByteArrayInputStream对应。此类实现一个可用作字符输入流的字符缓冲区。子类有LineNumberReader

    3.PipedReader
    与PipedInputStream对应

    4.FilterReader
    子类有PushbackReader。

    5.StringReader

    代码示例:

    public class Test {
        public static void main(String[] args) {
            try {
                FileReader fw = new FileReader("E:/test2.txt");
                BufferedReader bw = new BufferedReader(fw);
                //它只会在缓冲区读空的时候才会回头去读取
                //以字符数组的形式读取数据
                char[] c = new char[1024];
                //所有内容都读到此数组中
                int len = bw.read(c);
                //read(char c[])返回读入长度
                bw.close();
                System.out.println(new String(c, 0, len));
                //把字符数组变成字符串输出
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
    
               //还可以以while循环判断是否读到底
                int len=0;
                int temp=0;
                char[] c = new char[1024];
                while((temp=bw.read())!=-1){
                    c[len]=(char)temp;
                    len++;
                }
                bw.close();
                System.out.println(new String(c, 0, len));
                //把字符数组变成字符串输出
    
                 //还可以用readLine()方法
                //直接用String变量来承接所读取结果
                String line=null;
                while((line=bw.readLine())!=null){
                    System.out.println(line);
                }
    

    字节—字符转换

    OutputStreamWriter和InputStreamReader是字节流与字符流的转换类。从源码中分析:

    ++OutputStreamWriter是Writer的子类,将输出的字符流转换成字节流。++
    ++InputStreamReader是Reader的子类,将输入的字节流转换成字符流。++

    OutputStreamWriter

    public class OutputStreamWriter extends Writer
    

    OutputStreamWriter类是从字符流到字节流的桥梁。在OutputStreamWriter类中需要一个字节流的对象(OutputStream out)。

    OutputStreamWriter将多个字符写入到一个输出流,根据指定的字符编码将写入的字符编码成字节。实际负责编码的是StreamEncoder类,过程中必须使用指定的编码集。

    构造方法:

    1.可以通过名称指定支持的字符编码集:

    public OutputStreamWriter(OutputStream out, String charsetName)
            throws UnsupportedEncodingException
    

    2.构造器中指定Charset类型:

    public OutputStreamWriter(OutputStream out, Charset cs)
    

    3.也可以接受平台的默认编码集:

    public OutputStreamWriter(OutputStream out) 
    

    4.可以使用指定charset encoder:

    public OutputStreamWriter(OutputStream out, CharsetEncoder enc) 
    

    write()方法的每次调用都会在给定字符(或字符串)上调用编码转换器。在写入底层输出流之前,将在缓冲区中积累产生的字节

    注意,传递给write()方法的字符没有缓冲。OutputStreamWriter的write()方法写入的是字符(或字符串),而非字节。写入字节的是输出字节流(OutputStream)。

    为了提高效率,可以考虑将OutputStreamWriter包装在一个BufferedWriter中,以避免频繁的转换。例如:

     Writer out  = new BufferedWriter(new OutputStreamWriter(System.out));
    

    InputStreamReader

    public class InputStreamReader extends Reader 
    

    InputStreamReader类是从字节流到字符流的桥梁。在InputStreamReader类中需要一个字节流的对象(InputStream in)。

    它使用的字符集可以通过名称指定,也可以明确Charset类型,也可接受平台的默认字符集。与OutputStreamWriter类似。

    构造函数:

    public InputStreamReader(InputStream in, String charsetName)
    public InputStreamReader(InputStream in, Charset cs)
    public InputStreamReader(InputStream in) 
    
    

    每次调用InputStreamReader的read()方法,都可能导致从底层字节输入流中读取一个或多个字节。为了能有效地将字节转化为字符,可从底层输入流中读取更多字节,而不只是满足当前读取操作所需的字节。

    注意,InputStreamReader是Reader的子类,其read()方法读取的是一个或多个字符。读取字节的是字节输入流(InputStream)。

    为了提高效率,可以考虑将InputStreamReader包装在一个BufferedReader中,比如:

     BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    

    代码示例:用Buffered从Socket上读取数据

    /**
     *从Socket上读取数据
     */
    public class Test {
        public static void main(String[] args) {
            try {
                Socket chatSocket = new Socket("127.0.0.1", 5000);
                //建立对服务器的Socket连接
                InputStreamReader stream = new InputStreamReader(chatSocket.getInputStream());
                //从Socket取得输入串流
                //建立连接到Socket上的输入串流InputStreamReader(转换字节成字符的桥梁)
                BufferedReader reader = new BufferedReader(stream);
                String message = reader.readLine();
                reader.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
    

    如下图所示:

    image

    字节流与字符流的比较

    1. 字节流操作的基本单元是字节;字符流操作的基本单元为Unicode码元。

    2. 字节流在操作的时候本身不会用到缓冲区的,是与文件本身直接操作的;而字符流在操作的时候使用到缓冲区的。

    所有文件的存储都是字节(byte)的存储,在磁盘上保留的是字节。

    1. 在使用字节流操作中,即使没有关闭资源(close方法),也能输出;而字符流不使用clode方法的话,不会输出任何内容。

    说明字符流用到了缓冲区,如果执行关闭输出流的话会刷新缓冲区,所以可以把内容输出。如果没有关闭,可以调用flush()方法强制刷新缓冲区,这样可以在不使用close()的情况下输出内容。

    输入流、输出流相关类的使用判断

    根据使用场景决定使用哪个类。参考[字符流和字节流的区别,使用场景,相关类]。如下(不考虑特殊需要):

    1.考虑最原始的数据格式是什么:

    (1)二进制格式(只要不能确定是纯文本的):InputStream,OutputStream及其子类(字节流)。

    (2)纯文本格式(含纯英文与汉字或其他编码方式):Reader,Writer及其子类(字符流)。

    2.是输入还是输出:

    (1)输入:Reader,InputStream类型的子类。

    (2)输出:Writer,OutputStream类型的子类。

    3.是否需要转换流:

    字节到字符:InputStreamReader

    字符到字节:OutputStreamWriter

    4.数据来源(去向)是什么:

    (1)是文件:FileInputStream,FileOutputStream ; FileReader,FileWriter

    (2)是byte[]: ByteArrayInputStream, ByteArrayOutputStream

    (3)是Char[]:CharArrayReader,CharArrayWriter

    (4)是String:StringBufferInputStream,StringBufferOutputStream;StringReader,StringWriter

    (5)是网络数据流:InputStream,OutputStream;Reader,Writer

    5.是否要缓冲:(要注意readLine()是否有定义,有什么比read(),writer()更特殊的输入或输出方法)

    要缓冲:BufferedInputStream, BufferedOutputStream; BufferedReader, BufferedWriter

    6.是否要格式化输出:

    要格式化输出:PrintStream, PrintWriter

    PrintStream是FilterOutputStream的子类。
    PrintWriter是Writer的子类。可用PrintWriter写数据到Socket上。

    还有如下特殊需要的情景:

    (1)对象输入输出:ObjectInputStream, ObjectOutputStream

    (2)进程间通信:PipedInputStream, PipedOutputStream, PipedReader, PipedWriter

    (3)合并输入: SequenceInputStream

    (4)更特殊的需要:

    PushbackInputStream(FilterInputStream的子类),LineNumberInputStream(FilterInputStream的子类,java8中已不建议使用);

    LineNumberReader(BufferedReader的子类),PushbackReader(FilterReader的子类)

    相关文章

      网友评论

        本文标题:字节流、字符流及其转换、比较与使用场景

        本文链接:https://www.haomeiwen.com/subject/ykjtgxtx.html