FileInputStream 和 DataOutputStream - 处理 byte[] 缓冲区

FileInputStream and DataOutputStream - handling byte[] buffer

我一直在开发一个在两个主机之间移动文件的应用程序,当我的传输过程开始工作时(代码仍然非常混乱,对此很抱歉,我仍在修复它)我有点想知道它究竟是如何处理缓冲区的。我对 java 的网络还很陌生,所以我不想以 "meh i got it to work so let's move on" 的态度结束。

文件发送代码。

    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);
    }

}

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

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

事实上,您的代码可能有一个细微的错误,这完全是因为您处理缓冲区的方式。

当您从原始文件读取缓冲区时,read(byte[]) 方法 returns 实际读取的字节数。不能保证实际上已读取所有 8192 个字节。

假设您有一个 10000 字节的文件。您的第一个读取操作读取 8192 个字节。但是,您的第二个读取操作只会读取 1808 个字节。第三个操作将return-1.

在第一次读取中,您写入的字节与您读取的字节完全相同,因为您读取了一个完整的缓冲区。但是在第二次读取时,您的缓冲区实际上包含 1808 个正确字节,而剩余的 6384 个字节是错误的 - 它们仍然存在于上一次读取中。

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

但事实上,并不能真正保证从文件中读取 return 8192 字节,即使尚未到达末尾也是如此。该方法的合同不保证这一点,它取决于 OS 和底层文件系统。例如,它可以在您第一次读取时向您发送 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 的原因。不要那样截断它。将 remaining 保持为 long 并将其更改为 int 只有 你已经取了最小值(因为你知道最小值总是在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,没有理由不用socket的OutputStream /InputStream 直接,或使用 BufferedOutputStream/BufferedOutputStream 包装它。在完成 writing/reading.

之前也不需要使用 flush

此外,不要忘记在完成后至少关闭文件 input/output 流。您可能希望保持套接字 input/output 流打开以继续通信,但没有必要保持文件本身打开,这可能会导致问题。使用 try-with-resources 来保证它们是关闭的。