Ошибка кодирования файлов в base64 java

у меня есть этот класс для кодирования и декодирования файла. Когда я управляю классом .txt файлы результат успешно. Но когда я запускаю код .формат JPG или. док я не могу открыть файл или он не равен оригиналу. Я не знаю, почему это происходит. Я изменил этот класс http://myjeeva.com/convert-image-to-string-and-string-to-image-in-java.html. Но я хочу изменить эту строку

byte imageData[] = new byte[(int) file.length()];

на

byte example[] = new byte[1024];

и прочитать файл так много как раз то, что нам нужно. Спасибо.

import java.io.*;
import java.util.*;

  public class Encode {

вход = корень входного файла-выход = корень выходного файла-imageDataString =строка закодирована

  String input;
  String output;
  String imageDataString;


  public void setFileInput(String input){
    this.input=input;
  }

  public void setFileOutput(String output){
    this.output=output;
  }

  public String getFileInput(){
    return input;
  }

  public String getFileOutput(){
    return output;
  }

  public String getEncodeString(){
    return  imageDataString;
  }

  public String processCode(){
    StringBuilder sb= new StringBuilder();

    try{
        File fileInput= new File( getFileInput() );
        FileInputStream imageInFile = new FileInputStream(fileInput);

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

        byte buff[] = new byte[1024];

        int r = 0;

        while ( ( r = imageInFile.read( buff)) > 0 ) {

          String imageData = encodeImage(buff);

          sb.append( imageData);

          if ( imageInFile.available() <= 0 ) {
            break;
          }
        }



       } catch (FileNotFoundException e) {
        System.out.println("File not found" + e);
      } catch (IOException ioe) {
        System.out.println("Exception while reading the file " + ioe);

    } 

        imageDataString = sb.toString();

       return imageDataString;
}  


  public  void processDecode(String str) throws IOException{

      byte[] imageByteArray = decodeImage(str);
      File fileOutput= new File( getFileOutput());
      FileOutputStream imageOutFile = new FileOutputStream( fileOutput);

      imageOutFile.write(imageByteArray);
      imageOutFile.close();

}

 public static String encodeImage(byte[] imageByteArray) {

      return  Base64.getEncoder().withoutPadding().encodeToString( imageByteArray);

    }

    public static byte[] decodeImage(String imageDataString) {
      return  Base64.getDecoder().decode(  imageDataString);  

    }


  public static void main(String[] args) throws IOException {

    Encode a = new Encode();

    a.setFileInput( "C://Users//xxx//Desktop//original.doc");
    a.setFileOutput("C://Users//xxx//Desktop//original-copied.doc");

    a.processCode( );

    a.processDecode( a.getEncodeString());

    System.out.println("C O P I E D");
  }
}

Я пытался менять

String imageData = encodeImage(buff);

на

String imageData = encodeImage(buff,r);

и метод encodeImage

public static String encodeImage(byte[] imageByteArray, int r) {

     byte[] aux = new byte[r];

     for ( int i = 0; i < aux.length; i++) {
       aux[i] = imageByteArray[i];

       if ( aux[i] <= 0 ) {
         break;
       }
     }
return  Base64.getDecoder().decode(  aux);
}

но у меня ошибка:

Exception in thread "main" java.lang.IllegalArgumentException: Last unit does not have enough valid bits   

2 ответов


у вас есть две проблемы в вашей программе.

первое, Как упоминалось в @Joop Eggen, заключается в том, что вы неправильно обрабатываете свой ввод.

На самом деле Java не обещает вам, что даже в середине файла вы будете читать все 1024 байта. Он может просто прочитать 50 байт и сказать вам, что он прочитал 50 байт, а затем в следующий раз он прочитает еще 50 байт.

Предположим, вы прочитали 1024 байта в предыдущем раунде. А теперь, в настоящем раунд, ты читаешь только 50. Ваш массив байтов теперь содержит 50 новых байтов, а остальные-старые байты из предыдущего чтения!

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

Итак, чтобы исправить эту конкретную проблему, вам нужно сделать что-то вроде:

 while ( ( r = imageInFile.read( buff)) > 0 ) {

      byte[] realBuff = Arrays.copyOf( buff, r );

      String imageData = encodeImage(realBuff);

      ...
 }

однако, это не единственная проблема здесь. Ваша реальная проблема заключается в кодировке Base64 себя.

что Base64 делает, это взять ваши байты, разбить их на 6-битные куски, а затем рассматривать каждый из этих кусков как число между n 0 и 63. Затем он берет N-й символ из своей таблицы символов, чтобы представить этот кусок.

но это означает, что он не может просто кодировать один байт или два байта, потому что байт содержит 8 бит, а это означает один кусок 6 бит и 2 оставшихся бита. Два байта имеют 16 бит. Это 2 куска 6 бит и 4 оставшихся биты.

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

