Konwersja tablicy bajtów na ciąg znaków (Java)


85

Piszę aplikację internetową w Google App Engine. Pozwala ludziom zasadniczo edytować kod HTML, który jest przechowywany jako .htmlplik w blobstore.

Używam fetchData, aby zwrócić byte[]wszystkie znaki w pliku. Próbuję drukować do html, aby użytkownik mógł edytować kod HTML. Wszystko działa świetnie!

Oto mój jedyny teraz problem:

Tablica bajtów ma pewne problemy podczas konwersji z powrotem na ciąg. Sprytne cytaty i kilka postaci wyglądają fajnie. (? lub japońskie symbole itp.) W szczególności widzę, że kilka bajtów ma wartości ujemne, które powodują problem.

Cudzysłowy powracają jako -108iw -109tablicy bajtów. Dlaczego tak się dzieje i jak mogę zdekodować ujemne bajty, aby pokazać prawidłowe kodowanie znaków?



Cześć, wiem, że to naprawdę stary post, ale mam podobne problemy. Tworzę proxy typu man-in-the-middle dla ssl. Problem, przed którym stoję, jest taki sam, jak twój. Słucham gniazda i pobieram dane do, InputStreama następnie do byte[]. Teraz, gdy próbuję zamienić byte[]tekst na String (muszę użyć treści odpowiedzi do ataków), otrzymuję naprawdę zabawne postacie pełne sprytnych cudzysłowów i znaków zapytania, a co nie. Wierzę ciebie problemem jest taki sam jak mój, jak oboje mają do czynienia z htmlw byte[]. Czy możesz prosić o poradę?
Parul S

Nawiasem mówiąc, poszedłem do tego stopnia, aby znaleźć kodowanie mojego systemu za pomocą Sytem.properties i okazało się, że jest to „Cp1252”. Teraz użyłem, String str=new String(buffer, "Cp1252");ale bez pomocy.
Parul S

Odpowiedzi:


141

Tablica bajtów zawiera znaki w specjalnym kodowaniu (które powinieneś wiedzieć). Sposób na przekonwertowanie go na String to:

String decoded = new String(bytes, "UTF-8");  // example for one encoding type

Nawiasem mówiąc - surowe bajty mogą pojawiać się jako ujemne liczby dziesiętne tylko dlatego, że typ danych java bytejest podpisany, obejmuje zakres od -128 do 127.


-109 = 0x93: Control Code "Set Transmit State"

Wartość (-109) to niedrukowalny znak kontrolny w UNICODE. Więc UTF-8 nie jest poprawnym kodowaniem dla tego strumienia znaków.

0x93w „Windows-1252” to „inteligentny cytat”, którego szukasz, więc nazwa Java tego kodowania to „Cp1252”. Następna linia zawiera kod testowy:

System.out.println(new String(new byte[]{-109}, "Cp1252")); 

5
Próbowałem użyć UTF-8 i nadal wyszło jako? Dlaczego nie można znaleźć mapowania dla tych ujemnych wartości?
Josh

0x93 jest jednak poprawnym bajtem kontynuacji w UTF-8 - obecność tego bajtu wyklucza, że ​​jest to UTF-8, tylko jeśli nie występuje po bajcie z ustawionymi pierwszymi dwoma bitami.
Nick Johnson,

1
@Josh Andreas wyjaśnia dlaczego - ponieważ bytetyp danych Javy jest podpisany. Wartości „ujemne” to po prostu bajty z najbardziej znaczącym zestawem bajtów. Wyjaśnia również, jaki jest najbardziej prawdopodobny zestaw znaków, którego powinieneś używać - Windows-1252. Powinieneś jednak wiedzieć, jakiego zestawu znaków użyć z kontekstu lub konwencji, bez zgadywania.
Nick Johnson,

25

Java 7 i nowsze

Możesz również przekazać żądane kodowanie do Stringkonstruktora jako Charsetstałą z StandardCharsets . Może to być bezpieczniejsze niż przekazywanie kodowania jako a String, jak sugerowano w innych odpowiedziach.

Na przykład do kodowania UTF-8

