Konwertowanie między ciągami znaków a ArrayBuffers

264

Czy istnieje powszechnie akceptowana technika skutecznego konwertowania ciągów JavaScript na ArrayBuffers i odwrotnie? W szczególności chciałbym móc zapisać zawartość ArrayBuffer localStoragei odczytać ją ponownie.

kpozin
źródło
1
Nie mam w tym żadnego doświadczenia, ale sądząc z dokumentacji API ( khronos.org/registry/typedarray/specs/latest ), jeśli zbudujesz Int8Array ArrayBufferView, można po prostu użyć notacji nawiasowej do skopiowania znaków string[i] = buffer[i]i odwrotnie.
FK82,
2
@ FK82, które wygląda na rozsądne podejście (użycie Uint16Arrays dla 16-bitowych znaków JS), ale ciągi JavaScript są niezmienne, więc nie można przypisać bezpośrednio do pozycji znaku. Nadal będzie trzeba skopiować String.fromCharCode(x)z każdej wartości w Uint16Arraycelu normalny Array, a następnie zadzwonić .join()na Array.
kpozin
@kpozin: To prawda, że ​​tak naprawdę się nie zastanawiałem.
FK82,
5
@kpozin Okazuje się, że większość nowoczesnych silników JS zoptymalizowała konkatenację łańcuchów do tego stopnia, że ​​taniej jest po prostu użyć string += String.fromCharCode(buffer[i]);. Wydaje się dziwne, że nie byłoby wbudowanych metod konwersji między ciągami znaków a tablicami maszynowymi. Musieli wiedzieć, że coś takiego się pojawi.
pobierz
arrayBuffer.toString () działa dla mnie dobrze.
obywatel Conn

Odpowiedzi:

129

Aktualizacja 2016 - po pięciu latach w specyfikacjach pojawiły się nowe metody (patrz wsparcie poniżej) do konwersji między łańcuchami i tablicami maszynowymi przy użyciu odpowiedniego kodowania.

TextEncoder

TextEncoderReprezentuje :

TextEncoderInterfejs przedstawia koder przeznaczony do sposobu określonego, to znaczy kodowania specyficznego charakteru, jak utf-8,iso-8859-2, koi8, cp1261, gbk, ... Koder pobiera strumień punktów kodowych jako dane wejściowe i emituje strumień bajtów.

Zmień notatkę, ponieważ napisano powyższe: (ibid.)

Uwaga: Firefox, Chrome i Opera wcześniej obsługiwały typy kodowania inne niż utf-8 (takie jak utf-16, iso-8859-2, koi8, cp1261 i gbk). Począwszy od Firefoksa 48 [...], Chrome 54 [...] i Opery 41, nie są dostępne żadne inne typy kodowania poza utf-8, w celu dopasowania do specyfikacji. *

*) Zaktualizowano specyfikacje (W3) i tutaj (whatwg).

Po utworzeniu instancji TextEncoderzajmie ciąg znaków i zakoduje go przy użyciu danego parametru kodowania:

if (!("TextEncoder" in window)) 
  alert("Sorry, this browser does not support TextEncoder...");

var enc = new TextEncoder(); // always utf-8
console.log(enc.encode("This is a string converted to a Uint8Array"));

Następnie można oczywiście użyć .bufferparametru wynikowego, Uint8Arrayaby w ArrayBufferrazie potrzeby przekształcić podkładanie w inny widok.

Upewnij się tylko, że znaki w ciągu są zgodne ze schematem kodowania, na przykład, jeśli użyjesz znaków spoza zakresu UTF-8 w przykładzie, będą one zakodowane do dwóch bajtów zamiast jednego.

Do ogólnego użytku użyłbyś kodowania UTF-16 do takich celów localStorage.

TextDecoder

Podobnie proces odwrotny wykorzystujeTextDecoder :

TextDecoderInterfejs reprezentuje dekodera dla konkretnej metody, czyli kodowanie specyfika, jak utf-8, iso-8859-2, koi8, cp1261, gbk, ... Dekoder pobiera strumień bajtów jako wejście i emituje strumień punktów kodowych.

Wszystkie dostępne typy dekodowania można znaleźć tutaj .

if (!("TextDecoder" in window))
  alert("Sorry, this browser does not support TextDecoder...");

var enc = new TextDecoder("utf-8");
var arr = new Uint8Array([84,104,105,115,32,105,115,32,97,32,85,105,110,116,
                          56,65,114,114,97,121,32,99,111,110,118,101,114,116,
                          101,100,32,116,111,32,97,32,115,116,114,105,110,103]);
console.log(enc.decode(arr));

Biblioteka MDN StringView

