2016-01-30 4 views
1

Я пытаюсь работать с функцией передачи файлов для моего приложения. Моя реализация передачи файлов заключается в том, чтобы отправить файл по частям в виде объектов, содержащих информацию о файле, а также отправленные байты. Тем не менее, я заметил, что могу писать только в файл, если я сохраню все полученные байты в списке, а затем сразу же напишу его в файл. Если я попытаюсь записать файл по частям, он попадет в пустой файл, как если бы файл вообще не был написан.FileOutputStream по частям не работает

Вот мой метод, который читает исходный файл, а затем отправляет его по частям:

public void sendFile(File src) { 
    try { 

     BufferedInputStream is = new BufferedInputStream(new FileInputStream(src)); 
     Message msg = new Message(MType.FILE_OPEN, true); 
     com.transmit(msg); 
     byte[] buf = new byte[Utility.bufferSize]; 
     msg = new Message(MType.FILE_NAME, src.getName()); 
     msg.setValue(MType.FILE_SIZE, Files.size(src.toPath())); 
     com.transmit(msg); 
     for (int count = is.read(buf); count > 0; count = is.read(buf)) { 

      msg = new Message(MType.FILE_NAME, src.getName()); 
      msg.setValue(MType.FILE_SIZE, Files.size(src.toPath())); 
      msg.setValue(MType.FILE_BYTE, buf); 
      msg.setValue(MType.FILE_COUNT, count); 
      com.transmit(msg); 

     } 
     msg = new Message(MType.FILE_NAME, src.getName()); 
     msg.setValue(MType.FILE_SIZE, Files.size(src.toPath())); 
     msg.setValue(MType.FILE_CLOSE, true); 
     is.close(); 
     com.transmit(msg); 

    } catch (IOException e) { 
     sender.getChatRoomController().error(ProgramError.ATTACH_FILE); 
     Utility.log(e); 
     e.printStackTrace(); 
    } 
} 

Вот мой метод, который принимает сообщение объекты на другом конце:

public void readFile(Message msg) { 

    if (msg.hasID(MType.FILE_NAME)) { 
     String name = msg.getValue(MType.FILE_NAME).toString(); 
     long size = (long) msg.getValue(MType.FILE_SIZE); 
     File file = new File(Directories.fDir.getDirectory(), name); 
     TempFile tf = new TempFile(file); 
     if (!map.containsKey(file)) { 
      if (!file.exists()) { 
       try { 
        file.createNewFile(); 
       } catch (IOException e) { 
        e.printStackTrace(); 
       } 
      } 
      map.put(file, tf); 
     } else { 
      tf = map.get(file); 
     } 
     if (msg.hasValue(MType.FILE_BYTE)) { 
      byte[] buf = (byte[]) msg.getValue(MType.FILE_BYTE); 
      int count = (int) msg.getValue(MType.FILE_COUNT); 
      tf.addEntry(buf, count); 
     } 
     if (msg.hasValue(MType.FILE_CLOSE)) { 
      tf.writeFile(true); 
      map.remove(file); 

      if (sender instanceof Server) { 
       Server server = (Server) sender; 
       msg = new Message(MType.FILE_NAME, name); 
       msg.setValue(MType.FILE_SIZE, size); 
       msg.setValue(MType.FILE_ATTACHMENT, server.getFileID()); 
       addFile(file, server); 
       server.broadcast(msg); 
      } 

     } 
    } 
} 

И здесь мой класс TempFile:

public class TempFile { 

    private ArrayList<Byte[]> data; 
    private ArrayList<Integer> counts; 
    private File file; 

    public TempFile(File file) { 
     data = new ArrayList<>(); 
     counts = new ArrayList<>(); 
     this.file = file; 
    } 

    public void addEntry(byte[] data, int count) { 

     this.data.add(Utility.toWrapper(data)); 
     this.counts.add(count); 

    } 

