有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java FileInputStream和DataOutputStream处理字节[]缓冲区

我一直在开发一个在两台主机之间移动文件的应用程序,当我让传输过程正常工作时(代码仍然很混乱,很抱歉,我还在修复它),我有点想知道它到底是如何处理缓冲区的。我对java的网络相当陌生,所以我不想以“嗯,我成功了,让我们继续”的态度结束

文件发送代码

    public void sendFile(String filepath, DataOutputStream dos) throws Exception{
    if (new File(filepath).isFile()&&dos!=null){
        long size = new File(filepath).length();
        String strsize = Long.toString(size) +"\n";
        //System.out.println("File size in bytes: " + strsize);
        outToClient.writeBytes(strsize);
        FileInputStream fis = new FileInputStream(filepath);
        byte[] filebuffer = new byte[8192];

        while(fis.read(filebuffer) > 0){
            dos.write(filebuffer);
            dos.flush();
        }

文件接收代码

   public void saveFile() throws Exception{
    String size = inFromServer.readLine();
    long longsize = Long.parseLong(size);
    //System.out.println(longsize);
    String tmppath = currentpath + "\\" + tmpdownloadname;
    DataInputStream dis = new DataInputStream(clientSocket.getInputStream());
    FileOutputStream fos = new FileOutputStream(tmppath);
    byte[] filebuffer = new byte[8192];
    int read = 0;
    int remaining = (int)longsize;
    while((read = dis.read(filebuffer, 0, Math.min(filebuffer.length, remaining))) > 0){
        //System.out.println(Math.min(filebuffer.length, remaining));
        //System.out.println(read);
        //System.out.println(remaining);
        remaining -= read;
        fos.write(filebuffer,0, read);
    }

}

我想知道如何准确地处理两侧的缓冲区,以避免写入错误的字节。(ik接收代码如何避免这种情况,但我仍然想知道字节数组是如何处理的)

fis/dis是否总是等待缓冲区完全填满?在接收代码时,如果小于filebuffer,它总是写入完整数组或剩余长度。但发送代码的fis的长度如何


共 (1) 个答案

  1. # 1 楼答案

    事实上,你的代码可能有一个微妙的错误,正是因为你处理缓冲区的方式

    从原始文件读取缓冲区时,read(byte[])方法返回实际读取的字节数。实际上,无法保证所有8192字节都已被读取

    假设你有一个10000字节的文件。第一次读取操作读取8192字节。但是,第二次读取操作将只读取1808字节。第三个操作将返回-1

    在第一次读取时,您会准确地写入已读取的字节,因为您读取的缓冲区已满。但在第二次读取中,缓冲区实际上包含1808个正确的字节,而剩余的6384个字节是错误的——它们仍然存在于前一次读取中

    在这种情况下,您是幸运的,因为这只发生在您编写的最后一个缓冲区中。因此,当达到预先发送的长度时,您停止在客户端读取,这会导致您跳过那些本来不应该发送的6384个错误字节

    但事实上,即使尚未到达结尾,也无法保证从文件中读取将返回8192字节。该方法的契约不能保证这一点,这取决于操作系统和底层文件系统。例如,它可以在第一次读取时发送5000字节,在第二次读取时发送5000字节。在这种情况下,您将在文件中间发送3192个错误字节。

    因此,您的代码实际上应该如下所示:

    byte[] filebuffer = new byte[8192];
    int read = 0;
    while(( read = fis.read(filebuffer)) > 0){
        dos.write(filebuffer,0,read);
        dos.flush();
    }
    

    与接收端的代码非常相似。这保证了只写入实际读取的字节

    所以缓冲区的处理方式并没有什么神奇之处。给流一个缓冲区,告诉它允许填充多少缓冲区,但不能保证它会填充所有缓冲区。它可能会填充得更少,而你必须小心使用它告诉你它填充的部分

    不过,您正在犯的另一个严重错误是,将您收到的long转换为这一行中的int

    int remaining = (int)longsize;
    

    文件可能比整数包含的长度长。尤其是长视频等。这就是为什么你首先会得到一个long的数字。不要那样截断它。保持remaininglong,并在取最小值后将其更改为int(因为你知道最小值总是在^{的范围内)

    long remaining = longsize;
    long fileBufferLen = filebuffer.length;
    
    while((read = dis.read(filebuffer, 0, (int)Math.min(fileBufferLen, remaining))) > 0){
        ...
    }
    

    顺便说一句,没有真正的理由为此使用DataOutputStreamDataInputStreamread(byte[])read(byte[],int,int)write(byte[])write(byte[],int,int)是从底层InputStream继承的,没有理由不直接使用套接字的OutputStream/InputStream,或者使用BufferedOutputStream/BufferedOutputStream来包装它。在完成写作/阅读之前,也不需要使用flush

    此外,在完成文件输入/输出流时,不要忘记至少关闭它们。您可能希望保持套接字输入/输出流处于打开状态,以便继续通信,但不需要保持文件本身处于打开状态,这可能会导致问题。利用资源进行尝试,以确保它们已关闭