数据流DataInput(Output)Stream 和 字节数组流 ByteArrayInput(Output) Stream

来源:互联网 发布:p2p网络借贷法律规定 编辑:程序博客网 时间:2024/05/01 00:43


一, 1个网络传输模型

在一个网络传输模型中.


假如1个电脑A想把1个double类型的值12345.678 传给另1个网络另一端的电脑B. 

要如何实现呢?


大概分成几个步骤.


1. 转为字节数组(电脑A).

无论网络上两个终端要传输什么类型的数据, 实际在网线上传输的都是2进制数据(bit).

所以电脑A是不能直接把1个double类型传送出去的.   必须将double类型的值12345.678的值转换为字节类型(16个bit).


将1个double类型转换为字节类型并不简单, 这设计编码的问题, 但是java能把这些都封装好了.

而1个double类型的数据并不是1个字节就能存放的, double类型长度是64位啊, 所以就需要1个64位长度的字节数组才能完整存放1个float类型的数据.


有人问,既然网络上传送的是位(bit), 为何不直接把变量转化为bit类型.

答案很简单, 绝大部分计算机的内存的最小单位(1个地址)是8 bit,   所以大部分编程语言, 例如c/c++, java等所有变量最小长度就是8bit(1个字节).


所以无论传输什么类型的变量, 都需要把变量的数据转为字节数据, 然后存放在1个字节数组中.


2. 将字节数组打包(电脑A).

为了尽量减少网络上传输的数据大小,  我们通常要把要传输的字节数组打成1个压缩数据包. 本文不会详细讨论这点.


3. 传输数据包(网络).

这样, 在网络上传输的实际就是数据包了.   本文不会详细讨论这点.


4. 解压数据包得到字节数组(电脑B)

电脑B 接受到数据包后, 就需要解压数据包得到1里面封装的字节数组. 本文不会详细讨论这点.


5. 将字节数组里的数据转换为原始类型(double)(电脑B)

电脑B 最后还要把该字节数组的数据转换为原来的类型double, 才真正得到由电脑A传过来的float类型的值.




如下图





二, 将1个double变量转化为1个字节数组

上一节提过了, 本文的重点在于基本变量和字节数组的转换.

有人觉得数据类型的转化很基础, 但是实际上java并没有提供1个封装好的方法, 例如 xxx.toByteArray(Double d) 就返回1个字节数组.. 除非你自己写.


致使是double基本类型的封装类Double, 也没有提供转换为ByteArray的方法.

Double类里有1个类似的byteValue(), 但是返回的只是1个Byte, 实际上只取double变量的低8位. 高位的字节数据都被截掉了..


自己写1个int类型转换为1个ByteArray是可行的, 因为我们知道Java中int类型的编码是补码, 但是要用到位运算.

例如:

public static byte[] intToBytes2(int n){      byte[] b = new byte[4];      for(int i = 0;i < 4;i++){          b[i] = (byte)(n >> (24 - i * 8));       }      return b;  }  


但是double的编码远比int复杂, 必须要用另外的方法来编写.

而且这样的话违背了Java的最大优势和本质:

Java让程序猿focus on business, 并不需要关心数据在内存里是怎样存储的.



有没有一种方法, 可以接受各种基本类型参数, 转换为字节数组? 上面说过了, Java没有提供.


但是Java提供两个流, 它们嵌套使用的话, 就相当于实现了上面所说的功能, 将各种基本类型转换为字节数组.

它们就是DataOutputStream 和 ByteArrayOutputStream


2.1 ByteArrayOutputStream

我们知道FlieOutputStream是1个指向文件的的字节流,  同样地, ByteArrayOutputStream 也可以理解为1个指向1个ByteArray的字节流.

我们可以同个这个流添加字节数据到1个字节数组.



2.1.1 构造方法

有必要提提这个ByteArrayOutputStream的构造方法.

既然是指向ByteArray的流, 必然要有1个core 字节数组(目标数组).


例如FileOutputStream

有1个构造方法是

new FileOutputStream(File f)

作为参数,我们必须制定文件输出字节流的目标文件

而对于ByteArrayOutputStream,

没有

new ByteArrayOutputStream(byte[] b)

也就是讲我们不能指定数组ouput字节流的目标字节数组.


实际上, 对于ByteArrayOutputStream来讲,目标字节数组是builded in的, 也就是它对于程序猿是隐藏的.


但是它提供了另1个方法

