Mengubah antara string dan ArrayBuffers

264

Apakah ada teknik yang diterima secara umum untuk mengkonversi string JavaScript secara efisien ke ArrayBuffers dan sebaliknya? Secara khusus, saya ingin dapat menulis konten ArrayBuffer localStoragedan membacanya kembali.

kpozin
sumber
1
Saya tidak punya pengalaman dalam hal ini, tetapi menilai dari dokumentasi API ( khronos.org/registry/typedarray/specs/latest ) jika Anda membangunnya Int8Array ArrayBufferView, mungkin saja menggunakan notasi braket untuk menyalin karakter string[i] = buffer[i]dan sebaliknya.
FK82
2
@ FK82, yang terlihat seperti pendekatan yang masuk akal (menggunakan Uint16Arrays untuk karakter 16-bit JS), tetapi string JavaScript tidak dapat diubah sehingga Anda tidak dapat menetapkan secara langsung ke posisi karakter. Saya masih perlu menyalin String.fromCharCode(x)dari setiap nilai dalam Uint16Arrayuntuk normal Arraydan kemudian memanggil .join()pada Array.
kpozin
@kpozin: Benar, tidak terlalu memikirkannya.
FK82
5
@ kpozin Ternyata sebagian besar mesin JS modern telah mengoptimalkan penggabungan string ke titik di mana lebih murah untuk hanya digunakan string += String.fromCharCode(buffer[i]);. Tampaknya aneh bahwa tidak akan ada metode bawaan untuk mengkonversi antara string dan array yang diketik. Mereka harus tahu sesuatu seperti ini akan muncul.
unduh
arrayBuffer.toString () berfungsi dengan baik untuk saya.
Warga negara terhubung

Jawaban:

128

Pembaruan 2016 - lima tahun sekarang ada metode baru dalam spesifikasi (lihat dukungan di bawah) untuk mengkonversi antara string dan array yang diketik menggunakan pengkodean yang tepat.

TextEncoder

The TextEncodermewakili :

The TextEncoderantarmuka merupakan encoder untuk metode tertentu, yang merupakan pengkodean karakter tertentu, seperti utf-8,iso-8859-2, koi8, cp1261, gbk, ... Encoder mengambil aliran poin kode sebagai input dan memancarkan aliran byte.

Ubah catatan sejak tulisan di atas ditulis: (ibid.)

Catatan: Firefox, Chrome, dan Opera dulu memiliki dukungan untuk jenis penyandian selain utf-8 (seperti utf-16, iso-8859-2, koi8, cp1261, dan gbk). Pada Firefox 48 [...], Chrome 54 [...] dan Opera 41, tidak ada tipe penyandian lain yang tersedia selain utf-8, agar sesuai dengan spesifikasi. *

*) Spesifikasi yang diperbarui (W3) dan di sini (whatwg).

Setelah membuat instance dari TextEncoderitu akan mengambil string dan menyandikannya menggunakan parameter pengkodean yang diberikan:

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

Anda kemudian tentu saja menggunakan .bufferparameter pada hasil Uint8Arrayuntuk mengkonversi lapisan bawah ArrayBufferke tampilan yang berbeda jika diperlukan.

Pastikan saja bahwa karakter dalam string mematuhi skema penyandian, misalnya, jika Anda menggunakan karakter di luar rentang UTF-8 dalam contoh mereka akan dikodekan menjadi dua byte, bukan satu.

Untuk penggunaan umum, Anda akan menggunakan pengkodean UTF-16 untuk hal-hal seperti localStorage.

TextDecoder

Demikian juga, proses sebaliknya menggunakanTextDecoder :

The TextDecoderantarmuka merupakan decoder untuk metode tertentu, yang merupakan pengkodean karakter tertentu, seperti utf-8, iso-8859-2, koi8, cp1261, gbk, ... Sebuah decoder membutuhkan aliran byte sebagai masukan dan memancarkan aliran poin kode.

Semua tipe decoding yang tersedia dapat ditemukan di sini .

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

Perpustakaan MDN StringView