Alternatywą dla nich jest użycie StringViewbiblioteki (licencjonowanej jako lgpl-3.0), której celem jest:

  • utworzyć interfejs typu C dla ciągów znaków (tj. tablicę kodów znaków - ArrayBufferView w JavaScript) w oparciu o interfejs JavaScript ArrayBuffer
  • aby stworzyć wysoce rozszerzalną bibliotekę, którą każdy może rozszerzyć, dodając metody do obiektu StringView.prototype
  • aby stworzyć kolekcję metod dla takich ciągów obiektów (od teraz: stringViews), które działają ściśle na tablicach liczb, a nie na tworzeniu nowych niezmiennych ciągów JavaScript
  • do pracy z kodowaniem Unicode innym niż domyślne DOMStrings JavaScript UTF-16

dając znacznie większą elastyczność. Wymagałoby to jednak od nas linkowania do tej biblioteki lub osadzania jej podczas, gdy TextEncoder/ TextDecoderjest wbudowane w nowoczesne przeglądarki.

Wsparcie

Według stanu na lipiec / 2018 r .:

TextEncoder (Eksperymentalny, na standardowym torze)

 Chrome    | Edge      | Firefox   | IE        | Opera     | Safari
 ----------|-----------|-----------|-----------|-----------|-----------
     38    |     ?     |    19°    |     -     |     25    |     -

 Chrome/A  | Edge/mob  | Firefox/A | Opera/A   |Safari/iOS | Webview/A
 ----------|-----------|-----------|-----------|-----------|-----------
     38    |     ?     |    19°    |     ?     |     -     |     38

°) 18: Firefox 18 implemented an earlier and slightly different version
of the specification.

WEB WORKER SUPPORT:

Experimental, On Standard Track

 Chrome    | Edge      | Firefox   | IE        | Opera     | Safari
 ----------|-----------|-----------|-----------|-----------|-----------
     38    |     ?     |     20    |     -     |     25    |     -

 Chrome/A  | Edge/mob  | Firefox/A | Opera/A   |Safari/iOS | Webview/A
 ----------|-----------|-----------|-----------|-----------|-----------
     38    |     ?     |     20    |     ?     |     -     |     38

Data from MDN - `npm i -g mdncomp` by epistemex

źródło
2
Brak obsługi TextDecodera z IE i Edge: caniuse.com/#search=TextDecoder
Andrei Damian-Fekete
1
Według MS jest w fazie rozwoju: developer.microsoft.com/en-us/microsoft-edge/platform/status/…
Maurice Müller
Brak wsparcia dla Safari Mobile (iOS) w 2018-04-18: developer.mozilla.org/en-US/docs/Web/API/TextDecoder
brązu człowieka
One-liner: var encoder = 'TextEncoder' in window ? new TextEncoder() : {encode: function(str){return Uint8Array.from(str, function(c){return c.codePointAt(0);});}};więc możesz po prostuvar array = encoder.encode('hello');
Yeti
1
Chodzi o TextEncoderto, że jeśli masz dane binarne w ciągu (jak obrazek), nie chcesz ich używać TextEncoder(najwyraźniej). Znaki o kodach powyżej 127 wytwarzają dwa bajty. Dlaczego mam dane binarne w ciągu? cy.fixture(NAME, 'binary')( cypress) tworzy ciąg.
x-yuri
175

Chociaż Dennis i gengkev rozwiązują problemy z użyciem Blob / FileReader, nie sugerowałbym takiego podejścia. Jest to asynchroniczne podejście do prostego problemu i jest znacznie wolniejsze niż bezpośrednie rozwiązanie. Napisałem post w html5rocks za pomocą prostszego i (znacznie szybszego) rozwiązania: http://updates.html5rocks.com/2012/06/How-to-convert-ArrayBuffer-to-and-from-String

A rozwiązaniem jest:

function ab2str(buf) {
  return String.fromCharCode.apply(null, new Uint16Array(buf));
}

function str2ab(str) {
  var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
  var bufView = new Uint16Array(buf);
  for (var i=0, strLen=str.length; i<strLen; i++) {
    bufView[i] = str.charCodeAt(i);
  }
  return buf;
}

EDYTOWAĆ:

Kodowanie API ułatwia rozwiązywanie konwersja ciąg problem. Sprawdź odpowiedź Jeffa Posnika na Html5Rocks.com na powyższy oryginalny artykuł.

Fragment:

Interfejs API kodowania ułatwia tłumaczenie między nieprzetworzonymi bajtami a natywnymi ciągami JavaScript, bez względu na to, z którego z wielu standardowych kodowań trzeba pracować.

<pre id="results"></pre>

