Java基础之IO(2)-OutputStream

概述

上一篇讲了InputStream,和InputStream相对的,InputStream是把数据从一个源中读取出来,而OutputStream是把数据输出到一个源,这个源可以是一个文件,网络,内存,终端等。和InputStream一样,OutputStream也是字节流,前边解释过什么是字节流,这里再简单说一下,字节流就是以字节的方式操作流。

举个例子:

Alt

字节编码

上图中上边一行就是字符,字符就是通常我们看到的字母,汉字等各个国家的语言以及符号。下边一行是以UTF-8编码的字节,为了体现UTF-8变长的特点,所以里边的标点符号是英文标点符号。你可能已经注意到了,难道不同的编码还有不同的字节表示?

是的,因为开始各个国家都有自己的一套标准,因为各个国家的语言和符号不一样,所以各个国家、地区就都有一套自己的编码。ASCII是一个用一个字节表示的符号集,iso-8859-1被称为西欧语言,是ASCII的一种扩展,但是它们不能表示汉字,所以大陆地区又制定了GBK编码来覆盖汉字,台湾地区制定了BIG5编码。大家觉得你一套我一套不方便,就制定了国际通用的Unicode符号集,但是Unicode是用定长字节来表示字符的,如UTF-16是用两个字节来表示字符。但是有些字符如字母,其实用一个字节就够了,用两个是一种浪费,所有又制定了可变长的UTF-8,可用1-6个字节来编码字符。这里不做字符集的详细介绍,有计划专门写一篇字符集的介绍,这里只是希望大家能理解字符与字节的关系。

OutputStream继承关系图

Alt

按照惯例,将上图中的OutputStream分为节点流和处理流,上一篇中介绍了节点流和处理流,这里不多介绍了。

节点流

上图中的FileOutputStream、PipedOutputStream、ByteArrayOutputStream属于节点流,和上一篇中的FileInputStream、PipedInputStream、ByteArrayInputStream相对应。

