2015-07-02 6 views
5

Ho questa classe per codificare e decodificare un file. Quando eseguo la classe con file .txt, il risultato è riuscito. Ma quando eseguo il codice con .jpg o .doc non posso aprire il file o non è uguale a originale. Non so perché questo sta accadendo. Ho modificato questa classe http://myjeeva.com/convert-image-to-string-and-string-to-image-in-java.html. Ma voglio cambiare questa lineaErrore nella codifica dei file in base64 java

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

per

byte example[] = new byte[1024]; 

e leggere il file così tante volte come abbiamo bisogno. Grazie.

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

    public class Encode { 

Input = Ingresso root file - uscita = radice del file di output - imageDataString = stringa codificata

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

ho visto negli esempi che le persone creano un byte [] con la stessa lunghezza rispetto al file. Non voglio questo perché non saprò quale lunghezza avrà il file.

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

Ho provato a cambiare

String imageData = encodeImage(buff); 

per

String imageData = encodeImage(buff,r); 

e il metodo 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); 
} 

ma ho l'errore:

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

risposta

4

Hai due problemi nel programma.

Il primo, come menzionato in @Joop Eggen, è che non si sta gestendo correttamente l'input.

Infatti, Java non ti promette che anche nel mezzo del file, starai leggendo l'intero 1024 byte.Potrebbe solo leggere 50 byte, e dirti che legge 50 byte, e poi la prossima volta leggerà altri 50 byte.

Supponiamo di aver letto 1024 byte nel round precedente. E ora, nel round corrente, stai leggendo solo 50. La tua matrice di byte ora contiene 50 dei nuovi byte, e il resto sono i vecchi byte della lettura precedente!

Quindi è sempre necessario copiare il numero esatto di byte copiati in un nuovo array e passarlo alla funzione di codifica.

Così, per risolvere questo problema particolare, è necessario fare qualcosa di simile:

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

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

     String imageData = encodeImage(realBuff); 

     ... 
} 

Tuttavia, questo non è l'unico problema qui. Il tuo vero problema è con la codifica Base64 stessa.

Ciò che Base64 fa è prendere i byte, spezzarli in blocchi di 6 bit e quindi trattare ognuno di quei blocchi come un numero compreso tra N 0 e 63. Quindi prende l'ennesimo carattere dalla sua tabella di caratteri, per rappresentare quello pezzo.

Ma questo significa che non può solo codificare un singolo byte o due byte, perché un byte contiene 8 bit, e che significa un pezzo di 6 bit e 2 bit rimanenti. Due byte hanno 16 bit. Questo è 2 pezzi di 6 bit e 4 bit rimanenti.

Per risolvere questo problema, Base64 codifica sempre 3 byte consecutivi. Se l'input non si divide uniformemente di tre, lo aggiunge zero bit aggiuntivi.

Qui è un piccolo programma che illustra il problema:

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

Quindi, immaginate la mia matrice di 6 byte è il file di immagine. E immagina che il tuo buffer non stia leggendo 1024 byte ma 2 byte ogni volta. Questo sta per essere l'uscita della codifica:

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

Come si può vedere, la codifica della gamma completa ci ha dato 8 caratteri. Ogni gruppo di tre byte viene convertito in quattro blocchi di 6 bit, che a loro volta vengono convertiti in quattro caratteri.

Ma la codifica dei tre array a due byte ti ha fornito una stringa di 9 caratteri. È una corda completamente diversa! Ogni gruppo di due byte è stato esteso a tre blocchi di 6 bit mediante il riempimento con zeri. E dal momento che non hai richiesto il padding, produce solo 3 caratteri, senza il = aggiuntivo che di solito contrassegna quando il numero di byte non è divisibile per 3.

L'output dalla parte del programma che decodifica l'8-carattere , corretta stringa codificata va bene:

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

Ma il risultato dal tentativo di decodificare il 9 caratteri, errato stringa codificata è:

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) 
Non

buona! Un buon stringa base64 deve sempre avere multipli di 4 caratteri, e abbiamo solo 9.

Poiché si sceglie una dimensione di buffer di 1024, che non è un multiplo di 3, tale problema si accadere. È necessario codificare un multiplo di 3 byte ogni volta per produrre la stringa corretta.Quindi, in realtà, è necessario creare un buffer di dimensioni pari a 3072 o qualcosa del genere.

Ma a causa del primo problema, prestare molta attenzione a ciò che si passa all'encoder. Perché può sempre succedere che tu stia leggendo meno di 3072 byte. E poi, se il numero non è divisibile per tre, si verificherà lo stesso problema.

+0

Grazie per il tuo tempo per dare la risposta. Ho capito meglio la base64. Im andando a modificare la mia classe con i tuoi consigli. – JGG

+0

@JGG Dai un'occhiata al metodo 'Base64.Encoder.wrap (OutputStream)'. Crea un flusso di output dove puoi scrivere i byte in blocchi senza avere il problema che ho menzionato (ad esempio puoi scrivere 1024 byte ogni volta, e dopo aver chiuso il flusso avrai i dati codificati corretti nel suo output). – RealSkeptic

0

un'occhiata a:

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

read ritorna -1 in end-of-file o il numero effettivo di byte che sono stati letti.

Quindi l'ultimo buff potrebbe non essere letto completamente e persino contenere i rifiuti da qualsiasi lettura precedente. Quindi è necessario utilizzare r.

Poiché questo è un compito, il resto dipende da voi.

A proposito:

byte[] array = new byte[1024] 

è più convenzionale in Java. La sintassi:

byte array[] = ... 

era compatibile con C/C++.

+0

Grazie per la risposta. Ho aggiunto informazioni aggiuntive. Ho provato a eseguire la lezione usando r ma ho avuto un problema di compilazione e non ho trovato la soluzione – JGG