谈谈Java中使用DataInputStream与DataOutputStream的雷区
(本人新手,如果有误,请不吝赐教)
首先,讲下DataInputStream、DataOutputStream这两个类,它们是对输入输出流的再一层封装,封装了一些按照一定格式读取或写入指定类型的数据,比如今天要讲的readInt(),它将从输入流中读取4个字节并返回一个int类型的数据。
今天之所以写这篇文章,是因为今天我在读取一张bmp图片时候遇到的问题。下面贴代码(C代码)
FILE *f; //文件指针 fopen_s(&f,"C:\\Users\\IVAN\\Desktop\\1.bmp", "r"); //由于安全检查只能使用这个API,原型是fopen if (f == NULL) //判断是否打开 return 0; fseek(f, 0x0012, SEEK_SET); //偏移18位,接下来就是这张图片的长度和宽度,想了解的可以去研究下bmp图片的编码 int height,width; fread(&width, sizeof(int), 1, f); //读取宽度 fread(&height, sizeof(int), 1, f);//读取长度 printf("width %d,height %d\n", width, height);//打印 fclose(f); //释放
这段代码作用是读取一张名为1.bmp的图片长度及宽度。
下面java的代码:
public static void main(String[] args) throws IOException { File file = new File("C:\\Users\\IVAN\\Desktop\\1.bmp"); FileInputStream fin = new FileInputStream(file); byte[] bytes = new byte[(int) file.length()]; fin.skip(0x0012); //以上操作就是打开输入流,并偏移18位 DataInputStream din = new DataInputStream(fin);//今天讨论的对象 int weight = din.readInt(); //使用readInt()读入一个int类型的数据,也是本次讨论的重点 System.out.println("weight " + weight); fin.close(); }
以上两段代码结果是不同的(并不一定是不同的,原因后面会讲),以下是结果截图(C程序输出的结果是正确的):
为什么会不同呢?我们在学习c的时候,老师或者考试一定都有问过int的长度是16或者32位的(具体大小跟你使用的机器和编译器有关),当我们在c里面调用sizeof(int)会发现这个API返回的值是4,其实这个4代表的是一个int型的变量占用4个字节(byte)。为什么要扯这个问题呢?其实今天的重点就是机器是如何把这4个字节转化成int类型的数据。先看看下面的两段源码:
//DataOutputStream.java ... /** * Writes an <code>int</code> to the underlying output stream as four * bytes, high byte first. If no exception is thrown, the counter * <code>written</code> is incremented by <code>4</code>. * * @param v an <code>int</code> to be written. * @exception IOException if an I/O error occurs. * @see java.io.FilterOutputStream#out */ 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); } ... //DataInputStream.java ... /** * See the general contract of the <code>readInt</code> * method of <code>DataInput</code>. * <p> * Bytes * for this operation are read from the contained * input stream. * * @return the next four bytes of this input stream, interpreted as an * <code>int</code>. * @exception EOFException if this input stream reaches the end before * reading four bytes. * @exception IOException the stream has been closed and the contained * input stream does not support reading after close, or * another I/O error occurs. * @see java.io.FilterInputStream#in */ 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)); } ...
当读取int类型的数据时,我们先找到这个数据的地址,读取长度为4个字节的数据,将其****做处理****后就得到了我们想要的数据。为什么使用DataInputStream的readInt()方法得到了不正确的结果呢?
重点就在"做处理"这个步骤,DataInputStream/DataOutputStream固定按照[b1][b2][b3][b4]的顺序输入输出,这样做并没有问题,并且保证使用DataOutputStream/DataInputStream的输出输入得到的数据是一致的,但是从最开始的那两个例子来看,结果并不正确。
原因在于我运行c代码时,是直接将数据写入内存,虽然int数据类型存放空间长度是一致的,但是存放的顺序却是相反的。这个过程是由CPU决定的。
大多数计算机按正向顺序存储一个数,Intel CPU按逆向顺序存储一个数,因此,如果试图将基于Intel CPU的计算机连到其它类型的计算机上,就可能会引起混乱。
一个32位的数占4个字节的存储空间,如果我们按有效位从高到低的顺序,分别用Mm,Ml,Lm和Ll表示这4个字节,那么可以有4!(4的阶乘,即24)种方式来存储这些字节。在过去的这些年中,人们在设计计算机时,几乎用遍了这24种方式。然而,时至今天,只有两种方式是最流行的,一种是(Mm,MI,Lm,LD,也就是高位优先顺序,另一种是(Ll,Lm,Ml,Mm),也就是低位优先顺序。和存储16位的数一样,大多数计算机按高位优先顺序存储32位的数,但基于Intel CPU的计算机按低位优先顺序存储32位的数。
"http://blog.sina.com.cn/s/blog_9e2e84050101dipx.html"
到现在可以解释为什么之前例子的结果可能是不同的。下面是一段用于验证的C代码:
int show; char buf[4] = {80,0,0,0}; memcpy_s(&show, sizeof(int),buf,4); printf("低位为80结果 %d\n", show); char buf2[4] = { 0,0,0,80 }; printf("高位为80结果 %d\n", *(int*)buf2); system("pause");
我们可以对之前的java代码做出改造:
import java.io.*; public class Main { public static void main(String[] args) throws IOException { File file = new File("C:\\Users\\IVAN\\Desktop\\1.bmp"); FileInputStream fin = new FileInputStream(file); byte[] bytes = new byte[(int) file.length()]; fin.skip(0x0012); DataInputStream din = new DataInputStream(fin); int weight = readInt(din); System.out.println("weight " + weight); fin.close(); } public static int readInt(InputStream in) throws IOException { int ch4 = in.read(); int ch3 = in.read(); int ch2 = in.read(); int ch1 = in.read(); if ((ch1 | ch2 | ch3 | ch4) < 0) throw new EOFException(); return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0)); } }
结果正如我们所期望的: