当前位置: 移动技术网 > IT编程>开发语言>Java > 谈谈Java中使用DataInputStream与DataOutputStream的雷区

谈谈Java中使用DataInputStream与DataOutputStream的雷区

2018年05月19日  | 移动技术网IT编程  | 我要评论

(本人新手,如果有误,请不吝赐教)

首先,讲下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程序的结果
java程序的结果


为什么会不同呢?我们在学习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位的数。
""

到现在可以解释为什么之前例子的结果可能是不同的。下面是一段用于验证的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));
    }

}

结果正如我们所期望的:

这里写图片描述

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网