toByteArray() , 这个方法new了1个新的字节数组对象, 然后把隐藏的目标字节数组的数据复制到的这个新的字节数组内.

也就是说返回1个隐藏字节数组的副本.


而这个流提供了另1个构造方法

new ByteArrayOutputStream(int bufferSize)

参数只能用于指定缓冲区大小.




具体看看本节的例子.


2.1.2 write方法


ByteArrayOutputStream 的成员方法 和 FileOutputStream 很类似.

都具有

write(int)  写1个字节

write(byte[]) 写1个字节数组

函数


但根据本文, 很明显我们需要的是 write(double)这个方法, 但是 ByteArrayOutputStream只是1个原始的字节流, 并没有提供.

所以我们需要1个额外的包裹流.


2.2 DataOutputStream

跟上面那个流不同,  DataOutputStream不能理解为指向"Data"的字节流.

实际上DataOutputStream是处理流(包裹流)的一种, 必须基于1个原始流之上.


DataOutputStream 提供的方法就强大得多了.

例如:

writeFloat(float f)   写入1个float到流中.

writeLong(long l)   写入1个long类型到流中

writeDouble(double d)  写入1个double类型到流中

....


可见, DataOutputStream实际提供了各种将基本类型写入到字节流的1种渠道. 包裹ByteArrayOutputStream的话就能转换为字节数组.

而writeDouble(double d)方法正是我们需要的.




2.3 1个把double对象转换为字节数组的例子

   private static byte[] Send(double f) throws IOException{        byte[] bArr = new byte[64];        //the core byteArray is built in. and the 1024 is buffer size        ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);        DataOutputStream dos = new DataOutputStream(bos);        dos.writeDouble(f);//this method will create a new byte[] object        bArr = bos.toByteArray();        dos.close(); // bos will be cloased cascade        return bArr;     }

上面的send方法就是接受1个double f,并返回对应的字节数组.

这个就是上面模型电脑A在第一步完成的事情


三, 从字节数组内提取1个double类型

这个步骤是上面模型的电脑B的最后一步, 实际上就是上面第二节的逆行为.

那么到底如何从网络上接收到的字节数组提取1个double类型?


同样地, java也没有提供现成的方法.


道理是样的,

我们一样可以利用DataInputStream 和 ByteArrayInputStream 来见建立一条从字节数组到程序的输入字节流.


如果弄懂了上面第二节(Output), 那么这个Input的原理也不难理解. 把write替换成read就ok了.


下面例子就是把 1个字节数组转为double类型的方法:

    private static double Get(byte[] bArr) throws IOException{        double f;        ByteArrayInputStream bis = new ByteArrayInputStream(bArr);        DataInputStream dis = new DataInputStream(bis);        f = dis.readDouble();        dis.close();        return f;    }


四, 结合上面两个方法的简单程序

下面程序是这样的.

就是模拟本文一开始的模型,


一段发送1个double, 利用Send方法转换为 字节数组, 然后利用Get方法还原为double类型.

最后输出还原后的double类型..

import java.io.*;public class DataStream1{    public static void f(){        double fi = 12345.678;        double fo = 0;        byte[] bSend;        byte[] bGet;        try{            bSend = Send(fi);            //network.........            bGet = bSend;            fo = Get(bGet);        }catch(IOException e){            e.printStackTrace();        }                System.out.printf("fo is %f\n",fo);     }    private static byte[] Send(double f) throws IOException{        byte[] bArr = new byte[64];        //the core byteArray is built in. and the 1024 is buffer size        ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);        DataOutputStream dos = new DataOutputStream(bos);        dos.writeDouble(f);//this method will create a new byte[] object        bArr = bos.toByteArray();        dos.close(); // bos will be cloased cascade        return bArr;     }    private static double Get(byte[] bArr) throws IOException{        double f;        ByteArrayInputStream bis = new ByteArrayInputStream(bArr);        DataInputStream dis = new DataInputStream(bis);        f = dis.readDouble();        dis.close();        return f;    }}

输出:

gateman@TPEOS Java_1 $ antBuildfile: /media/store1/Studies/Java/java_start/Java_1/build.xmlmain:    [javac] /media/store1/Studies/Java/java_start/Java_1/build.xml:10: warning: 'includeantruntime' was not set, defaulting to build.sysclasspath=last; set to false for repeatable builds    [javac] Compiling 1 source file to /media/store1/Studies/Java/java_start/Java_1/build/classes     [java] fo is 12345.678000BUILD SUCCESSFULTotal time: 1 second
















1 0
原创粉丝点击