FileOutputStream
  • FileOutputStream构造函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //以File对象做为构造参数
    FileOutputStream(File file)
    //以一个已连接的文件描述符为构造参数
    FileOutputStream(FileDescriptor fdObj)
    //以File对象做为构造参数是否追加内容
    FileOutputStream(File file, boolean append)
    //以文件路径为参数
    FileOutputStream(String name)
    //以文件路径和是否追加内容
    FileOutputStream(String name, boolean append)
  • FileOutputStream方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //关闭流
    void close()
    //这个方法不推荐使用并且在未来会被移除
    protected void finalize()
    //获取文件流的channel,这个在NIO中会用到
    FileChannel getChannel()
    //获取文件描述符
    FileDescriptor getFD()
    //写byte数组
    void write(byte[] b)
    //从off位置开始写byte数组的内容,写len个字节
    void write(byte[] b, int off, int len)

    void write(int b)
  • 使用例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    public class FileOutputStreamDemo {
    public void wirteFile(){
    try (FileOutputStream fot = new FileOutputStream("/tmp/out")) {
    String str = "File:你好,世界!";
    fot.write(str.getBytes(Charset.forName("UTF-8")));
    fot.flush();
    } catch (IOException e) {
    e.printStackTrace();
    }

    try (FileInputStream fis = new FileInputStream("/tmp/out")) {
    byte[] bytes = new byte[1024];
    while (fis.read(bytes) != -1){
    System.out.println(new String(bytes, Charset.forName("utf-8")));
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    public void wiriteFD(){
    try (FileOutputStream fos = new FileOutputStream(FileDescriptor.out)) {
    fos.write("FD:你好,世界!".getBytes(Charset.forName("UTF-8")));
    fos.flush();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    public static void main(String[] args) {
    FileOutputStreamDemo demo = new FileOutputStreamDemo();
    demo.wirteFile();
    demo.wiriteFD();
    }
    }

    输出:

    1
    2
    File:你好,世界!
    FD:你好,世界!
PipedOutputStream

上一篇讲PipedInputStream时讲过Pipe的结构,如下图

Alt

数据是有流向的,从PipedOutputStream流向PipedInputStream的缓冲区。

  • PipedOutputStream构造函数

    1
    2
    3
    4
    //空构造函数,创建一个尚未与PipedInputStream相连接的PipedOutputStream,再之后通过connect方法连接
    PipedOutputStream()
    //创建一个与传入PipedInputStream相连接的PipedOutputStream
    PipedOutputStream(PipedInputStream snk)
  • PipedOutputStream方法

    1
    2
    3
    4
    5
    6
    7
    8
    void close()
    //与一个PipedInputStream相连接
    void connect(PipedInputStream snk)
    //
    void flush()

    void write(byte[] b, int off, int len)
    void write(int b)
  • 使用例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    public class PipedInputStreamDemo {
    public static void main(String[] args) {
    PipedInputStream pipedInputStream = new PipedInputStream();
    PipedOutputStream pipedOutputStream = new PipedOutputStream();

    try {
    pipedOutputStream.connect(pipedInputStream);
    } catch (IOException e) {
    e.printStackTrace();
    }

    ExecutorService executor = Executors.newFixedThreadPool(2);
    executor.execute(new ReadWorker(pipedInputStream));
    executor.execute(new WirteWorker(pipedOutputStream));

    try {
    Thread.sleep(10000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    executor.shutdown();
    }
    }

    class ReadWorker implements Runnable {
    private PipedInputStream pipedInputStream;

    public ReadWorker(PipedInputStream pipedInputStream){
    this.pipedInputStream = pipedInputStream;
    }

    @Override
    public void run() {
    byte[] bytes = new byte[1024];
    try {
    while (pipedInputStream.read(bytes) != -1){
    System.out.println(new String(bytes, Charset.forName("UTF-8")));
    }
    } catch (IOException e){

    } finally {
    try {
    pipedInputStream.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    }
    }

    class WirteWorker implements Runnable {
    private PipedOutputStream pipedOutputStream;

    public WirteWorker(PipedOutputStream pipedOutputStream){
    this.pipedOutputStream = pipedOutputStream;
    }
    @Override
    public void run() {
    String str = "hello world!";
    try {
    pipedOutputStream.write(str.getBytes(Charset.forName("UTF-8")));
    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    try {
    pipedOutputStream.close();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
    }
ByteArrayOutputStream

前面一篇讲过ByteArrayInputStream,这里讲的是ByteArrayOutputStream,ByteArrayOutputStream在创建时会提供一个缓冲区,然后可以将输出暂存与缓冲区内,待处理完所有流程后一次将缓冲区内的数据一次取出。

  • ByteArrayOutputStream构造函数

    1
    2
    3
    ByteArrayOutputStream()	

    ByteArrayOutputStream(int size)
  • ByteArrayOutputStream方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    void close()
    //重置ByteArrayOutputStream的count为0,之前的数据将丢失
    void reset()
    //当前缓存区内数据大小
    int size()
    //新创建一个byte数组来返回缓冲区内的数据,注意返回的这个byte数组不是缓冲区,而是一个新的byte数组
    byte[] toByteArray()
    //以平台默认的字符集解码缓存区的字节为字符串
    String toString()
    //不推荐使用
    String toString(int hibyte)
    //以指定的字符集名称解码缓存区的字节为字符串
    String toString(String charsetName)
    //以指定的字符集解码缓存区的字节为字符串
    String toString(Charset charset)
    //将byte数组从off位置开始的len个数组写入ByteArrayOutputStream
    void write(byte[] b, int off, int len)

    void write(int b)

    void writeBytes(byte[] b)
    //将ByteArrayOutputStream的数据全部写入OutputStream输出流中
    void writeTo(OutputStream out)
  • 使用例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    public class ByteArrayOutputStreamDemo {
    public void toByteArrayDemo(){
    try (ByteArrayOutputStream baou = new ByteArrayOutputStream()){
    baou.write("你好,".getBytes(Charset.forName("UTF-8")));
    baou.write("世界!".getBytes(Charset.forName("UTF-8")));
    System.out.println(new String(baou.toByteArray(), Charset.forName("UTF-8")));
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    public void wirteToDemo(){
    try (ByteArrayOutputStream baou = new ByteArrayOutputStream();
    FileOutputStream fos = new FileOutputStream("/tmp/byte")){
    baou.write("你好,".getBytes(Charset.forName("UTF-8")));
    //会重置缓冲区,之前写的数据会丢失
    baou.reset();
    baou.write("世界!".getBytes(Charset.forName("UTF-8")));
    baou.writeTo(fos);
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    public static void main(String[] args) {
    ByteArrayOutputStreamDemo demo = new ByteArrayOutputStreamDemo();
    demo.wirteToDemo();
    demo.toByteArrayDemo();
    }
    }

    输出结果:

    1
    2
    3
    4
    //wirteToDemo byte文件中显示,可以看到调用了reset之后,前边的”你好“已经丢失。
    1 世界!
    //toByteArrayDemo终端结果
    你好,世界!

处理流

BufferedOutputStream

和BufferedInputStream类似,BufferedOutputStream也提供一个缓冲区,增强OutputStream的能力,使得用户在写数据时,不用关系写的数据是如何写入源端,而只有当缓冲区填满或者用户主动调用flush、close时才会将数据真正写入到源端,这样可以减少与源端交互的次数,从而提升性能。要注意的是,当调用BufferedOutputStream方法数据不够填满缓冲区而没有立刻调用flush,并且流没有关闭时,数据并不会立刻写入源端,如果写入的是磁盘文件,打开文件是看不到刚刚写入的数据的。

  • BufferedOutputStream构造函数

    1
    2
    3
    BufferedOutputStream(OutputStream out)
    //创建时设置缓冲区大小,默认是8192
    BufferedOutputStream(OutputStream out, int size)
  • BufferedOutputStream方法

    1
    2
    3
    4
    5
    void flush()	

    void write(byte[] b, int off, int len)

    void write(int b)
  • 使用例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //BufferedOutputStream方法比较简单
    public class BufferedOutputStreamDemo {
    public static void main(String[] args) {
    try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("/tmp/buffer"))) {
    bos.write("你好,世界!".getBytes(Charset.forName("UTF-8")));
    bos.flush();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
DataOutputStream

DataOutputStream和DataInputStream是一对操作,它们提供了一组操作来对用户屏蔽具体数据类型的底层存储细节,但是在用DataInputStream读取时,需要知道数据的格式然后用对应的方法来读取。

  • DataOutputStream构造函数

    1
    DataOutputStream(OutputStream out)
  • DataOutputStream方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    //方法都很简单,基本看名字都知道作用,但是还有有两个方法要说明一下

    void flush()

    int size()

    void write(byte[] b, int off, int len)

    void write(int b)

    void writeBoolean(boolean v)

    void writeByte(int v)
    //该方法不能写入中文,因为在写入时执行out.write((byte)s.charAt(i)),在java中char是两个字节表示的,这里直接强制转换为byte导致信息丢失
    void writeBytes(String s)

    void writeChar(int v)

    void writeChars(String s)

    void writeDouble(double v)

    void writeFloat(float v)

    void writeInt(int v)

    void writeLong(long v)

    void writeShort(int v)
    //在写入时,会自动将字符串的UTF-8字节编码数大写写入开头,
    //如果DataInputStream的readUTF不是读取该方法的写入,并且没有补齐前两个表示字节数的标识,会得到EOFException异常
    void writeUTF(String str)
  • 使用例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    public class DataOutputStreamDemo {
    /**
    * 测试wirteBytes(String s)写中文
    */
    public void wirteBytesChinseStringTest(){
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    try (DataOutputStream dos = new DataOutputStream(bos)) {
    dos.writeBytes("你好");
    dos.flush();
    } catch (IOException e) {
    e.printStackTrace();
    }

    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    try (DataInputStream dis = new DataInputStream(bis)) {
    byte[] bytes = new byte[128];
    dis.readFully(bytes, 0, 2);
    System.out.println(new String(bytes, Charset.forName("UTF-8")));
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    /**
    * 测试writeUTF
    */
    public void wirteUTF(){
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    try (DataOutputStream dos = new DataOutputStream(bos)) {
    dos.writeUTF("你好");
    dos.flush();
    } catch (IOException e) {
    e.printStackTrace();
    }

    ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    try (DataInputStream dis = new DataInputStream(bis)) {
    System.out.println(dis.readUTF());
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    public static void main(String[] args) {
    DataOutputStreamDemo demo = new DataOutputStreamDemo();
    demo.wirteBytesChinseStringTest();
    demo.wirteUTF();
    }
    }

    输出结果:

    1
    2
    `}                                                                                                                              
    你好

    第一个方法测试writeBytes(String s)输出中文时,会出现字节丢失导致无法还原原始数据。第二个方法是以UTF-8方式输出,该方法会将写入字符串以UTF-8编码的字节数大小以两个字节写在数据的最前端,来保证DataInputStream的readUTF方法能知道数据的结尾。如下图是写入了“你好”的字节数组,前两个字节表示总共有6个字节。

    Alt

ObjectOutputStream

ObjectOutputStream将实现Serializable、Externalizable接口的对象序列化到源端。反序列化需要ObjectInputStream。

  • ObjectOutputStream构造函数

    1
    2
    3
    protected ObjectOutputStream()	

    ObjectOutputStream(OutputStream out)
  • ObjectOutputStream方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    protected void	annotateClass(Class<?> cl)

    protected void annotateProxyClass(Class<?> cl)

    void close()

    void defaultWriteObject()

    protected void drain()

    protected boolean enableReplaceObject(boolean enable)

    void flush()

    ObjectOutputStream.PutField putFields()

    protected Object replaceObject(Object obj)

    void reset()

    void useProtocolVersion(int version)

    void write(byte[] buf)

    void write(byte[] buf, int off, int len)

    void write(int val)

    void writeBoolean(boolean val)

    void writeByte(int val)

    void writeBytes(String str)

    void writeChar(int val)

    void writeChars(String str)

    protected void writeClassDescriptor(ObjectStreamClass desc)

    void writeDouble(double val)

    void writeFields()

    void writeFloat(float val)

    void writeInt(int val)

    void writeLong(long val)

    void writeObject(Object obj)

    protected void writeObjectOverride(Object obj)

    void writeShort(int val)

    protected void writeStreamHeader()

    void writeUnshared(Object obj)

    void writeUTF(String str)

    方法有点多,不要被吓到,其实很多方法都是一眼就看出用途的。

  • 使用例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    public class ObjectOutputStreamDemo extends ObjectOutputStream{
    public ObjectOutputStreamDemo(OutputStream out) throws IOException {
    super(out);
    }

    protected ObjectOutputStreamDemo() throws IOException, SecurityException {
    }

    public static void main(String[] args) {

    try (ObjectOutputStreamDemo oos = new ObjectOutputStreamDemo(new FileOutputStream("/tmp/person"))) {
    Person person = new Person();
    person.setName("mark");
    person.setAge(20);
    oos.writeObject(person);
    oos.flush();
    } catch (IOException e) {
    e.printStackTrace();
    }
    try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/tmp/person"))) {
    Person person = (Person) ois.readObject();
    System.out.println("Person name:" + person.getName() + " age:" + person.getAge());
    } catch (IOException | ClassNotFoundException e) {
    e.printStackTrace();
    }
    }
    }

    class Person implements Serializable {
    private String name;

    private Integer age;

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public Integer getAge() {
    return age;
    }

    public void setAge(Integer age) {
    this.age = age;
    }
    }

    ObjectOutputStream的有一些方法要起作用,需要用户继承ObjectInputStream重写该方法。

PrintStream

PrintStream打印输出流,大家应该最熟悉了,如果你觉得不熟悉,你平时肯定用过System.out.println(),而且是经常使用,其中的out就是PrintStream,也就是说你System.out调用的方法都是PrintStream的方法。

1
public static final PrintStream out = null;

PrintStream同样是一个处理流,通过对输出流的增强,提供一些打印的功能,如print()、println()等等功能。

  • PrintStream构造函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    PrintStream(File file)	

    PrintStream(File file, String csn)

    PrintStream(File file, Charset charset)

    PrintStream(OutputStream out)

    PrintStream(OutputStream out, boolean autoFlush)

    PrintStream(OutputStream out, boolean autoFlush, String encoding)

    PrintStream(OutputStream out, boolean autoFlush, Charset charset)

    PrintStream(String fileName)

    PrintStream(String fileName, String csn)

    PrintStream(String fileName, Charset charset)
  • PrintStream方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    PrintStream	append(char c)	

    PrintStream append(CharSequence csq)

    PrintStream append(CharSequence csq, int start, int end)

    boolean checkError()

    protected void clearError()

    void close()

    void flush()

    PrintStream format(String format, Object... args)

    PrintStream format(Locale l, String format, Object... args)

    void print(boolean b)

    void print(char c)

    void print(char[] s)

    void print(double d)

    void print(float f)

    void print(int i)

    void print(long l)

    void print(Object obj)

    void print(String s)

    PrintStream printf(String format, Object... args)

    PrintStream printf(Locale l, String format, Object... args)

    void println()

    void println(boolean x)

    void println(char x)

    void println(char[] x)

    void println(double x)

    void println(float x)

    void println(int x)

    void println(long x)

    void println(Object x)

    void println(String x)

    protected void setError()

    void write(byte[] buf, int off, int len)

    void write(int b)

    PrintStream大家经常用,这里就不举例子了。