String bytesAsString = new String(bytes, StandardCharsets.UTF_8);

1
To jest powtórka odpowiedzi z 2011 roku. -1
james.garriss

2
@ james.garriss Nie wydaje mi się, żeby tak było, o ile wspomnę tylko o nowym konstruktorze wprowadzonym w java 7, pozwalającym na przekazywanie kodowania jako stałej, co moim zdaniem jest ładniejsze i bezpieczniejsze niż poprzednie api wspomniano we wcześniejszych odpowiedziach, gdzie kodowanie zostało przekazane jako ciąg znaków, jeśli w ogóle.
davnicwil


5
public class Main {

    /**
     * Example method for converting a byte to a String.
     */
    public void convertByteToString() {

        byte b = 65;

        //Using the static toString method of the Byte class
        System.out.println(Byte.toString(b));

        //Using simple concatenation with an empty String
        System.out.println(b + "");

        //Creating a byte array and passing it to the String constructor
        System.out.println(new String(new byte[] {b}));

    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        new Main().convertByteToString();
    }
}

Wynik

65
65
A

5
public static String readFile(String fn)   throws IOException 
{
    File f = new File(fn);

    byte[] buffer = new byte[(int)f.length()];
    FileInputStream is = new FileInputStream(fn);
    is.read(buffer);
    is.close();

    return  new String(buffer, "UTF-8"); // use desired encoding
}

3
Ten kod spowoduje wyciek zasobu, jeśli readzgłosi wyjątek.
Raedwald,

4

sugeruję Arrays.toString(byte_array);

To zależy od twojego celu. Na przykład chciałem zapisać tablicę bajtów dokładnie w formacie, który można zobaczyć w czasie debugowania, który jest [1, 2, 3]mniej więcej taki : Jeśli chcesz zapisać dokładnie tę samą wartość bez konwersji bajtów na format znakowy, Arrays.toString (byte_array)zrób to. Ale jeśli chcesz zapisać znaki zamiast bajtów, powinieneś użyć String s = new String(byte_array). W tym przypadku sjest to odpowiednik [1, 2, 3]w formacie znaku.


Czy możesz podać więcej informacji, dlaczego to sugerujesz? (Czy to rozwiąże problem? Czy możesz powiedzieć, dlaczego to rozwiązuje?) Dzięki!
Dean J

To zależy od twojego celu. Na przykład chciałem zapisać tablicę bajtów dokładnie w formacie, który można zobaczyć w czasie debugowania, który wygląda mniej więcej tak: [1, 2, 3] Jeśli chcesz zapisać dokładnie tę samą wartość bez konwersji bajtów na format znakowy, Arrays.toString (byte_array) robi to. Ale jeśli chcesz zapisać znaki zamiast bajtów, powinieneś użyć String s = new String (byte_array). W tym przypadku s jest równoważne z [1, 2, 3] w formacie znakowym.
Przesłuchujący,

@sas, powinieneś dodać te informacje do samej odpowiedzi (edytując ją), a nie jako komentarz. Ogólnie w SO należy zawsze pamiętać, że komentarze mogą w dowolnym momencie zostać usunięte - naprawdę ważne informacje powinny znajdować się w samej odpowiedzi.
Jeen Broekstra

3

Poprzednia odpowiedź od Andreas_D jest dobra. Dodam tylko, że wszędzie tam, gdzie wyświetlasz dane wyjściowe, będzie czcionka i kodowanie znaków i może nie obsługiwać niektórych znaków.

Aby sprawdzić, czy problem stanowi Java, czy wyświetlacz, wykonaj następujące czynności:

    for(int i=0;i<str.length();i++) {
        char ch = str.charAt(i);
        System.out.println(i+" : "+ch+" "+Integer.toHexString(ch)+((ch=='\ufffd') ? " Unknown character" : ""));
    }

Java odwzoruje wszystkie znaki, których nie może zrozumieć, na 0xfffd oficjalny znak nieznanych znaków. Jeśli widzisz znak „?” w danych wyjściowych, ale nie jest odwzorowany na 0xfffd, problem stanowi czcionka ekranu lub kodowanie, a nie Java.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.