Alternatif untuk ini adalah dengan menggunakan StringViewperpustakaan (berlisensi sebagai lgpl-3.0) yang tujuannya adalah:

  • untuk membuat antarmuka seperti-C untuk string (yaitu, array kode karakter - sebuah ArrayBufferView dalam JavaScript) berdasarkan pada antarmuka JavaScript ArrayBuffer
  • untuk membuat pustaka yang sangat extensible yang dapat diperluas oleh siapa pun dengan menambahkan metode ke objek StringView.prototype
  • untuk membuat kumpulan metode untuk objek seperti string (sejak sekarang: stringViews) yang bekerja secara ketat pada array angka daripada membuat string JavaScript baru yang tidak dapat diubah
  • untuk bekerja dengan penyandian Unicode selain dari JavaScript standar UTF-16 DOMStrings

memberi lebih banyak fleksibilitas. Namun, itu akan mengharuskan kita untuk menautkan atau menyematkan pustaka ini saat TextEncoder/ TextDecodersedang built-in di browser modern.

Dukung

Per Juli / 2018:

TextEncoder (Eksperimental, Di Jalur Standar)

 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

sumber
2
Tidak ada dukungan untuk TextDecoder dari IE & Edge: caniuse.com/#search=TextDecoder
Andrei Damian-Fekete
1
Menurut MS sedang dalam pengembangan: developer.microsoft.com/en-us/microsoft-edge/platform/status/…
Maurice Müller
Tidak ada dukungan untuk Safari Mobile (ios) di 2018-04-18: developer.mozilla.org/en-US/docs/Web/API/TextDecoder
pria perunggu
One-liner: var encoder = 'TextEncoder' in window ? new TextEncoder() : {encode: function(str){return Uint8Array.from(str, function(c){return c.codePointAt(0);});}};jadi Anda bisavar array = encoder.encode('hello');
Yeti
1
Masalahnya TextEncoderadalah bahwa jika Anda memiliki data biner dalam sebuah string (seperti, gambar), Anda tidak ingin menggunakan TextEncoder(tampaknya). Karakter dengan titik kode yang lebih besar dari 127 menghasilkan dua byte. Mengapa saya memiliki data biner dalam sebuah string? cy.fixture(NAME, 'binary')( cypress) menghasilkan string.
x-yuri
176

Meskipun Dennis dan solusi gengkev menggunakan Blob / FileReader bekerja, saya tidak akan menyarankan mengambil pendekatan itu. Ini adalah pendekatan async untuk masalah sederhana, dan itu jauh lebih lambat daripada solusi langsung. Saya telah membuat posting di html5rocks dengan solusi yang lebih sederhana dan (jauh lebih cepat): http://updates.html5rocks.com/2012/06/Bagaimana- untuk- mengkonversi-ArrayBuffer-to-and-from-String

Dan solusinya adalah:

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

EDIT:

The API Encoding membantu memecahkan string konversi masalah. Lihat respons dari Jeff Posnik di Html5Rocks.com ke artikel asli di atas.

Kutipan:

API Pengkodean membuatnya mudah untuk menerjemahkan antara byte mentah dan string JavaScript asli, terlepas dari mana dari banyak pengkodean standar yang harus Anda gunakan.