<script>
  if ('TextDecoder' in window) {
    // The local files to be fetched, mapped to the encoding that they're using.
    var filesToEncoding = {
      'utf8.bin': 'utf-8',
      'utf16le.bin': 'utf-16le',
      'macintosh.bin': 'macintosh'
    };

    Object.keys(filesToEncoding).forEach(function(file) {
      fetchAndDecode(file, filesToEncoding[file]);
    });
  } else {
    document.querySelector('#results').textContent = 'Your browser does not support the Encoding API.'
  }

  // Use XHR to fetch `file` and interpret its contents as being encoded with `encoding`.
  function fetchAndDecode(file, encoding) {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', file);
    // Using 'arraybuffer' as the responseType ensures that the raw data is returned,
    // rather than letting XMLHttpRequest decode the data first.
    xhr.responseType = 'arraybuffer';
    xhr.onload = function() {
      if (this.status == 200) {
        // The decode() method takes a DataView as a parameter, which is a wrapper on top of the ArrayBuffer.
        var dataView = new DataView(this.response);
        // The TextDecoder interface is documented at http://encoding.spec.whatwg.org/#interface-textdecoder
        var decoder = new TextDecoder(encoding);
        var decodedString = decoder.decode(dataView);
        // Add the decoded file's text to the <pre> element on the page.
        document.querySelector('#results').textContent += decodedString + '\n';
      } else {
        console.error('Error while requesting', file, this);
      }
    };
    xhr.send();
  }
</script>
mangini
źródło
16
Niestety mój komentarz do html5rocks nie został jeszcze zatwierdzony. Dlatego krótka odpowiedź tutaj. Nadal uważam, że nie jest to właściwy sposób, ponieważ brakuje wielu znaków, szczególnie dlatego, że większość stron ma obecnie kodowanie UTF-8. Z jednej strony, dla większej liczby znaków specjalnych (powiedzmy azjatyckich) funkcja charCodeAt zwraca wartość 4-bajtową, więc zostaną one posiekane. Z drugiej strony proste angielskie znaki dwa razy powiększą ArrayBuffer (używasz 2 bajtów na każdy 1-bajtowy znak). Wyobraź sobie, że wysyłasz angielski tekst przez WebSocket, będzie on potrzebował dwa razy więcej czasu (źle w środowisku czasu rzeczywistego).
Dennis
9
Trzy przykłady: (1) This is a cool text!20 bajtów w UTF8 - 40 bajtów w Unicode. (2) ÄÖÜ6 bajtów w UTF8 - 6 bajtów w Unicode. (3) ☐☑☒9 bajtów w UTF8 - 6 bajtów w Unicode. Jeśli chcesz zapisać ciąg jako plik UTF8 (przez interfejs API obiektów Blob i File Writer), nie możesz użyć tych 2 metod, ponieważ ArrayBuffer będzie w formacie Unicode, a nie w UTF8.
Dennis,
3
Pojawia się błąd: Nieprzechwycony błąd zakresu: Przekroczono maksymalny rozmiar stosu wywołań. Co może być problemem?
Jacob
6
@Dennis - Ciągi JS używają UCS2, a nie UTF8 (a nawet UTF16) - co oznacza, że ​​charCodeAt () zawsze zwraca wartości 0 -> 65535. Każdy punkt kodowy UTF-8, który wymaga 4 bajtów, będzie reprezentowany przez pary zastępcze (patrz en.wikipedia .org / wiki /… ) - tj. dwie oddzielne 16-bitowe wartości UCS2.
Broofa
6
@jacob - Uważam, że błąd wynika z tego, że istnieje ograniczenie długości tablicy, które można przekazać do metody apply (). Np. String.fromCharCode.apply(null, new Uint16Array(new ArrayBuffer(246300))).lengthDziała dla mnie w Chrome, ale jeśli zamiast tego użyjesz 246301, otrzymam twój wyjątek
RangeError
71

Możesz użyć TextEncoderi TextDecoderze standardu kodowania , który jest wypełniany przez bibliotekę kodowania łańcuchów , do konwersji łańcucha na i z ArrayBuffers:

var uint8array = new TextEncoder().encode(string);
var string = new TextDecoder(encoding).decode(uint8array);
Ilmari Heikkinen
źródło
2
Nawiasem mówiąc, jest to domyślnie dostępne w przeglądarce Firefox: developer.mozilla.org/en-US/docs/Web/API/TextDecoder.decode
Joel Richard
2
Kciuki za nowe interfejsy API, które są znacznie lepsze niż dziwne obejścia!
Tomáš Zato - Przywróć Monikę
1
To nie zadziała ze wszystkimi typami postaci.
David
5
npm install text-encoding, var textEncoding = require('text-encoding'); var TextDecoder = textEncoding.TextDecoder;. Nie, dziękuję.
Evan Hu
narzekam ... jeśli mam istniejący bufor tablicowy, chcę napisać ciąg do, myślę, że muszę wziąć uint8array i skopiować go drugi raz?
shaunc,
40

