(本人新手,如果有误,请不吝赐教)
首先,讲下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));
}
}
结果正如我们所期望的:
(注:这是一篇学习性质博客,我废话可能多了一点,我只想尽可能把我的学习过程展示出来)