<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
sumber
16
Sayangnya komentar saya tentang html5rocks belum disetujui. Karenanya jawaban singkat di sini. Saya masih berpikir, ini bukan cara yang benar, karena Anda kehilangan banyak karakter, terutama karena sebagian besar halaman dalam pengkodean UTF-8 hari ini. Di satu sisi, untuk karakter yang lebih khusus (katakanlah Asia), fungsi charCodeAt mengembalikan nilai 4-Byte, sehingga mereka akan dipotong. Di sisi lain, karakter bahasa Inggris yang sederhana akan menumbuhkan ArrayBuffer dua kali (Anda menggunakan 2 Byte untuk setiap karakter 1-Byte). Bayangkan mengirim teks bahasa Inggris melalui WebSocket, itu akan membutuhkan dua kali waktu (tidak baik dalam lingkungan waktu nyata).
Dennis
9
Tiga contoh: (1) This is a cool text!20 Byte di UTF8 - 40 Byte di Unicode. (2) ÄÖÜ6 Bytes di UTF8 - 6 Bytes di Unicode. (3) ☐☑☒9 Bytes di UTF8 - 6 Bytes di Unicode. Jika Anda ingin menyimpan string sebagai file UTF8 (melalui Blob dan File Writer API), Anda tidak dapat menggunakan 2 metode ini, karena ArrayBuffer akan berada di Unicode dan bukan di UTF8.
Dennis
3
Saya mendapat kesalahan: UnEught RangeError: Ukuran stack panggilan maksimum terlampaui. Apa yang bisa menjadi masalah?
Yakub
6
@Dennis - String JS menggunakan UCS2, bukan UTF8 (atau bahkan UTF16) - yang berarti charCodeAt () selalu mengembalikan nilai 0 -> 65535. Setiap titik kode UTF-8 yang membutuhkan 4 byte ujung akan diwakili dengan pasangan pengganti (lihat en.wikipedia .org / wiki / ... ) - yaitu dua nilai UCS2 16-bit yang terpisah.
broofa
6
@ jacob - Saya percaya kesalahannya adalah karena ada batasan panjang array yang dapat diteruskan ke metode apply (). Misalnya String.fromCharCode.apply(null, new Uint16Array(new ArrayBuffer(246300))).lengthbekerja untuk saya di Chrome, tetapi jika Anda menggunakan 246301, saya mendapatkan pengecualian RangeError Anda
broofa
71

Anda dapat menggunakan TextEncoderdan TextDecoderdari standar Pengkodean , yang diisi dengan polyfilled oleh perpustakaan stringencoding , untuk mengonversi string ke dan dari ArrayBuffers:

var uint8array = new TextEncoder().encode(string);
var string = new TextDecoder(encoding).decode(uint8array);
Ilmari Heikkinen
sumber
2
Omong-omong, ini tersedia di Firefox secara default: developer.mozilla.org/en-US/docs/Web/API/TextDecoder.decode
Joel Richard
2
Acungan jempol untuk API baru yang jauh lebih baik daripada solusi aneh!
Tomáš Zato - Reinstate Monica
1
Ini tidak akan berfungsi dengan semua jenis karakter di luar sana.
David
5
npm install text-encoding, var textEncoding = require('text-encoding'); var TextDecoder = textEncoding.TextDecoder;. Tidak, terima kasih.
Evan Hu
menggerutu ... jika saya memiliki arraybuffer yang ada, saya ingin menulis string ke saya kira saya harus mengambil uint8array dan menyalinnya 2 kali ??
shaunc
40

Gumpalan jauh lebih lambat daripada String.fromCharCode(null,array);

tapi itu gagal jika buffer array terlalu besar. Solusi terbaik yang saya temukan adalah menggunakanString.fromCharCode(null,array); dan membaginya menjadi operasi yang tidak akan meniup stack, tetapi lebih cepat dari satu karakter sekaligus.

Solusi terbaik untuk buffer array besar adalah:

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;

}

Saya menemukan ini sekitar 20 kali lebih cepat daripada menggunakan gumpalan. Ini juga berfungsi untuk string besar lebih dari 100MB.

Ryan Weinstein
sumber
3
Kita harus pergi dengan solusi ini. Karena ini memecahkan satu kasus penggunaan lebih dari yang digunakan
sam
24

Berdasarkan jawaban gengkev, saya membuat fungsi untuk kedua cara, karena BlobBuilder dapat menangani String dan 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());
}

dan

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