Kropelka jest znacznie wolniejsza niż String.fromCharCode(null,array);

ale kończy się to niepowodzeniem, jeśli bufor tablicy staje się zbyt duży. Najlepszym rozwiązaniem, jakie znalazłem, jest użycie String.fromCharCode(null,array);i podzielenie go na operacje, które nie wysadzą stosu, ale są szybsze niż jeden znak na raz.

Najlepszym rozwiązaniem dla bufora dużej macierzy jest:

function arrayBufferToString(buffer){

    var bufView = new Uint16Array(buffer);
    var length = bufView.length;
    var result = '';
    var addition = Math.pow(2,16)-1;

    for(var i = 0;i<length;i+=addition){

        if(i + addition > length){
            addition = length - i;
        }
        result += String.fromCharCode.apply(null, bufView.subarray(i,i+addition));
    }

    return result;

}

Przekonałem się, że jest to około 20 razy szybsze niż użycie obiektu blob. Działa również w przypadku dużych ciągów ponad 100 MB.

Ryan Weinstein
źródło
3
Powinniśmy pójść z tym rozwiązaniem. Ponieważ rozwiązuje to jeszcze jeden przypadek użycia niż przyjęty
Sam
24

Na podstawie odpowiedzi gengkev utworzyłem funkcje na dwa sposoby, ponieważ BlobBuilder może obsługiwać String i ArrayBuffer:

function string2ArrayBuffer(string, callback) {
    var bb = new BlobBuilder();
    bb.append(string);
    var f = new FileReader();
    f.onload = function(e) {
        callback(e.target.result);
    }
    f.readAsArrayBuffer(bb.getBlob());
}

i

function arrayBuffer2String(buf, callback) {
    var bb = new BlobBuilder();
    bb.append(buf);
    var f = new FileReader();
    f.onload = function(e) {
        callback(e.target.result)
    }
    f.readAsText(bb.getBlob());
}

Prosty test:

string2ArrayBuffer("abc",
    function (buf) {
        var uInt8 = new Uint8Array(buf);
        console.log(uInt8); // Returns `Uint8Array { 0=97, 1=98, 2=99}`

        arrayBuffer2String(buf, 
            function (string) {
                console.log(string); // returns "abc"
            }
        )
    }
)
Dennis
źródło
Czy w arrayBuffer2String () chciałeś wywołać callback (...) zamiast console.log ()? W przeciwnym razie argument wywołania zwrotnego nie będzie używany.
Dan Phillimore
To wygląda jak droga - dzięki genkev i Dennis. Wydaje się to trochę głupie, że nie ma synchronicznego sposobu na osiągnięcie tego, ale co możesz zrobić ...
kpozin
JavaScript jest jednowątkowy. Dlatego FileReader jest asynchroniczny z dwóch powodów: (1) nie blokuje wykonywania innych skryptów JavaScript podczas ładowania (dużego) pliku (wyobraź sobie bardziej złożoną aplikację) i (2) nie blokuje interfejsu użytkownika / przeglądarki (częsty problem z długim czasem wykonywania kodu JS). Wiele interfejsów API jest asynchronicznych. Nawet w XMLHttpRequest 2 synchroniczny jest usuwany.
Dennis
Naprawdę miałem nadzieję, że to zadziała dla mnie, ale konwersja z łańcucha na ArrayBuffer nie działa niezawodnie. Tworzę ArrayBuffer z 256 wartościami i mogę przekształcić go w ciąg o długości 256. Ale jeśli spróbuję przekonwertować go z powrotem na ArrayBuffer - w zależności od zawartości mojego początkowego ArrayBuffer - otrzymam 376 elementów. Jeśli chcesz spróbować odtworzyć mój problem, traktuję mój ArrayBuffer jako siatkę 16x16 w Uint8Array, z wartościami obliczonymi tak a[y * w + x] = (x + y) / 2 * 16; , jak próbowałem getBlob("x"), z wieloma różnymi typami mimetów - bez powodzenia.
Matt Cruikshank
18
BlobBuilder jest przestarzały w nowszych przeglądarkach. Zmień new BlobBuilder(); bb.append(buf);na new Blob([buf]), rzutuj ArrayBuffer w drugiej funkcji na UintArray przez new UintArray(buf)(lub cokolwiek odpowiedniego dla danego typu danych), a następnie pozbądź się getBlob()wywołań. Na koniec, dla czystości, zmień nazwę bb na blob, ponieważ nie jest to już BlobBuilder.
sowbug
18

Wszystkie poniższe informacje dotyczą pobierania ciągów binarnych z buforów tablic

Polecam nie używać

var binaryString = String.fromCharCode.apply(null, new Uint8Array(arrayBuffer));

