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 localStorage
i odczytać ją ponownie.
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 localStorage
i odczytać ją ponownie.
Int8Array
ArrayBufferView
, można po prostu użyć notacji nawiasowej do skopiowania znakówstring[i] = buffer[i]
i odwrotnie.Uint16Array
s 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 wUint16Array
celu normalnyArray
, a następnie zadzwonić.join()
naArray
.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.Odpowiedzi:
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
TextEncoder
Reprezentuje :Zmień notatkę, ponieważ napisano powyższe: (ibid.)
*) Zaktualizowano specyfikacje (W3) i tutaj (whatwg).
Po utworzeniu instancji
TextEncoder
zajmie ciąg znaków i zakoduje go przy użyciu danego parametru kodowania:Następnie można oczywiście użyć
.buffer
parametru wynikowego,Uint8Array
aby wArrayBuffer
razie 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 wykorzystuje
TextDecoder
:Wszystkie dostępne typy dekodowania można znaleźć tutaj .
Biblioteka MDN StringView
Alternatywą dla nich jest użycie
StringView
biblioteki (licencjonowanej jako lgpl-3.0), której celem jest:dając znacznie większą elastyczność. Wymagałoby to jednak od nas linkowania do tej biblioteki lub osadzania jej podczas, gdy
TextEncoder
/TextDecoder
jest wbudowane w nowoczesne przeglądarki.Wsparcie
Według stanu na lipiec / 2018 r .:
TextEncoder
(Eksperymentalny, na standardowym torze)źródło
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');
TextEncoder
to, ż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.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:
EDYTOWAĆ:
Kodowanie API ułatwia rozwiązywanie konwersja ciąg problem. Sprawdź odpowiedź Jeffa Posnika na Html5Rocks.com na powyższy oryginalny artykuł.
Fragment:
źródło
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.String.fromCharCode.apply(null, new Uint16Array(new ArrayBuffer(246300))).length
Działa dla mnie w Chrome, ale jeśli zamiast tego użyjesz 246301, otrzymam twój wyjątekMożesz użyć
TextEncoder
iTextDecoder
ze standardu kodowania , który jest wypełniany przez bibliotekę kodowania łańcuchów , do konwersji łańcucha na i z ArrayBuffers:źródło
npm install text-encoding
,var textEncoding = require('text-encoding'); var TextDecoder = textEncoding.TextDecoder;
. Nie, dziękuję.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:
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.
źródło
Na podstawie odpowiedzi gengkev utworzyłem funkcje na dwa sposoby, ponieważ BlobBuilder może obsługiwać String i ArrayBuffer:
i
Prosty test:
źródło
a[y * w + x] = (x + y) / 2 * 16;
, jak próbowałemgetBlob("x")
, z wieloma różnymi typami mimetów - bez powodzenia.new BlobBuilder(); bb.append(buf);
nanew Blob([buf])
, rzutuj ArrayBuffer w drugiej funkcji na UintArray przeznew 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.Wszystkie poniższe informacje dotyczą pobierania ciągów binarnych z buforów tablic
Polecam nie używać
ponieważ to
Maximum call stack size exceeded
błąd na buforze 120000 bajtów (Chrome 29))Jeśli potrzebujesz dokładnie rozwiązania synchronicznego, użyj czegoś takiego
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
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.
źródło
( 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):
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
0004000000
zamiast kilku zerowych znaków i chr (4).Edytować:
Po zapoznaniu się z MDC tutaj możesz utworzyć
ArrayBuffer
zArray
:Aby odpowiedzieć na oryginalne pytanie, pozwala to na konwersję
ArrayBuffer
<->String
w następujący sposób:Dla wygody jest tutaj
function
konwersja surowego UnicodeString
naArrayBuffer
(działa tylko ze znakami ASCII / jednobajtowymi)Powyższe umożliwia przejście od
ArrayBuffer
->String
i powrót doArrayBuffer
ponownie, gdzie ciąg może być przechowywany np..localStorage
:)Mam nadzieję że to pomoże,
Dan
źródło
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.
Sprawdzanie, czy to działa:
źródło
W przypadku, gdy masz dane binarne w ciągu (uzyskane z
nodejs
+readFile(..., 'binary')
lubcypress
+cy.fixture(..., 'binary')
itp.), Nie możesz użyćTextEncoder
. Obsługuje tylkoutf8
. Bajty z wartościami>= 128
są zamieniane na 2 bajty.ES2015:
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
„ºRFl¶é (÷ LõÎW0 Náò8ìÉPPv \ 0”
źródło
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:
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.
źródło
jeśli użyłeś ogromnej tablicy,
arr.length=1000000
możesz użyć tego kodu, aby uniknąć problemów z wywołaniem zwrotnym stosufunkcja odwrotna mangini odpowiedź od góry
źródło
Oto nieco skomplikowany sposób robienia tego samego:
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!)
źródło
Po zabawie z rozwiązaniem mangini do konwersji z
ArrayBuffer
naString
-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łanieString.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
ArrayBuffer
w porcjach. Zmodyfikowane rozwiązanie to:Rozmiar porcji jest ustawiony na,
2^16
ponieważ 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ącCHUNK_SIZE
zmienną 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ć.
źródło
Zobacz tutaj: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays/StringView (podobny do C interfejs dla ciągów opartych na interfejsie JavaScript ArrayBuffer)
źródło
źródło
arrayBufferToString(stringToArrayBuffer('🐴'))==='44'
Dla node.js, a także dla przeglądarek korzystających z https://github.com/feross/buffer
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
źródło
Powiedzmy, że masz tablicę binaryStr:
a następnie przypisujesz tekst do stanu.
źródło
„Natywny” ciąg binarny zwracany przez atob () to tablica 1-bajtowa na znak.
Dlatego nie powinniśmy przechowywać 2 bajtów w postaci.
źródło
Tak:
źródło
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:
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.
źródło
Zobacz https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder/decode
źródło
Użyłem tego i działa dla mnie.
źródło