Tes sederhana:

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
sumber
Di arrayBuffer2String (), apakah Anda bermaksud memanggil callback (...) alih-alih console.log ()? Kalau tidak, argumen panggilan balik tidak digunakan.
Dan Phillimore
Ini sepertinya cara untuk pergi - terima kasih genkev dan Dennis. Agak konyol bahwa tidak ada cara sinkron untuk mencapai ini, tapi apa yang bisa Anda lakukan ...
kpozin
JavaScript adalah utas tunggal. Oleh karena itu FileReader tidak sinkron karena dua alasan: (1) tidak akan memblokir eksekusi JavaScript lainnya saat memuat file (besar) (bayangkan aplikasi yang lebih kompleks) dan (2) tidak akan memblokir UI / Browser (masalah umum dengan lama mengeksekusi kode JS). Banyak API yang tidak sinkron. Bahkan di XMLHttpRequest 2 sinkron dihapus.
Dennis
Saya benar-benar berharap ini akan bekerja untuk saya, tetapi konversi dari string ke ArrayBuffer tidak berfungsi dengan baik. Saya membuat ArrayBuffer dengan nilai 256, dan dapat mengubahnya menjadi string dengan panjang 256. Tetapi kemudian jika saya mencoba mengubahnya kembali menjadi ArrayBuffer - tergantung pada isi ArrayBuffer awal saya - saya mengeluarkan 376 elemen. Jika Anda ingin mencoba mereproduksi masalah saya, saya memperlakukan ArrayBuffer saya sebagai kisi 16x16 dalam Uint8Array, dengan nilai yang dihitung saat a[y * w + x] = (x + y) / 2 * 16; saya sudah mencoba getBlob("x"), dengan banyak mimetipe yang berbeda - tidak beruntung.
Matt Cruikshank
18
BlobBuilder tidak digunakan lagi di browser yang lebih baru. Ubah new BlobBuilder(); bb.append(buf);ke new Blob([buf]), masukkan ArrayBuffer di fungsi kedua ke UintArray via new UintArray(buf)(atau apa pun yang sesuai untuk tipe data yang mendasarinya), lalu singkirkan getBlob()panggilan. Akhirnya, untuk kebersihan, ganti nama bb menjadi gumpalan karena itu bukan BlobBuilder lagi.
sowbug
18

Semua berikut ini adalah tentang mendapatkan string biner dari buffer array

Saya sarankan untuk tidak menggunakan

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

karena itu

  1. menabrak buffer besar (seseorang menulis tentang "sihir" ukuran 246300 tapi aku mengertiMaximum call stack size exceeded kesalahan pada buffer 120000 byte (Chrome 29))
  2. ini memiliki kinerja yang sangat buruk (lihat di bawah)

Jika Anda benar-benar membutuhkan solusi sinkron, gunakan sesuatu seperti

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

lambat seperti yang sebelumnya tetapi bekerja dengan benar. Tampaknya pada saat penulisan ini tidak ada solusi sinkron yang cukup cepat untuk masalah itu (semua perpustakaan yang disebutkan dalam topik ini menggunakan pendekatan yang sama untuk fitur sinkron mereka).

Tapi yang saya sangat merekomendasikan adalah menggunakan pendekatan 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' }));
}

satu-satunya kelemahan (tidak untuk semua) adalah asinkron . Dan itu sekitar 8-10 kali lebih cepat daripada solusi sebelumnya! (Beberapa perincian: solusi sinkron pada lingkungan saya membutuhkan 950-1050 ms untuk buffer 2,4Mb tetapi solusi dengan FileReader memiliki kali sekitar 100-120 ms untuk jumlah data yang sama. Dan saya telah menguji keduanya solusi sinkron pada buffer 100Kb dan mereka telah mengambil waktu yang hampir bersamaan, sehingga loop tidak jauh lebih lambat menggunakan 'apply'.)

BTW di sini: Bagaimana cara mengubah ArrayBuffer ke dan dari String author membandingkan dua pendekatan seperti saya dan mendapatkan hasil yang benar-benar berlawanan ( kode pengujiannya ada di sini ) Mengapa hasil yang sangat berbeda? Mungkin karena string pengujiannya yang panjangnya 1Kb (ia menyebutnya "veryLongStr"). Buffer saya adalah gambar JPEG yang sangat besar dengan ukuran 2,4 MB.

Konstantin Smolyanin
sumber
13

( Pembaruan Silakan lihat bagian 2 dari jawaban ini, di mana saya (semoga) memberikan solusi yang lebih lengkap.)

Saya juga mengalami masalah ini, berikut ini berfungsi untuk saya di FF 6 (untuk satu arah):

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

Sayangnya, tentu saja, Anda berakhir dengan representasi teks ASCII dari nilai-nilai dalam array, bukan karakter. Ini masih (harus) jauh lebih efisien daripada loop. misalnya. Untuk contoh di atas, hasilnya adalah 0004000000, alih-alih beberapa karakter nol & satu chr (4).

Edit:

Setelah melihat MDC di sini , Anda dapat membuat ArrayBufferdari Arraysebagai berikut:

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;