ponieważ to

  1. ulega awarii na dużych buforach (ktoś napisał o „magicznym” rozmiarze 246300, ale dostałem Maximum call stack size exceededbłąd na buforze 120000 bajtów (Chrome 29))
  2. ma naprawdę słabą wydajność (patrz poniżej)

Jeśli potrzebujesz dokładnie rozwiązania synchronicznego, użyj czegoś takiego

var
  binaryString = '',
  bytes = new Uint8Array(arrayBuffer),
  length = bytes.length;
for (var i = 0; i < length; i++) {
  binaryString += String.fromCharCode(bytes[i]);
}

jest tak wolny jak poprzedni, ale działa poprawnie. Wydaje się, że w chwili pisania tego nie ma dość szybkiego rozwiązania synchronicznego dla tego problemu (wszystkie biblioteki wymienione w tym temacie używają tego samego podejścia do swoich funkcji synchronicznych).

Ale tak naprawdę polecam użycie podejścia Blob+FileReader

function readBinaryStringFromArrayBuffer (arrayBuffer, onSuccess, onFail) {
  var reader = new FileReader();
  reader.onload = function (event) {
    onSuccess(event.target.result);
  };
  reader.onerror = function (event) {
    onFail(event.target.error);
  };
  reader.readAsBinaryString(new Blob([ arrayBuffer ],
    { type: 'application/octet-stream' }));
}

jedyną wadą (nie dla wszystkich) jest to, że jest asynchroniczna . I to około 8-10 razy szybciej niż poprzednie rozwiązania! (Kilka szczegółów: rozwiązanie synchroniczne w moim środowisku zajęło 950–1050 ms dla bufora 2,4 Mb, ale rozwiązanie z FileReaderem miało czas około 100–120 ms dla tej samej ilości danych. Przetestowałem oba rozwiązania synchroniczne w buforze 100 KB i pobrali prawie w tym samym czasie, więc pętla nie jest dużo wolniejsza przy użyciu „zastosuj”.)

BTW tutaj: Jak przekonwertować ArrayBuffer na i z String autor porównuje dwa podejścia takie jak ja i uzyskuje całkowicie przeciwne wyniki ( jego kod testowy jest tutaj ) Dlaczego tak różne wyniki? Prawdopodobnie z powodu jego łańcucha testowego o długości 1 KB (nazwał go „veryLongStr”). Mój bufor był naprawdę dużym obrazem JPEG o rozmiarze 2,4 Mb.

Konstantin Smolianin
źródło
13

( Aktualizacja Proszę zobaczyć drugą połowę tej odpowiedzi, gdzie (mam nadzieję) podałem bardziej kompletne rozwiązanie).

Natknąłem się również na ten problem, następujące prace działają dla mnie w FF 6 (w jednym kierunku):

var buf = new ArrayBuffer( 10 );
var view = new Uint8Array( buf );
view[ 3 ] = 4;
alert(Array.prototype.slice.call(view).join(""));

Niestety, oczywiście kończy się reprezentacją tekstową ASCII wartości w tablicy, a nie znaków. Jednak nadal (powinna) być znacznie wydajniejsza niż pętla. na przykład. W powyższym przykładzie wynikiem jest 0004000000zamiast kilku zerowych znaków i chr (4).

Edytować:

Po zapoznaniu się z MDC tutaj możesz utworzyć ArrayBufferz Array:

var arr = new Array(23);
// New Uint8Array() converts the Array elements
//  to Uint8s & creates a new ArrayBuffer
//  to store them in & a corresponding view.
//  To get at the generated ArrayBuffer,
//  you can then access it as below, with the .buffer property
var buf = new Uint8Array( arr ).buffer;

Aby odpowiedzieć na oryginalne pytanie, pozwala to na konwersję ArrayBuffer<-> Stringw następujący sposób:

var buf, view, str;
buf = new ArrayBuffer( 256 );
view = new Uint8Array( buf );

view[ 0 ] = 7; // Some dummy values
view[ 2 ] = 4;

// ...

// 1. Buffer -> String (as byte array "list")
str = bufferToString(buf);
alert(str); // Alerts "7,0,4,..."

// 1. String (as byte array) -> Buffer    
buf = stringToBuffer(str);
alert(new Uint8Array( buf )[ 2 ]); // Alerts "4"

// Converts any ArrayBuffer to a string
//  (a comma-separated list of ASCII ordinals,
//  NOT a string of characters from the ordinals
//  in the buffer elements)
function bufferToString( buf ) {
    var view = new Uint8Array( buf );
    return Array.prototype.join.call(view, ",");
}
// Converts a comma-separated ASCII ordinal string list
//  back to an ArrayBuffer (see note for bufferToString())
function stringToBuffer( str ) {
    var arr = str.split(",")
      , view = new Uint8Array( arr );
    return view.buffer;
}

