Java中的IO-05

引言

上一篇文章中我们谈及了字节数组流以及字符数组流,这些流虽然不常见,但是在一定时候却能发挥巨大的作用。这里我们要学习的不是和数组有关的了,而是和Java中的数据类型有关的流。众所周知,Java中的数据分为基本数据类型还有对象,那么我就从基本数据类型开始说起吧。

DataInputStream, DateOutputStream

这两个流的使用方式也是非常简答的。简单的看看官方文档就行了。

java.lang.Object 
    java.io.InputStream 
        java.io.FilterInputStream 
            java.io.DataInputStream 
public class DataInputStream
extends FilterInputStream
implements DataInput
  • 数据输入流允许应用程序以独立于机器的方式从底层输入流读取原始Java数据类型。应用程序使用数据输出流来写入稍后可以被数据输入流读取的数据。
  • DataInputStream对于多线程访问来说不一定是安全的。 线程安全是可选的,是本课程中用户的责任。
DataInputStream(InputStream in) 
// 创建使用指定的底层InputStream的DataInputStream。 
int read(byte[] b) 
// 从包含的输入流中读取一些字节数,并将它们存储到缓冲区数组 b 。  
int read(byte[] b, int off, int len) 
// 从包含的输入流读取最多 len个字节的数据为字节数组。  
boolean readBoolean() 
// 见的总承包 readBoolean的方法 DataInput 。  
byte readByte() 
// 见的总承包 readByte的方法 DataInput 。  
char readChar() 
// 见 readChar方法的总合同 DataInput 。  
double readDouble() 
// 见 readDouble方法 DataInput的总体合同。  
float readFloat() 
// 见 readFloat法 DataInput的一般合同。  
void readFully(byte[] b) 
// 见的总承包 readFully的方法 DataInput 。  
void readFully(byte[] b, int off, int len) 
// 见的总承包 readFully的方法 DataInput 。  
int readInt() 
// 见 readInt方法 DataInput的一般合同。  
String readLine() 
/*已弃用 
此方法无法将字节正确转换为字符。 从JDK 1.1开始,读取文本行的BufferedReader.readLine()方法是通过BufferedReader.readLine()方法。 使用DataInputStream类读取行的程序可以转换为使用BufferedReader类替换以下形式的代码: 
     DataInputStream d = new DataInputStream(in);
 与: 
     BufferedReader d
          = new BufferedReader(new InputStreamReader(in));
*/  
long readLong() 
// 见的总承包 readLong的方法 DataInput 。  
short readShort() 
// 见 readShort方法 DataInput的一般合同。  
int readUnsignedByte() 
// 见的总承包 readUnsignedByte的方法 DataInput 。  
int readUnsignedShort() 
// 见 readUnsignedShort法 DataInput的一般合同。  
String readUTF() 
// 见 readUTF法 DataInput的一般合同。  
static String readUTF(DataInput in) 
// 从流in读取以modified UTF-8格式编码的Unicode字符串的表示; 这个字符串然后作为String返回。  
int skipBytes(int n) 
// 见 skipBytes法 DataInput的一般合同。  

这些方法倒也是非常容易理解的。下面直接给出代码举例吧。

代码举例

