FileInputStream и DataOutputStream - обработка буфера byte [] - PullRequest
0 голосов
/ 06 января 2019

Я работал над приложением для перемещения файлов между двумя хостами, и пока я начал процесс переноса (код все еще очень грязный, так что извините за это, я все еще его исправляю). как именно он обрабатывает буфер. Я довольно новичок в работе с сетями в 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);
    }

}

Я хотел бы знать, как именно обрабатываются буферы с обеих сторон, чтобы избежать записи неправильных байтов. (я знаю, как получение кода избегает этого, но я все же хотел бы знать, как обрабатывается байтовый массив)

Всегда ли fis / dis ожидает полного заполнения буферов? При получении кода он всегда записывает полный массив или оставшуюся длину, если он меньше, чем filebuffer.length, но как насчет отправки кода.

1 Ответ

0 голосов
/ 06 января 2019

На самом деле ваш код может содержать небольшую ошибку, именно из-за того, как вы обрабатываете буферы.

Когда вы читаете буфер из исходного файла, метод 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. Не обрезай это так. Оставьте remaining как long и измените его на int только после того, как вы взяли минимум (потому что вы знаете, что минимум всегда будет в диапазоне int).

long remaining = longsize;
long fileBufferLen = filebuffer.length;

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

Кстати, нет реальной причины использовать для этого DataOutputStream и DataInputStream. read(byte[]), read(byte[],int,int), write(byte[]) и write(byte[],int,int) наследуются от базового InputStream, и нет никаких оснований не использовать сокет OutputStream / InputStream напрямую или использовать BufferedOutputStream / BufferedOutputStream чтобы обернуть его. Также нет необходимости использовать flush, пока вы не закончите писать / читать.

Кроме того, не забудьте, по крайней мере, закрыть потоки ввода / вывода файлов, когда вы закончите с ними. Возможно, вы захотите оставить потоки ввода / вывода сокетов открытыми для продолжения связи, но нет необходимости держать сами файлы открытыми, это может вызвать проблемы. Используйте попытку с ресурсами, чтобы гарантировать, что они закрыты.

...