Dla wygody jest tutaj functionkonwersja surowego Unicode Stringna ArrayBuffer(działa tylko ze znakami ASCII / jednobajtowymi)

function rawStringToBuffer( str ) {
    var idx, len = str.length, arr = new Array( len );
    for ( idx = 0 ; idx < len ; ++idx ) {
        arr[ idx ] = str.charCodeAt(idx) & 0xFF;
    }
    // You may create an ArrayBuffer from a standard array (of values) as follows:
    return new Uint8Array( arr ).buffer;
}

// Alerts "97"
alert(new Uint8Array( rawStringToBuffer("abc") )[ 0 ]);

Powyższe umożliwia przejście od ArrayBuffer-> Stringi powrót do ArrayBufferponownie, gdzie ciąg może być przechowywany np. .localStorage:)

Mam nadzieję że to pomoże,

Dan

Dan Phillimore
źródło
1
Nie sądzę, że jest to wydajna metoda (pod względem czasu lub miejsca) i jest to bardzo nietypowy sposób przechowywania danych binarnych.
kpozin
@kpozin: O ile mi wiadomo, nie ma innego sposobu przechowywania danych binarnych w localStorage
Dan Phillimore
1
Co z użyciem kodowania base64?
Nick Sotiros
13

W przeciwieństwie do rozwiązań tutaj musiałem przekonwertować dane do / z UTF-8. W tym celu zakodowałem następujące dwie funkcje za pomocą (un) escape / (en) decodeURIComponent trick. Nie marnują pamięci, alokują 9-krotność długości zakodowanego ciągu utf8, chociaż powinny one zostać odzyskane przez gc. Po prostu nie używaj ich do tekstu 100 MB.

function utf8AbFromStr(str) {
    var strUtf8 = unescape(encodeURIComponent(str));
    var ab = new Uint8Array(strUtf8.length);
    for (var i = 0; i < strUtf8.length; i++) {
        ab[i] = strUtf8.charCodeAt(i);
    }
    return ab;
}

function strFromUtf8Ab(ab) {
    return decodeURIComponent(escape(String.fromCharCode.apply(null, ab)));
}

Sprawdzanie, czy to działa:

strFromUtf8Ab(utf8AbFromStr('latinкирилицаαβγδεζηあいうえお'))
-> "latinкирилицаαβγδεζηあいうえお"
Moszewa
źródło
8

W przypadku, gdy masz dane binarne w ciągu (uzyskane z nodejs+ readFile(..., 'binary')lub cypress+ cy.fixture(..., 'binary')itp.), Nie możesz użyć TextEncoder. Obsługuje tylko utf8. Bajty z wartościami >= 128są zamieniane na 2 bajty.

ES2015:

a = Uint8Array.from(s, x => x.charCodeAt(0))