Untuk menjawab pertanyaan awal Anda, ini memungkinkan Anda mengonversi ArrayBuffer<-> Stringsebagai berikut:

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

Untuk kenyamanan, berikut adalah functionuntuk mengubah Unicode mentah Stringmenjadi ArrayBuffer(hanya akan bekerja dengan karakter ASCII / satu byte)

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

Di atas memungkinkan Anda untuk pergi dari ArrayBuffer-> String& kembali ArrayBufferlagi, di mana string dapat disimpan dalam mis. .localStorage:)

Semoga ini membantu,

Dan

Dan Phillimore
sumber
1
Saya tidak berpikir ini adalah metode yang efisien (dalam hal waktu atau ruang), dan ini adalah cara yang sangat tidak biasa untuk menyimpan data biner.
kpozin
@kpozin: Sejauh yang saya tahu, tidak ada cara lain untuk menyimpan data biner di localStorage
Dan Phillimore
1
Bagaimana dengan menggunakan pengkodean base64?
Nick Sotiros
13

Tidak seperti solusi di sini, saya perlu mengkonversi ke / dari data UTF-8. Untuk tujuan ini, saya mengkodekan dua fungsi berikut, menggunakan (un) escape / (en) decodeURIComponent trick. Mereka cukup boros memori, mengalokasikan 9 kali panjang utf8-string yang dikodekan, meskipun itu harus dipulihkan oleh gc. Hanya saja, jangan menggunakannya untuk teks 100mb.

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

Memeriksa apakah itu berfungsi:

strFromUtf8Ab(utf8AbFromStr('latinкирилицаαβγδεζηあいうえお'))
-> "latinкирилицаαβγδεζηあいうえお"
Moshev
sumber
8

Jika Anda memiliki data biner dalam sebuah string (diperoleh dari nodejs+ readFile(..., 'binary'), atau cypress+ cy.fixture(..., 'binary'), dll), Anda tidak dapat menggunakan TextEncoder. Hanya mendukung utf8. Bytes dengan nilai >= 128masing-masing diubah menjadi 2 byte.

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, 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"

pengguna3832931
sumber
7

Saya menemukan saya punya masalah dengan pendekatan ini, pada dasarnya karena saya mencoba untuk menulis output ke file dan itu tidak dikodekan dengan benar. Karena JS tampaknya menggunakan pengkodean UCS-2 ( sumber , sumber ), kita perlu memperluas solusi ini selangkah lebih maju, inilah solusi saya yang ditingkatkan yang bekerja untuk saya.

Saya tidak mengalami kesulitan dengan teks umum, tetapi ketika itu ke bahasa Arab atau Korea, file output tidak memiliki semua karakter tetapi sebaliknya menunjukkan karakter kesalahan

Output file: ","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}

Asli: ","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}

Saya mengambil informasi dari solusi dennis dan posting ini saya temukan.

Ini kode saya:

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

Ini memungkinkan saya untuk menyimpan konten ke file tanpa masalah penyandian.

Cara kerjanya: Pada dasarnya dibutuhkan potongan 8-byte tunggal yang menyusun karakter UTF-8 dan menyimpannya sebagai karakter tunggal (oleh karena itu karakter UTF-8 yang dibangun dengan cara ini, dapat disusun oleh 1-4 karakter ini). UTF-8 mengkodekan karakter dalam format yang panjangnya bervariasi dari 1 hingga 4 byte. Apa yang kami lakukan di sini adalah mengkodekan sengatan dalam komponen URI dan kemudian mengambil komponen ini dan menerjemahkannya dalam karakter 8 byte yang sesuai. Dengan cara ini kami tidak kehilangan informasi yang diberikan oleh karakter UTF8 yang panjangnya lebih dari 1 byte.

Dieghito
sumber
6

jika Anda menggunakan contoh array besar arr.length=1000000 Anda dapat kode ini untuk menghindari masalah panggilan balik stack

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
}

membalikkan fungsi mangini menjawab dari atas

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
sumber
4

Nah, inilah cara yang agak berbelit-belit untuk melakukan hal yang sama:

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

