还是从输出流开始说、此流是有付出才有回报、虽然不想PipedInputStream、PipedOutputStream那样必须一对对象结合才能使用、但是也要先有DataOutputStream将数据写入目的地中、才能使用DataInputStream将数据读取到程序中并转换成对应java基础类型。
数据字节输出流、在介绍DataOutputStream之前要先介绍一个类FilterOutputStream、一个接口DataOutput、FilterOutputStream:前面已经说过、是所有装饰字节输出流的装饰类的父类、定义装饰的标准、其意义是将装饰的实现对用户透明化、DataOutput:数据输出接口、定义了将java各种原始数据转换成二进制字节流的方法、而DataOutputStream作为DataOutput实现类、重写了DataOut中所有方法、到这里很自然的就明白了DataOutputStream类的作用:将java中的基础数据类型写入数据字节输出流中、保存在存储介质中、然后可以用DataInputStream从存储介质中读取到程序中还原成java基础类型。这里多提一句、DataOutputStream、DataOut、FilterOutputStream三个类的关系的这种设计既使用了装饰器模式、又使用了桥接模式、避免了类的爆炸式增多。
protected int wrriten; 用于记录当前流中已经写入的字节数 private byte[] bytearr; 用于临时存放writeUTF(String str)方法中str转换成的字节数组和表示字节数组长度的字节
DataOutputStream(OutputStream out); 根据传入的低级字节输入流构造dos
void flush(); 将dos中的所有字节flush到目的地中 int size(); 返回dos中现存的字节总数 synchronized void write(int b); 将一个字节以整数形式写入dos中 synchronized void write(byte b[], int off, int len); 将一个字节数组的一部分写入到dos中 writeBoolean(boolean v); 将一个boolean数据写入dos中 writeByte(int v); 将一个字节写入dos中 writeShort(int v); 将一个short型数据写入dos中 void writeChar(int v); 将一个字符型数据写入dos中 void writeInt(int v); 将一个整形数据写入dos中 void writeLong(long v); 将一个long型数据写入dos中 void writeFloat(float v); 将一个float型数据写入dos中 void writeDouble(double v); 将一个double型数据写入dos中 void writeBytes(String s); 将一个字符串形式的有序字节数据写入dos中 void writeChars(String s); 将一个有序字符组成的字符串数据写入dos中 void writeUTF(String str); 将一个任意字符串数据写入dos中
package com.chy.io.original.code; import java.io.IOException; import java.io.UTFDataFormatException; /** * DataOutput的实现类:将java基础类型转换成字节、写入到一个二进制流中的方法。 * 同时还提供了一个将 String 转换成 UTF-8 修改版格式并写入所得到的系列字节的工具。 */ public class DataOutputStream extends FilterOutputStream implements DataOutput { /** * 到目前为止写入数据输出流的字节数、实际上就是个计数器、用于size()的返回值。 */ protected int written; /** * writeUTF使用的变量。 */ private byte[] bytearr = null; /** * 通过OutputStream实现类创建一个DataOutputStream dos 、此dos继承FilterOutputStream装饰类、DataOut接口。 * 写入字节数标记:written = 0 。 */ public DataOutputStream(OutputStream out) { super(out); } /** * 增加written的值、直到int类型能达到的最大的值2的31次方-1。 */ private void incCount(int value) { int temp = written + value; if (temp < 0) { temp = Integer.MAX_VALUE; } written = temp; } /** * 调用传入的OutputStream(以下简称out)的out.write(b)方法将int b写入out指向的目的地中。 * 并且written + 1。 */ public synchronized void write(int b) throws IOException { out.write(b); incCount(1); } /** * 调用out.write(byte[] b, int off, int len)将b的起始下标为off长度为len字节写入out中。 * 注意:这个方法的修饰符是synchronized、也就是这个方法完成之前不允许此类的别的方法执s行、 * 这样做的目的就是保证写入out中的字节的有序性、这样在我们取出来的时候才不会发生各种各样的问题。 * 同样、写完之后written + len。 */ public synchronized void write(byte b[], int off, int len) throws IOException { out.write(b, off, len); incCount(len); } /** * 将out中的数据flush到out指定的目的地中去。 */ public void flush() throws IOException { out.flush(); } /** * 将一boolean型数据写入out中、written + 1。 */ public final void writeBoolean(boolean v) throws IOException { out.write(v ? 1 : 0); incCount(1); } /** * 将一个字节写入out中、written + 1。 */ public final void writeByte(int v) throws IOException { out.write(v); incCount(1); } /** * 将一个short写入out、written + 2。 */ public final void writeShort(int v) throws IOException { out.write((v >>> 8) & 0xFF); out.write((v >>> 0) & 0xFF); incCount(2); } /** * 将一个char写入out、written + 2。 */ public final void writeChar(int v) throws IOException { out.write((v >>> 8) & 0xFF); out.write((v >>> 0) & 0xFF); incCount(2); } /** * 将一个int写入out、written + 4。 */ public final void writeInt(int v) throws IOException { out.write((v >>> 24) & 0xFF); out.write((v >>> 16) & 0xFF); out.write((v >>> 8) & 0xFF); out.write((v >>> 0) & 0xFF); incCount(4); } private byte writeBuffer[] = new byte[8]; /** * 将一个long写入out、written + 8。 */ public final void writeLong(long v) throws IOException { writeBuffer[0] = (byte)(v >>> 56); writeBuffer[1] = (byte)(v >>> 48); writeBuffer[2] = (byte)(v >>> 40); writeBuffer[3] = (byte)(v >>> 32); writeBuffer[4] = (byte)(v >>> 24); writeBuffer[5] = (byte)(v >>> 16); writeBuffer[6] = (byte)(v >>> 8); writeBuffer[7] = (byte)(v >>> 0); out.write(writeBuffer, 0, 8); incCount(8); } /** * 将一个float写入out。 */ public final void writeFloat(float v) throws IOException { writeInt(Float.floatToIntBits(v)); } /** * 将一个double写入out。 */ public final void writeDouble(double v) throws IOException { writeLong(Double.doubleToLongBits(v)); } /** * 将一个字符串转换成一串有序字节写入out * written + s.length(); */ public final void writeBytes(String s) throws IOException { int len = s.length(); for (int i = 0 ; i < len ; i++) { out.write((byte)s.charAt(i)); } incCount(len); } /** * 将一个字符串以一串有序字符写入out中 * written + s.lengt()*2; */ public final void writeChars(String s) throws IOException { int len = s.length(); for (int i = 0 ; i < len ; i++) { int v = s.charAt(i); out.write((v >>> 8) & 0xFF); out.write((v >>> 0) & 0xFF); } incCount(len * 2); } /** * 调用下方 writeUTF(String str, DataOutput out) 将一个字符串写入out。 */ public final void writeUTF(String str) throws IOException { writeUTF(str, this); } /** * 将一个字符串写入out。 */ static int writeUTF(String str, DataOutput out) throws IOException { int strlen = str.length(); int utflen = 0; int c, count = 0; // 由于UTF-8是1~4个字节不等; // 这里是根据UTF-8首字节的范围,判断UTF-8是几个字节的。 for (int i = 0; i < strlen; i++) { c = str.charAt(i); if ((c >= 0x0001) && (c <= 0x007F)) { utflen++; } else if (c > 0x07FF) { utflen += 3; } else { utflen += 2; } } if (utflen > 65535) throw new UTFDataFormatException( "encoded string too long: " + utflen + " bytes"); byte[] bytearr = null; if (out instanceof DataOutputStream) { DataOutputStream dos = (DataOutputStream)out; if(dos.bytearr == null || (dos.bytearr.length < (utflen+2))) dos.bytearr = new byte[(utflen*2) + 2]; bytearr = dos.bytearr; } else { bytearr = new byte[utflen+2]; } //字节数组前两个写入的是表示UTF-8字符串的长度、所以在写入字符串的时候会将str.length + 2 个字节写入out中。可以用写入str前后dos.size()验证一下。 bytearr[count++] = (byte) ((utflen >>> 8) & 0xFF); bytearr[count++] = (byte) ((utflen >>> 0) & 0xFF); int i=0; for (i=0; i<strlen; i++) { c = str.charAt(i); if (!((c >= 0x0001) && (c <= 0x007F))) break; bytearr[count++] = (byte) c; } for (;i < strlen; i++){ c = str.charAt(i); if ((c >= 0x0001) && (c <= 0x007F)) { bytearr[count++] = (byte) c; } else if (c > 0x07FF) { bytearr[count++] = (byte) (0xE0 | ((c >> 12) & 0x0F)); bytearr[count++] = (byte) (0x80 | ((c >> 6) & 0x3F)); bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F)); } else { bytearr[count++] = (byte) (0xC0 | ((c >> 6) & 0x1F)); bytearr[count++] = (byte) (0x80 | ((c >> 0) & 0x3F)); } } out.write(bytearr, 0, utflen+2); return utflen + 2; } /** * 返回当前out中已经写入的字节数、如果超过int范围则返回int最大值 */ public final int size() { return written; } }
数据字节输入流、java的IO体系相同作用的类之间有很强的对称性、这里我们可以很理所当然的想到、DataInputStream继承FilterInputStream、实现DataInput接口、FilterInputStream:过滤器字节输入流、所有字节输入流装饰类的父类、DataInput:对比DataOutput、脑海中立马闪现数据输入、定义了各种方法、可以分别将二进制流转换成java原始基础类型、作为DataInput的实现类DataInputStream、作用就是将使用DataOutputStream写入目的地中的java基础类型数据读取到程序中、并还原成java基础类型、我们可以直接在程序中使用。同样使用了装饰器、桥接两种设计模式。
DataInputStream(InputStream in); 通过传入的低级字节输入流构造dis
int read(byte b[]); 读取一个字节并以整数形式返回 int read(byte b[], int off, int len); 读取len个字节放入下标中off开始后的len个位置的字节数组b中、并返回读取的字节数 void readFully(byte b[]); 读取b.length个字节并以放入byte[] b 中 void readFully(byte b[], int off, int len) 读取len个字节放入下标中off开始后的len个位置的字节数组b中 int skipBytes(int n); 跳过pis中n个字节、返回实际跳过的字节数 boolean readBoolean(); 读取并返回一个boolean型数据 byte readByte(); 读取并返回一个字节型数据 int readUnsignedByte(); 读取并返回一个正整数形式表示的字节型数据 short readShort(); 读取并返回一个short型数据 int readUnsignedShort(); 读取并返回一个无符号型short数据 char readChar(); 读取并返回一个char型数据 int readInt(); 读取并返回一个int型数据 long readLong(); 读取并返回一个long型数据 float readFloat(); 读取并返回一个float型数据 double readDouble(); 读取并返回一个double型数据 String readUTF(); 读取并返回一个String型数据 static String readUTF(DataInput in); 读取in中一个字符串型数据并返回
package com.chy.io.original.code; import java.io.DataInput; import java.io.EOFException; import java.io.IOException; import java.io.UTFDataFormatException; /** * DataInputStrem 数据字节输入流、我们已经知道、FilterInputStream是一个装饰器、 * 他的子类就是为传入的InputStream子类添加新的功能、DataInputStrem作为FilterInputStream * 的一个实现类就是为传入的InputStream子类提供: * 应用程序将java的原始数据以一种与机器无关的方式读入、然后可以使用对应的DataOutputStream写入传入InputStream实现类自定的目的地中。 * */ public class DataInputStream extends FilterInputStream implements DataInput { java.io.DataInputStream /** * 调用父类FilterInputStream构造方法、在传入的InputStream实现类基础上创建DataInputStream; */ public DataInputStream(InputStream in) { super(in); } /** * readUTF方法使用的初始化数组 */ private byte bytearr[] = new byte[80]; private char chararr[] = new char[80]; /** * 调用传入的InputStream实现类(以下简称:in)的read(byte b ,int off, int len)方法读取字节数组; * 装饰器设计模式的本质:在保留组件原有功能的同时为现有被修饰的组件添加新功能; * 至于这里为什么不调用:in.read(byte[] b)、很简单、in.read(byte[] b)本身就是调用in.read(byte[]b ,int off, int len)来实现的、何必还要走弯路; */ public final int read(byte b[]) throws IOException { return in.read(b, 0, b.length); } /** * 调用in.read(byte[]b ,int off, int len)读取len个字节; */ public final int read(byte b[], int off, int len) throws IOException { return in.read(b, off, len); } /** *调用自己的 readFully(byte[] b,int off, int len)读取b.length个字节;为DataInput中定义的方法 */ public final void readFully(byte b[]) throws IOException { readFully(b, 0, b.length); } /** * 从in中读取len个字节、为DataInput中定义的方法 */ public final void readFully(byte b[], int off, int len) throws IOException { if (len < 0) throw new IndexOutOfBoundsException(); int n = 0; while (n < len) { int count = in.read(b, off + n, len - n); if (count < 0) throw new EOFException(); n += count; } } /** * 跳过in的前n个字节、并在以后的读取中不再读取这些字节。 */ public final int skipBytes(int n) throws IOException { int total = 0; int cur = 0; while ((total<n) && ((cur = (int) in.skip(n-total)) > 0)) { total += cur; } return total; } /** * 用于读取DataOut接口writeBoolean方法写入的字节。 */ public final boolean readBoolean() throws IOException { //boolean占一个字节 int ch = in.read(); if (ch < 0) throw new EOFException(); return (ch != 0); } /** * 用于读取DataOut接口writeByte()方法写入的字节、该字节被看作是 -128 到 127(包含)范围内的一个有符号值。 * 即返回的仍是一个byte、而非转换后的无符号的int、从结果的返回处理也能看出来。 */ public final byte readByte() throws IOException { int ch = in.read(); if (ch < 0) throw new EOFException(); return (byte)(ch); } /** * 读取一个输入字节,将它左侧补零 (zero-extend) 转变为 int 类型,并返回结果,所以结果的范围是 0 到 255。 * 如果接口 DataOutput 的 writeByte 方法的参数是 0 到 255 之间的值,则此方法适用于读取用 writeByte 写入的字节。 */ public final int readUnsignedByte() throws IOException { int ch = in.read(); if (ch < 0) throw new EOFException(); return ch; } /** * 读取两个输入字节并返回一个 short 值。 */ public final short readShort() throws IOException { int ch1 = in.read(); int ch2 = in.read(); if ((ch1 | ch2) < 0) throw new EOFException(); return (short)((ch1 << 8) + (ch2 << 0)); } /** * 读取两个输入字节,并返回 0 到 65535 范围内的一个 int 值。 * 如果接口 DataOutput 的 writeShort 方法的参数是 0 到 65535 范围内的值,则此方法适用于读取用 writeShort 写入的字节。 */ public final int readUnsignedShort() throws IOException { int ch1 = in.read(); int ch2 = in.read(); if ((ch1 | ch2) < 0) throw new EOFException(); return (ch1 << 8) + (ch2 << 0); } /** * 读取两个输入字节并返回一个 char 值。 */ public final char readChar() throws IOException { int ch1 = in.read(); int ch2 = in.read(); if ((ch1 | ch2) < 0) throw new EOFException(); return (char)((ch1 << 8) + (ch2 << 0)); } /** * 读取四个输入字节并返回一个int 值 */ public final int readInt() throws IOException { int ch1 = in.read(); int ch2 = in.read(); int ch3 = in.read(); int ch4 = in.read(); if ((ch1 | ch2 | ch3 | ch4) < 0) throw new EOFException(); return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); } private byte readBuffer[] = new byte[8]; /** * 读取8个输入字节并返回一个long 值 */ public final long readLong() throws IOException { readFully(readBuffer, 0, 8); return (((long)readBuffer[0] << 56) + ((long)(readBuffer[1] & 255) << 48) + ((long)(readBuffer[2] & 255) << 40) + ((long)(readBuffer[3] & 255) << 32) + ((long)(readBuffer[4] & 255) << 24) + ((readBuffer[5] & 255) << 16) + ((readBuffer[6] & 255) << 8) + ((readBuffer[7] & 255) << 0)); } /** * 读取四个字节并返回一个float 值 */ public final float readFloat() throws IOException { return Float.intBitsToFloat(readInt()); } /** * 读取8个字节并返回一个double值 */ public final double readDouble() throws IOException { return Double.longBitsToDouble(readLong()); } /** * 调用下方的readUTF(DataInut in) */ public final String readUTF() throws IOException { return readUTF(this); } /** * 读取输入流中的一串有序字节字节、并转换成String返回 */ public final static String readUTF(DataInput in) throws IOException { // 从“数据输入流”中读取“无符号的short类型”的值: // 从DataOutputStream 的 writeUTF(String str)方法知道此方法读取的前2个字节是数据的长度 int utflen = in.readUnsignedShort(); byte[] bytearr = null; char[] chararr = null; if (in instanceof DataInputStream) { DataInputStream dis = (DataInputStream)in; if (dis.bytearr.length < utflen){ dis.bytearr = new byte[utflen*2]; dis.chararr = new char[utflen*2]; } chararr = dis.chararr; bytearr = dis.bytearr; } else { bytearr = new byte[utflen]; chararr = new char[utflen]; } int c, char2, char3; int count = 0; int chararr_count=0; in.readFully(bytearr, 0, utflen); while (count < utflen) { c = (int) bytearr[count] & 0xff; if (c > 127) break; count++; chararr[chararr_count++]=(char)c; } while (count < utflen) { c = (int) bytearr[count] & 0xff; switch (c >> 4) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: /* 0xxxxxxx*/ count++; chararr[chararr_count++]=(char)c; break; case 12: case 13: /* 110x xxxx 10xx xxxx*/ count += 2; if (count > utflen) throw new UTFDataFormatException( "malformed input: partial character at end"); char2 = (int) bytearr[count-1]; if ((char2 & 0xC0) != 0x80) throw new UTFDataFormatException( "malformed input around byte " + count); chararr[chararr_count++]=(char)(((c & 0x1F) << 6) | (char2 & 0x3F)); break; case 14: /* 1110 xxxx 10xx xxxx 10xx xxxx */ count += 3; if (count > utflen) throw new UTFDataFormatException( "malformed input: partial character at end"); char2 = (int) bytearr[count-2]; char3 = (int) bytearr[count-1]; if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) throw new UTFDataFormatException( "malformed input around byte " + (count-1)); chararr[chararr_count++]=(char)(((c & 0x0F) << 12) | ((char2 & 0x3F) << 6) | ((char3 & 0x3F) << 0)); break; default: /* 10xx xxxx, 1111 xxxx */ throw new UTFDataFormatException( "malformed input around byte " + count); } } // The number of chars produced may be less than utflen return new String(chararr, 0, chararr_count); } /** * 此方法已废弃、不再过多关注 */ public String readLine() throws IOException { return null; } }
package com.chy.io.original.test; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; /** * DataInputStream、DataOutputStream测试类。基本涵盖这两个类的所有方法。 * @author andyChen * @version 1.1, 13/11/10 */ public class DataStreamTest { private final static byte[] byteArray = {0x61, 0x62, 0x63, 0x64, 0x65}; private static byte[] buff = new byte[5]; private static DataOutputStream dos; private static DataInputStream dis; /** * DataOutputStream 测试:将不同类型的java基础数据写入out中。 */ public static void testDataOutputStream() throws IOException{ dos = new DataOutputStream(new FileOutputStream(new File("D:\\dos.txt"))); dos.write(0x61); dos.write(byteArray, 0, 3); dos.write(byteArray); dos.writeBoolean(false); dos.writeByte(0x61); dos.writeShort(32766); dos.writeChar("c".charAt(0)); dos.writeInt(214783647); System.out.println(dos.size()); dos.writeFloat(5.12F);//一个float类型数据占4个字节 dos.writeDouble(55.55); System.out.println(dos.size()); dos.writeUTF("e"); System.out.println(dos.size()); dos.writeUTF("a陈画b");//这里的一个汉字占3个字节、外加两个表示字符串长度的字节 System.out.println(dos.size()); dos.writeLong(1L); System.out.println("dos 总字节数:" + dos.size()); dos.close(); } /** * * DataInputstream 测试:这里要注意:怎么用DataOutputStream写入的、就要怎么读出来、即读取类型的顺序要和写入类型的顺序一致、 * 原因:因为DataoutputStream将java基本类型写入out中时、是先把基本类型转换成一定顺序的字节写入的、 * DataInputStream读取的也是根据不同读取方法读取不同个数的字节、再转换成相应的类型返回。顺序不一致转换结构就很可能不是我们想要的。 */ public static void testDataInputStream() throws IOException{ //创建以FileInputStream为基础流的dis; dis = new DataInputStream(new FileInputStream(new File("D:\\dos.txt"))); System.out.println(byteToString((byte)dis.read())); //System.out.println(dis.readUnsignedByte());与上面方法相比、这个方法是读取字节的无符号形式、即将读取的byte左侧补零返回0 - 255 之间的整数。 System.out.println("有效字节数:" + dis.available()); dis.readFully(buff, 0, 3); printByteValue(buff); dis.readFully(buff); printByteValue(buff); System.out.println(dis.readBoolean()); System.out.println(byteToString(dis.readByte())); System.out.println(dis.readShort()); // System.out.println(dis.readUnsignedShort());与readUnsignedByte()相同读取的是无符号、并且将左侧补零。 System.out.println(dis.readChar()); System.out.println(dis.readInt()); System.out.println(dis.readFloat()); System.out.println(dis.readDouble()); /** * 这里在使用之前有个困惑:这个方法是怎么知道我要读的这个字符串是哪个?是多长?如果我连续写入两个字符串会不会一起读出来? * 很明显、从打印结果可以看出:程序会按照顺序读取你想要读取的那一个字符串、不会多读、也不会少读、 * 那程序是怎么识别的?也就是说程序是如何分割每个字符串的? * 解铃还须系铃人:DataOutputStream源码中的writeUTF(String str)方法、在写入真正的String str之前会先写入两个字节、用来表示这个字符串的长度。 * 到这里就基本明白了:当DataInputSrteam的readUTF()方法开始读取字符串时、首先读到的是前两个表示这个字符串长度的字节、然后读取这个长度的字节转换成str。 */ System.out.println(dis.readUTF()); System.out.println(dis.readUTF()); System.out.println(dis.readLong()); } public static void testDataInputStreamSkipByte() throws IOException{ //使用这个方法之前要对dos写入每个类型时、这个类型在dos指定的目的地中占多少个字节要掌握。尤其是写入字符串时、会多写入两个字节来表示字符串的长度! dis = new DataInputStream(new FileInputStream(new File("D:\\dos.txt"))); dis.skip(1);//FileInputStream 本身的skip函数 System.out.println(dis.available()); dis.read(buff, 0, 3); printByteValue(buff); dis.skipBytes(5);//DataInputStream自己新增的函数 System.out.println(dis.readBoolean()); //这里跳的超过dos总有效字节后、会取负、ByteArrayInputStream返回的是非负数 count - pos; dis.skip(dos.size() + 1); System.out.println(dis.available()); } public static void main(String[] args) throws IOException { testDataOutputStream(); testDataInputStream(); //testDataInputStreamSkipByte(); } private static void printByteValue(byte[] buf) { for(byte b : buf){ if(b != 0){ System.out.print(byteToString(b) + " "); } } System.out.println(); } private static String byteToString(byte b){ byte[] bAray = {b}; return new String (bAray); } }
DataInputStream、DataOutputStream两个装饰类的功能就是装饰底层字节流、使得java基础数据可以在存储介质和程序中以一种与机器无关的方式进行传递、DataOutputStream的核心是先将java基础类型的数据根据一定的规则转换成同一的二进制流、再调用传入的底层输出流的write(byte b) 、write(byte[] b, int off, int len)方法将此二进制流写入到dos中进而保存到存储介质中取、DataInputStream的核心是调用传入的底层字节输入流的read(byteb ) 、read(byte[] b,int off, int len)将源中通过dos写入的java基础数据的字节表示形式的数据以字节的形式读取到程序中、然后根据一定的转换规则将这些字节还原成相应的java基础类型的数据、比如boolean型的数据是写如一个字节“0”或者“1”用于表示“true”或者“false”那读取到“0”的话就返回“true”这种表示形式、
值得注意的是:DataInputStream读取的字节是要按照DataOutputStream写入字节的顺序来读取、所以不能乱读取字节、要做到正确使用这两个类、最稳妥的就是将读取数据的类型顺序和写入数据的类型的顺序一致、当我们需要使用skipBytes方法的时候、要很明确的知道当调用DataOutputStream写入java基础类型的时候、每种基础类型在目的地中占多少个字节、跳过多少个字节之后下一个或一组字节表示的是哪个java基础类型?这里要很清楚才能正确使用这两个类、当然、一般我们使用的时候就是怎么样写入的就怎么样读取出来就行。而且并不是所有的方法都会经常用到。将常用的熟练掌握、不常用的用的时候能想起来查API就ok、
还有一点、就是当时我的疑问?当我写入多个String的时候、按顺序读取的话、还是可以将他们分别读取出来、我并没有传递任何参数表示我要读取哪个字符串、程序是如何知道将这些不是同一次写入的字符串区分开来的?原来:当DataOutputStream写入字符串的时候会多些两个字节、这两个字节是用来表示将要写入的字符串的长度的、所以当我们使用DataInputStream读取字符串的时候、首先读取的是接下来要读取的字符串的长度、然后他就会读取这些字节之后停止读取、将读取的结果转换成字符串返回。