Uint8Array (33) [2, 134, 140, 186, 82, 70, 108, 182, 233, 40, 143, 247, 29, 76, 245, 206, 29, 87, 48, 160, 78, 225, 242 , 56, 236, 201, 80, 80, 152, 118, 92, 144, 48

s = String.fromCharCode.apply(null, a)

„ºRFl¶é (÷ LõÎW0 Náò8ìÉPPv \ 0”

użytkownik3832931
źródło
7

Odkryłem, że mam problemy z tym podejściem, głównie dlatego, że próbowałem zapisać dane wyjściowe do pliku, który nie został poprawnie zakodowany. Ponieważ wydaje się, że JS używa kodowania UCS-2 ( źródło , źródło ), musimy rozszerzyć to rozwiązanie o krok dalej, oto moje ulepszone rozwiązanie, które działa na mnie.

Nie miałem żadnych problemów z ogólnym tekstem, ale kiedy był to arabski lub koreański, plik wyjściowy nie miał wszystkich znaków, ale zamiast tego wyświetlał znaki błędów

Dane wyjściowe pliku: ","10k unit":"",Follow:"Õ©íüY‹","Follow %{screen_name}":"%{screen_name}U“’Õ©íü",Tweet:"ĤüÈ","Tweet %{hashtag}":"%{hashtag} ’ĤüÈY‹","Tweet to %{name}":"%{name}U“xĤüÈY‹"},ko:{"%{followers_count} followers":"%{followers_count}…X \Ì","100K+":"100Ì tÁ","10k unit":"Ì è",Follow:"\°","Follow %{screen_name}":"%{screen_name} Ø \°X0",K:"œ",M:"1Ì",Tweet:"¸","Tweet %{hashtag}":"%{hashtag}

Oryginalny: ","10k unit":"万",Follow:"フォローする","Follow %{screen_name}":"%{screen_name}さんをフォロー",Tweet:"ツイート","Tweet %{hashtag}":"%{hashtag} をツイートする","Tweet to %{name}":"%{name}さんへツイートする"},ko:{"%{followers_count} followers":"%{followers_count}명의 팔로워","100K+":"100만 이상","10k unit":"만 단위",Follow:"팔로우","Follow %{screen_name}":"%{screen_name} 님 팔로우하기",K:"천",M:"백만",Tweet:"트윗","Tweet %{hashtag}":"%{hashtag}

Wziąłem informacje z rozwiązania Dennisa i ten post znalazłem.

Oto mój kod:

function encode_utf8(s) {
  return unescape(encodeURIComponent(s));
}

function decode_utf8(s) {
  return decodeURIComponent(escape(s));
}

 function ab2str(buf) {
   var s = String.fromCharCode.apply(null, new Uint8Array(buf));
   return decode_utf8(decode_utf8(s))
 }

function str2ab(str) {
   var s = encode_utf8(str)
   var buf = new ArrayBuffer(s.length); 
   var bufView = new Uint8Array(buf);
   for (var i=0, strLen=s.length; i<strLen; i++) {
     bufView[i] = s.charCodeAt(i);
   }
   return bufView;
 }

To pozwala mi zapisać zawartość do pliku bez problemów z kodowaniem.

Jak to działa: Zasadniczo zajmuje pojedyncze 8-bajtowe fragmenty składające się na znak UTF-8 i zapisuje je jako pojedyncze znaki (dlatego zbudowana w ten sposób postać UTF-8 może składać się z 1-4 tych znaków). UTF-8 koduje znaki w formacie o długości od 1 do 4 bajtów. To, co tu robimy, to kodowanie żądła w komponencie URI, a następnie weź ten komponent i przetłumacz go na odpowiedni 8-bajtowy znak. W ten sposób nie tracimy informacji podanych przez znaki UTF8 o długości większej niż 1 bajt.

Dieghito
źródło
6

jeśli użyłeś ogromnej tablicy, arr.length=1000000 możesz użyć tego kodu, aby uniknąć problemów z wywołaniem zwrotnym stosu

function ab2str(buf) {
var bufView = new Uint16Array(buf);
var unis =""
for (var i = 0; i < bufView.length; i++) {
    unis=unis+String.fromCharCode(bufView[i]);
}
return unis
}

funkcja odwrotna mangini odpowiedź od góry

function str2ab(str) {
    var buf = new ArrayBuffer(str.length*2); // 2 bytes for each char
    var bufView = new Uint16Array(buf);
    for (var i=0, strLen=str.length; i<strLen; i++) {
        bufView[i] = str.charCodeAt(i);
    }
    return buf;
}
Elbaz
źródło
4

Oto nieco skomplikowany sposób robienia tego samego:

var string = "Blah blah blah", output;
var bb = new (window.BlobBuilder||window.WebKitBlobBuilder||window.MozBlobBuilder)();
bb.append(string);
var f = new FileReader();
f.onload = function(e) {
  // do whatever
  output = e.target.result;
}
f.readAsArrayBuffer(bb.getBlob());

Edycja: BlobBuilder od dawna jest przestarzały na korzyść konstruktora Blob, który nie istniał, kiedy pierwszy raz napisałem ten post. Oto zaktualizowana wersja. (I tak, zawsze był to bardzo głupi sposób na konwersję, ale to była tylko zabawa!)

var string = "Blah blah blah", output;
var f = new FileReader();
f.onload = function(e) {
  // do whatever
  output = e.target.result;
};
f.readAsArrayBuffer(new Blob([string]));
gengkev
źródło
3

Po zabawie z rozwiązaniem mangini do konwersji z ArrayBufferna String- ab2str(które jest najbardziej eleganckie i przydatne, jakie znalazłem - dzięki!), Miałem pewne problemy z obsługą dużych tablic. Mówiąc dokładniej, wywołanie String.fromCharCode.apply(null, new Uint16Array(buf));powoduje błąd:

arguments array passed to Function.prototype.apply is too large.

Aby go rozwiązać (obejście) postanowiłem obsłużyć dane wejściowe ArrayBufferw porcjach. Zmodyfikowane rozwiązanie to:

function ab2str(buf) {
   var str = "";
   var ab = new Uint16Array(buf);
   var abLen = ab.length;
   var CHUNK_SIZE = Math.pow(2, 16);
   var offset, len, subab;
   for (offset = 0; offset < abLen; offset += CHUNK_SIZE) {
      len = Math.min(CHUNK_SIZE, abLen-offset);
      subab = ab.subarray(offset, offset+len);
      str += String.fromCharCode.apply(null, subab);
   }
   return str;
}

Rozmiar porcji jest ustawiony na, 2^16ponieważ był to rozmiar, który działał w moim środowisku programistycznym. Ustawienie wyższej wartości spowodowało ponowne wystąpienie tego samego błędu. Można to zmienić, ustawiając CHUNK_SIZEzmienną na inną wartość. Ważne jest, aby mieć parzystą liczbę.

Uwaga dotycząca wydajności - nie wykonałem żadnych testów wydajności dla tego rozwiązania. Ponieważ jednak jest oparty na poprzednim rozwiązaniu i może obsługiwać duże tablice, nie widzę powodu, aby go nie używać.

Yinon
źródło
możesz użyć typedarray.subarray, aby uzyskać porcję w określonej pozycji i rozmiarze, to właśnie robię, aby odczytać nagłówki z formatów binarnych w js
Nikos M.,
2
  stringToArrayBuffer(byteString) {
    var byteArray = new Uint8Array(byteString.length);
    for (var i = 0; i < byteString.length; i++) {
      byteArray[i] = byteString.codePointAt(i);
    }
    return byteArray;
  }
  arrayBufferToString(buffer) {
    var byteArray = new Uint8Array(buffer);
    var byteString = '';
    for (var i = 0; i < byteArray.byteLength; i++) {
      byteString += String.fromCodePoint(byteArray[i]);
    }
    return byteString;
  }
Admirał
źródło
ten kod jest błędny, jeśli ciąg znaków zawiera znaki Unicode. przykład:arrayBufferToString(stringToArrayBuffer('🐴'))==='44'
xmcp
2

Dla node.js, a także dla przeglądarek korzystających z https://github.com/feross/buffer

function ab2str(buf: Uint8Array) {
  return Buffer.from(buf).toString('base64');
}
function str2ab(str: string) {
  return new Uint8Array(Buffer.from(str, 'base64'))
}

Uwaga: rozwiązania tutaj nie działały dla mnie. Muszę obsługiwać node.js i przeglądarki oraz po prostu serializować UInt8Array do łańcucha. Mógłbym serializować to jako liczbę [], ale to zajmuje niepotrzebne miejsce. Dzięki temu rozwiązaniu nie muszę się martwić kodowaniem, ponieważ jest to base64. Na wypadek, gdyby inni zmagali się z tym samym problemem ... Moje dwa centy

rakbero
źródło
2

Powiedzmy, że masz tablicę binaryStr:

let text = String.fromCharCode.apply(null, new Uint8Array(binaryStr));

a następnie przypisujesz tekst do stanu.

Hilal Aissani
źródło
1

„Natywny” ciąg binarny zwracany przez atob () to tablica 1-bajtowa na znak.

Dlatego nie powinniśmy przechowywać 2 bajtów w postaci.

var arrayBufferToString = function(buffer) {
  return String.fromCharCode.apply(null, new Uint8Array(buffer));
}

var stringToArrayBuffer = function(str) {
  return (new Uint8Array([].map.call(str,function(x){return x.charCodeAt(0)}))).buffer;
}
wdhwg001
źródło
1

Tak:

const encstr = (`TextEncoder` in window) ? new TextEncoder().encode(str) : Uint8Array.from(str, c => c.codePointAt(0));
Denis Giffeler
źródło
0

Odradzam używanie przestarzałych interfejsów API, takich jak BlobBuilder

Obiekt BlobBuilder od dawna jest nieaktualny przez obiekt Blob. Porównaj kod w odpowiedzi Dennisa - w przypadku użycia BlobBuilder - z kodem poniżej:

function arrayBufferGen(str, cb) {

  var b = new Blob([str]);
  var f = new FileReader();

  f.onload = function(e) {
    cb(e.target.result);
  }

  f.readAsArrayBuffer(b);

}

Zauważ, o ile czystsze i mniej wzdęte jest to w porównaniu do przestarzałej metody ... Tak, to zdecydowanie coś do rozważenia tutaj.

realkstrawn93
źródło
To znaczy tak, ale ten konstruktor obiektów Blob nie był tak naprawdę użyteczny w 2012 roku;)
gengkev
0

Użyłem tego i działa dla mnie.

function arrayBufferToBase64( buffer ) {
    var binary = '';
    var bytes = new Uint8Array( buffer );
    var len = bytes.byteLength;
    for (var i = 0; i < len; i++) {
        binary += String.fromCharCode( bytes[ i ] );
    }
    return window.btoa( binary );
}



function base64ToArrayBuffer(base64) {
    var binary_string =  window.atob(base64);
    var len = binary_string.length;
    var bytes = new Uint8Array( len );
    for (var i = 0; i < len; i++)        {
        bytes[i] = binary_string.charCodeAt(i);
    }
    return bytes.buffer;
}
Elias Vargas
źródło