public class DataStreamDemo {
    public static void main(String[] args) {
        DataOutputStream out = null;
        BufferedReader br = null;
        byte[] a= "sheriby\nsheriby\nhonysher".getBytes(StandardCharsets.UTF_8);
        try {
            DataInputStream in = new DataInputStream(new ByteArrayInputStream(a));
            out = new DataOutputStream(new FileOutputStream("./Exercise/src/sher2"));
            br = new BufferedReader(new InputStreamReader(in));
            String line;
            while ((line = br.readLine()) != null) {
                String temp = line.toUpperCase();
                out.writeChars(temp);
                out.writeChar('\n');
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (br != null) {
                    br.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

其实上面的这个代码的示例并不是非常的典型,所谓的典型的用法其实是下面的这个用法。

public class DataStreamDemo2 {
    public static void main(String[] args) {
        DataOutputStream out = null;
        DataInputStream in = null;

        try {
            out = new DataOutputStream(new FileOutputStream("test"));
            out.writeInt(2);
            out.writeDouble(3.1415926);
            out.writeUTF("sher is the best boy!");
            out.writeBoolean(false);
            in = new DataInputStream(new FileInputStream("test"));
            System.out.println(in.readInt());
            System.out.println(in.readDouble());
            System.out.println(in.readInt());
            System.out.println(in.readUTF());
            System.out.println(in.readBoolean());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
                if (out != null) {
                    out.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

我们通过DataOutputStream将我们需要的数据按顺序写入到了文件当中,然后使用DataInputStream来读取文件中的基本类型的数据,不过需要注意的是我们读取时的顺序必须要和写入是的顺序是相同的,不然代码就会抛出一个异常。还要注意的是,此时那个文件test并不是一个可读的文件(是我们有点儿看不懂的文件)。比如

   @    !�M�J sher is the best boy! 

可以看到前面的数据都是不可读的,只有后面的是writeUTF()写入的是可读的,但是如果只是用writeUTF()方法的话也是会出现不可读的字符的,那是因为使用wrteUTF()方法会在前面的两个字节储存长度信息。

上面基本上就是这两个流的基本的用法了。

ObjectInputStream, ObjectOutputStream

上面的流是进行基本数据类型的读写,下面要说的下Java当中最为重要的对象类型的读写流。用法与上面的基本数据类型流基本是类似的。不过也有一些需要注意的地方。下面就看一下ObjecOutputStream的官方文档吧。

java.lang.Object 
    java.io.OutputStream     
        java.io.ObjectOutputStream 
public class ObjectOutputStream
extends OutputStream
implements ObjectOutput, ObjectStreamConstants

官方文档中对这个类的说明非常的长而且复杂,所以我们先看看构造器还有他的成员方法吧。

protected  ObjectOutputStream() 
// 为完全重新实现ObjectOutputStream的子类提供一种方法,不必分配刚刚被ObjectOutputStream实现使用的私有数据。  
ObjectOutputStream(OutputStream out) 
// 创建一个写入指定的OutputStream的ObjectOutputStream。  

第一个构造方法是protected的,所以我们大可不必看这个玩意。

protected void annotateClass(类<?> cl) 
// 子类可以实现此方法,以允许类数据存储在流中。  
protected void annotateProxyClass(类<?> cl) 
// 子类可以实现这种方法来存储流中的自定义数据以及动态代理类的描述符。  
void close() 
// 关闭流。  
void defaultWriteObject() 
// 将当前类的非静态和非瞬态字段写入此流。  
protected void drain() 
// 排除ObjectOutputStream中的缓冲数据。  
protected boolean enableReplaceObject(boolean enable) 
// 启用流来替换流中的对象。  
void flush() 
// 刷新流。  
ObjectOutputStream.PutField putFields() 
// 检索用于缓冲要写入流的持久性字段的对象。  
protected Object replaceObject(Object obj) 
// 该方法将允许ObjectOutputStream的可信子类在序列化期间将一个对象替换为另一个对象。  
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) 
// 写入一个8位字节。  
void writeBytes(String str) 
// 写一个字符串作为字节序列。  
void writeChar(int val) 
// 写一个16位的字符。  
void writeChars(String str) 
// 写一个字符串作为一系列的字符。  
protected void writeClassDescriptor(ObjectStreamClass desc) 
// 将指定的类描述符写入ObjectOutputStream。  
void writeDouble(double val) 
// 写一个64位的双倍。  
void writeFields() 
// 将缓冲的字段写入流。  
void writeFloat(float val) 
// 写一个32位浮点数。  
void writeInt(int val) 
// 写一个32位int。  
void writeLong(long val) 
// 写一个64位长  
void writeObject(Object obj) 
// 将指定的对象写入ObjectOutputStream。  
protected void writeObjectOverride(Object obj) 
// 子类使用的方法来覆盖默认的writeObject方法。  
void writeShort(int val) 
// 写一个16位短。  
protected void writeStreamHeader() 
// 提供了writeStreamHeader方法,因此子类可以在流中附加或预先添加自己的头。  
void writeUnshared(Object obj) 
// 将“非共享”对象写入ObjectOutputStream。  
void writeUTF(String str) 
// 此字符串的原始数据写入格式为 modified UTF-8 。  

可以看到这个类的成员方法是非常多的,但是我们可以发现的是,这个类不仅仅是可以写入对象,也可以写入基本数据类型,甚至原始数组(原始数组其实也是一个对象)。DataStream的功能基本上都是被ObjectStream说涵盖了的。

下面就来看看官方文档对这个类的一些说明。

  • ObjectOutputStream将Java对象的原始数据类型和图形写入OutputStream。可以使用ObjectInputStream读取(重构)对象。可以通过使用流的文件来实现对象的持久存储。如果流是网络套接字流,则可以在另一个主机上或另一个进程中重构对象。

  • 只有支持java.io.Serializable接口的对象才能写入流中。 每个可序列化对象的类被编码,包括类的类名和签名,对象的字段和数组的值以及从初始对象引用的任何其他对象的关闭。

  • 方法writeObject用于将一个对象写入流中。 任何对象,包括字符串和数组,都是用writeObject编写的。 多个对象或原语可以写入流。 必须从对应的ObjectInputstream读取对象,其类型和写入次序相同。

  • 原始数据类型也可以使用DataOutput中的适当方法写入流中。 字符串也可以使用writeUTF方法写入。

  • 对象的默认序列化机制写入对象的类,类签名以及所有非瞬态和非静态字段的值。 引用其他对象(除了在瞬态或静态字段中)也会导致这些对象被写入。 使用引用共享机制对单个对象的多个引用进行编码,以便可以将对象的图形恢复为与原始文件相同的形状。

  • 例如,要写一个ObjectInputStream中的示例可以读取的对象:

FileOutputStream fos = new FileOutputStream("t.tmp");
     ObjectOutputStream oos = new ObjectOutputStream(fos);

       oos.writeInt(12345);
        oos.writeObject("Today");
        oos.writeObject(new Date());

        oos.close(); 
  • 在序列化和反序列化过程中需要特殊处理的类必须采用具有这些精确签名的特殊方法:

    private void readObject(java.io.ObjectInputStream stream)
    throws IOException, ClassNotFoundException;
    private void writeObject(java.io.ObjectOutputStream stream)
      throws IOException
    private void readObjectNoData()
       throws ObjectStreamException; 
    • writeObject方法负责为其特定的类编写对象的状态,以便相应的readObject方法可以恢复它。 该方法不需要关心属于对象的超类或子类的状态。 通过使用writeObject方法或通过使用DataOutput支持的原始数据类型的方法将各个字段写入ObjectOutputStream来保存状态。
    • 序列化不会写出任何不实现java.io.Serializable接口的对象的字段。 不可序列化的对象的子类可以是可序列化的。 在这种情况下,非可序列化类必须有一个无参数构造函数,以允许其字段被初始化。 在这种情况下,子类有责任保存并恢复不可序列化类的状态。 通常情况下,该类的字段是可访问的(public,package或protected),或者可以使用get和set方法来恢复状态。
  • 可以通过实现抛出NotSerializableExceptionwriteObjectreadObject方法来防止对象的序列化。 异常将被ObjectOutputStream捕获并中止序列化过程。

    • 实现Externalizable接口允许对象完全控制对象的序列化表单的内容和格式。 调用Externalizable接口writeExternalreadExternal的方法来保存和恢复对象的状态。 当由类实现时,他们可以使用ObjectOutputObjectInput的所有方法来写入和读取自己的状态。 对象处理发生的任何版本控制都是有责任的。
    • 枚举常数与普通可序列化或外部化对象不同的是序列化。 枚举常数的序列化形式仅由其名称组成; 不传输常数的字段值。 要序列化一个枚举常量,ObjectOutputStream会写入常数名称方法返回的字符串。 像其他可序列化或可外部化的对象一样,枚举常量可以作为随后在序列化流中出现的反向引用的目标。 枚举常数序列化的过程无法定制; 在序列化期间,将忽略由枚举类型定义的任何类特定的writeObjectwriteReplace方法。 类似地,任何serialPersistentFieldsserialVersionUID字段声明也被忽略 - 所有枚举类型都有一个固定的serialVersionUID为0L。
    • 原始数据(不包括可序列化字段和外部化数据)在块数据记录中写入ObjectOutputStream。 块数据记录由报头和数据组成。 块数据头由标记和跟随标题的字节数组成。 连续的原始数据写入被合并成一个块数据记录。 用于块数据记录的阻塞因子将是1024字节。 每个块数据记录将被填充到1024个字节,或者每当块数据模式终止时都被写入。 调用ObjectOutputStream方法writeObjectdefaultWriteObjectwriteFields最初终止任何现有的块数据记录。

第二点是非常重要的,只有支持java.io.Serializable接口的对象才能写入流中,如果没用实现这个接口,写入的时候会报错。至于什么叫Serializable呢。这个被称为是可序列化的。就是可以被写入到文件当中。要使类实现这个接口,这需要implement Serializable不需要实现任何的函数。也就是说这个接口这是起了一个标记的作用,其母的是告诉编译器这个类可以被序列化写入到文件当中去。不过如果有一些数据我们不想其被写入到文件当中去,我们就要加上transient关键词,表示这个属性不会被写入到文件中去。关于序列化还有一点要说的是,序列化不只是对象的一个简单的复制,和此对象有关的所有的有关的对象都会被写入到文件当中去,如果其中有不可序列化的对象也会报错。假如序列化只是写入链接,当我们反序列化的时候码举例


一枚小菜鸡