вот небольшая программа, которая демонстрирует проблему:

package testing;

import java.util.Base64;

public class SimpleTest {

    public static void main(String[] args) {

        // An array containing six bytes to encode and decode.
        byte[] fullArray = { 0b01010101, (byte) 0b11110000, (byte)0b10101010, 0b00001111, (byte)0b11001100, 0b00110011 };

        // The same array broken into three chunks of two bytes.

        byte[][] threeTwoByteArrays = {
            {       0b01010101, (byte) 0b11110000 },
            { (byte)0b10101010,        0b00001111 },
            { (byte)0b11001100,        0b00110011 }
        };
        Base64.Encoder encoder = Base64.getEncoder().withoutPadding();

        // Encode the full array

        String encodedFullArray = encoder.encodeToString(fullArray);

        // Encode the three chunks consecutively 

        StringBuilder encodedStringBuilder = new StringBuilder();
        for ( byte [] twoByteArray : threeTwoByteArrays ) {
            encodedStringBuilder.append(encoder.encodeToString(twoByteArray));
        }
        String encodedInChunks = encodedStringBuilder.toString();

        System.out.println("Encoded full array: " + encodedFullArray);
        System.out.println("Encoded in chunks of two bytes: " + encodedInChunks);

        // Now  decode the two resulting strings

        Base64.Decoder decoder = Base64.getDecoder();

        byte[] decodedFromFull = decoder.decode(encodedFullArray);   
        System.out.println("Byte array decoded from full: " + byteArrayBinaryString(decodedFromFull));

        byte[] decodedFromChunked = decoder.decode(encodedInChunks);
        System.out.println("Byte array decoded from chunks: " + byteArrayBinaryString(decodedFromChunked));
    }

    /**
     * Convert a byte array to a string representation in binary
     */
    public static String byteArrayBinaryString( byte[] bytes ) {
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for ( byte b : bytes ) {
            sb.append(Integer.toBinaryString(Byte.toUnsignedInt(b))).append(',');
        }
        if ( sb.length() > 1) {
            sb.setCharAt(sb.length() - 1, ']');
        } else {
            sb.append(']');
        }
        return sb.toString();
    }
}

Итак, представьте, что мой 6-байтовый массив - это ваш файл изображения. И представьте, что ваш буфер каждый раз читает не 1024 байта, а 2 байта. Это будет выход кодировки:

Encoded full array: VfCqD8wz
Encoded in chunks of two bytes: VfAqg8zDM

как вы можете смотрите, кодировка полного массива дала нам 8 символов. Каждая группа из трех байтов преобразуется в четыре блока по 6 бит, которые в свою очередь преобразуются в четыре символа.

но кодировка трех двухбайтовых массивов дала вам строку из 9 символов. Это совершенно другая струна! Каждая группа из двух байтов была расширена до трех кусков по 6 бит путем заполнения нулями. И так как вы не просили никакого заполнения, он производит только 3 символа, без дополнительного = что обычно отмечает, когда количество байтов не делится на 3.

вывод из части программы, которая декодирует 8-символьную, правильную кодированную строку, в порядке:

Byte array decoded from full: [1010101,11110000,10101010,1111,11001100,110011]

но результат от попытки декодировать 9-символьную, неверно закодированную строку:

Exception in thread "main" java.lang.IllegalArgumentException: Last unit does not have enough valid bits
    at java.util.Base64$Decoder.decode0(Base64.java:734)
    at java.util.Base64$Decoder.decode(Base64.java:526)
    at java.util.Base64$Decoder.decode(Base64.java:549)
    at testing.SimpleTest.main(SimpleTest.java:34)

не хорошо! Хорошая строка base64 всегда должна иметь кратные 4 символа, и у нас есть только 9.

поскольку вы выбрали размер буфера 1024, который не является множественным из 3, что проблема будет произойдет. Вам нужно кодировать несколько 3 байтов каждый раз, чтобы создать правильную строку. Поэтому на самом деле вам нужно создать буфер размером 3072 или что-то подобное.

но из-за первой проблемы, будьте очень осторожны, что вы проходите в энкодер. Потому что всегда может случиться так, что вы будете читать меньше, чем 3072 байт. И тогда, если число не делится на три, возникнет та же проблема.


посмотреть:

    while ( ( r = imageInFile.read( buff)) > 0 ) {
      String imageData = encodeImage(buff);

read возвращает -1 в конец файла или на фактическое количество байт это было прочитано.

Итак, последний buff может быть не полностью прочитан и даже содержать мусор из любого предыдущего чтения. Поэтому вам нужно использовать r.

как это задание, остальное зависит от вас.

кстати:

 byte[] array = new byte[1024]

является более традиционным в Java. Синтаксис:

 byte array[] = ...

был для совместимости с C / C++.