    public void writeFile(boolean append) { 
     try { 
      BufferedOutputStream os = new BufferedOutputStream(new FileOutputStream(file)); 
      for (int i = 0; i < data.size(); i++) { 
       byte[] chunk = Utility.toPrimitive(data.get(i)); 
       int count = counts.get(i); 
       os.write(chunk, 0, count); 
      } 
      os.flush(); 
      os.close(); 
     } catch (FileNotFoundException e) { 
      e.printStackTrace(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 
    } 

} 

И вот моя другая реализация, которая включает в себя действие ual temp files:

public class TempFile2 { 

    private File file; 
    private File tempFile; 
    private FileOutputStream os; 

    public TempFile2(File file) { 
     this.file = file; 
     this.tempFile = new File(file.getParent(), FilenameUtils.getBaseName(file.getName()) + ".tmp"); 
     if (!tempFile.exists()) { 
      try { 
       tempFile.createNewFile(); 
      } catch (IOException e) { 
       e.printStackTrace(); 
      } 
     } 
     try { 
      os = new FileOutputStream(tempFile); 
     } catch (FileNotFoundException e) { 
      e.printStackTrace(); 
     } 
    } 

    public void addEntry(byte[] data, int count) { 

     try { 
      os.write(data, 0, count); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

    } 

    public void writeFile() { 

     try { 
      os.close(); 
      Files.copy(tempFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

    } 

} 
+0

Являются ли оба файла .tmp и окончательный файл пустыми? –

+0

Вы добавляете все записи в «ArrayList <> данные в памяти», а затем, когда вы на самом деле пишете в «OutputStream» в своем классе «TempFile», вы повторяете и выполняете только 'os.write' в цикле. Вы только 'os.flush' вне этого цикла. Таким образом, да, это только очистит то, что вы написали в буфере «OutputStream», когда вы закончите запись всех записей файла или до тех пор, пока данные не превысят встроенный лимит. Выполнение флеша после каждой записи может повлечь за собой штрафные санкции ввода-вывода за ваш код, но, возможно, сделать флеш после каждых n записей. –

ответ

0

Чтобы сделать эту работу, вам необходимо очистить выходной поток по мере поступления.

public class TempFile2 { 

    private File file; 
    private File tempFile; 
    private FileOutputStream os; 

    public TempFile2(File file) { 
     this.file = file; 
     this.tempFile = new File(file.getParent(), FilenameUtils.getBaseName(file.getName()) + ".tmp"); 
     if (!tempFile.exists()) { 
      try { 
       tempFile.createNewFile(); 
      } catch (IOException e) { 
       e.printStackTrace(); 
      } 
     } 
     try { 
      os = new FileOutputStream(tempFile); 
     } catch (FileNotFoundException e) { 
      e.printStackTrace(); 
     } 
    } 

    public void addEntry(byte[] data, int count) { 

     try { 
      os.write(data, 0, count); 
      os.flush(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

    } 

    public void writeFile() { 

     try { 
      os.close(); 
      Files.copy(tempFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

    } 

} 

В самом деле, вместо того, чтобы выходной поток открыт, можно открыть и закрыть его каждый раз, когда вы получаете порцию данных:

public class TempFile2 { 

    private File file; 
    private File tempFile; 

    public TempFile2(File file) { 
     this.file = file; 
     this.tempFile = new File(file.getParent(), FilenameUtils.getBaseName(file.getName()) + ".tmp"); 
     if (!tempFile.exists()) { 
      try { 
       tempFile.createNewFile(); 
      } catch (IOException e) { 
       e.printStackTrace(); 
      } 
     } 

    } 

    public void addEntry(byte[] data, int count) { 
     try(OutputStream os = new FileOutputStream(tempFile, true)) { 
      os.write(data, 0, count); 
      os.flush(); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

    } 

    public void writeFile() { 

     try { 
      Files.copy(tempFile.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING); 
     } catch (IOException e) { 
      e.printStackTrace(); 
     } 

    } 

} 

Кроме того, у меня есть предложение.

Операция read() может считывать только несколько байтов, но ваша текущая реализация все равно отправит весь массив. Одним из решений этой проблемы было бы сделать новый меньший массив для хранения данных:

byte[] bytesToSend = new byte[count]; 
System.arraycopy(buf, 0, bytesToSend, 0, count); 

Я думаю, еще лучшее решение было бы использовать класс Base64 здесь и отправить сериализованную версию массива байт вместо самого байтового массива.

// In sender 
byte[] bytesToSend = new byte[count]; 
System.arraycopy(buf, 0, bytesToSend, 0, count); 
String encodedBytes = Base64.getEncoder().encodeToString(bytesToSend); 
msg.setValue(MType.FILE_BYTE, encodedBytes); 

// In receiver 
String encodedBytes = (String) msg.getValue(MType.FILE_BYTE); 
buf = Base64.getDecoder().decode(encodedBytes);