Sunting: BlobBuilder telah lama ditinggalkan demi konstruktor Blob, yang tidak ada ketika saya pertama kali menulis posting ini. Ini versi terbaru. (Dan ya, ini selalu menjadi cara yang sangat konyol untuk melakukan konversi, tapi itu hanya untuk bersenang-senang!)

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
sumber
3

Setelah bermain dengan solusi mangini untuk mengkonversi dari ArrayBufferke String- ab2str(yang merupakan yang paling elegan dan berguna yang saya temukan - terima kasih!), Saya memiliki beberapa masalah ketika menangani array besar. Lebih spesifik, panggilan String.fromCharCode.apply(null, new Uint16Array(buf));melempar kesalahan:

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

Untuk mengatasinya (memotong) saya telah memutuskan untuk menangani input ArrayBufferdalam potongan. Jadi solusi yang dimodifikasi adalah:

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

Ukuran chunk diatur 2^16karena ini adalah ukuran yang saya temukan untuk bekerja di lanskap pengembangan saya. Menetapkan nilai yang lebih tinggi menyebabkan kesalahan yang sama terulang kembali. Itu dapat diubah dengan mengatur CHUNK_SIZEvariabel ke nilai yang berbeda. Penting untuk memiliki angka genap.

Catatan tentang kinerja - Saya tidak melakukan tes kinerja untuk solusi ini. Namun, karena ini didasarkan pada solusi sebelumnya, dan dapat menangani array yang besar, saya tidak melihat alasan mengapa tidak menggunakannya.

yinon
sumber
Anda dapat menggunakan typedarray.subarray untuk mendapatkan bongkahan pada posisi dan ukuran yang ditentukan, ini adalah apa yang saya lakukan untuk membaca header dari format biner di js
Nikos M.
2

Lihat di sini: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays/StringView (antarmuka mirip-C untuk string yang didasarkan pada antarmuka JavaScript ArrayBuffer)

pascov
sumber
2
Kode itu berada di bawah GPLv3. Saya pikir itu sangat tidak profesional dari Mozilla untuk bahkan memadukan kode itu dengan dokumentasi yang sesuai standar mereka.
user239558
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;
  }
Kagumi
sumber
kode ini bermasalah jika string berisi karakter unicode. contoh:arrayBufferToString(stringToArrayBuffer('🐴'))==='44'
xmcp
2

Untuk node.js dan juga untuk browser menggunakan 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'))
}

Catatan: Solusi di sini tidak bekerja untuk saya. Saya perlu mendukung node.js dan browser dan hanya membuat serial UInt8Array ke string. Saya bisa membuat cerita bersambung sebagai angka [] tapi itu menempati ruang yang tidak perlu. Dengan solusi itu saya tidak perlu khawatir tentang pengkodean karena itu base64. Untuk berjaga-jaga jika orang lain bergumul dengan masalah yang sama ... Dua sen saya

kankerbero
sumber
2

Katakanlah Anda memiliki arrayBuffer binaryStr:

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

dan kemudian Anda menetapkan teks ke negara.

Hilal Aissani
sumber
1

String biner "asli" yang dikembalikan atob () adalah Array 1-byte-per-karakter.

Jadi kita seharusnya tidak menyimpan 2 byte ke dalam karakter.

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
sumber
1

Iya:

const encstr = (`TextEncoder` in window) ? new TextEncoder().encode(str) : Uint8Array.from(str, c => c.codePointAt(0));
Denis Giffeler
sumber
0

Saya sarankan TIDAK menggunakan API yang sudah usang seperti BlobBuilder

BlobBuilder telah lama ditinggalkan oleh objek Blob. Bandingkan kode dalam jawaban Dennis - di mana BlobBuilder digunakan - dengan kode di bawah ini:

function arrayBufferGen(str, cb) {

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

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

  f.readAsArrayBuffer(b);

}

Perhatikan seberapa banyak ini lebih bersih dan lebih sedikit kembung dibandingkan dengan metode yang sudah usang ... Ya, ini pasti sesuatu yang perlu dipertimbangkan di sini.

ditarik kembali93
sumber
Maksud saya, ya, tetapi konstruktor Blob itu tidak dapat digunakan kembali pada tahun 2012;)
gengkev
0

Saya menggunakan ini dan bekerja untuk saya.

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
sumber