Перетворення між рядками та ArrayBuffers


264

Чи існує загальноприйнята методика ефективного перетворення рядків JavaScript в ArrayBuffers і навпаки? Зокрема, я хотів би мати можливість записувати вміст ArrayBuffer localStorageі читати його назад.


1
Я не маю жодного досвіду в цьому, але, судячи з документації API ( khronos.org/registry/typedarray/specs/latest ), якщо ви будуєте, Int8Array ArrayBufferViewможливо, можна просто використовувати нотацію дужок для копіювання символів string[i] = buffer[i]та навпаки.
FK82

2
@ FK82, це виглядає як розумний підхід (використовуючи Uint16Arrays для 16-бітових символів JS), але рядки JavaScript незмінні, тому ви не можете призначати безпосередньо позицію символу. Мені все одно потрібно буде скопіювати String.fromCharCode(x)кожне значення в Uint16Arrayнормальне значення, Arrayа потім зателефонувати .join()на Array.
kpozin

@kpozin: Правда, насправді це не продумали.
FK82

5
@kpozin Виявляється, більшість сучасних двигунів JS оптимізували конкатенацію рядків до тієї точки, коли її дешевше просто використовувати string += String.fromCharCode(buffer[i]);. Здається дивним, що не було б вбудованих методів перетворення між рядками та набраними масивами. Вони мали знати, що щось подібне вийде.
завантажити

arrayBuffer.toString () працює для мене добре.
громадянин конн

Відповіді:


129

Оновлення 2016 - п’ять років тепер у специфікаціях є нові методи (див. Підтримку нижче) для перетворення між рядками та набраними масивами з використанням правильного кодування.

TextEncoder

TextEncoderпредставляє :

TextEncoderІнтерфейс являє собою кодер для конкретного методу, тобто кодує специфічний характер, як utf-8,iso-8859-2, koi8, cp1261, gbk, ... Кодер приймає потік кодових точок як вхідний і випромінює потік байтів.

Зміна примітки, оскільки було написано вище: (там же)

Примітка: Firefox, Chrome і Opera раніше підтримували типи кодування, крім utf-8 (наприклад, utf-16, iso-8859-2, koi8, cp1261 та gbk). Як і для Firefox 48 [...], Chrome 54 [...] та Opera 41, інші типи кодування, крім utf-8, не доступні для відповідності специфікації. *

*) Оновлені специфікації (W3) і тут (whatwg).

Після створення екземпляра TextEncoderвін займе рядок і кодує його за допомогою заданого параметра кодування:

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

Потім ви, звичайно, використовуєте .bufferпараметр на отриманому, Uint8Arrayщоб перетворити підкладку ArrayBufferв інший вигляд, якщо потрібно.

Просто переконайтесь, що символи в рядку дотримуються схеми кодування, наприклад, якщо ви використовуєте символи поза діапазоном UTF-8 у прикладі, вони будуть закодовані до двох байтів замість одного.

Для загального використання ви б використовували кодування UTF-16 для таких речей localStorage.

TextDecoder

Аналогічно, протилежний процес використовуєTextDecoder :

TextDecoderІнтерфейс являє декодер для конкретного методу, тобто кодує специфічний характер, як utf-8, iso-8859-2, koi8, cp1261, gbk, ... Декодер приймає потік байтів в якості вхідних даних і видає потік кодових точок.

Усі доступні типи декодування можна знайти тут .

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

Бібліотека MDN StringView

Альтернативою цьому є використання StringViewбібліотеки (ліцензованої як lgpl-3.0), метою якої є:

  • створити C-подібний інтерфейс для рядків (тобто масив кодових символів - ArrayBufferView в JavaScript) на основі інтерфейсу JavaScript ArrayBuffer
  • створити бібліотеку з високим розширенням, яку кожен може розширити, додавши методи в об’єкт StringView.prototype
  • створити колекцію методів для таких струнних об'єктів (відтепер: stringViews), які працюють строго на масивах чисел, а не на створенні нових незмінних рядків JavaScript
  • для роботи з кодуваннями Unicode, відмінними від типових JavaScript UTF-16 DOMStrings за замовчуванням JavaScript

даючи набагато більше гнучкості. Тим НЕ менше, це вимагало б від нас посилання на або вбудувати цю бібліотеку час TextEncoder/ TextDecoderбудується в в сучасних браузерах.

Підтримка

Станом на липень / 2018:

TextEncoder (Експериментальний, на стандартній доріжці)

 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

2
Немає підтримки TextDecoder від IE & Edge: caniuse.com/#search=TextDecoder
Андрій Даміан-Фекете

1
За даними MS, він розробляється: developer.microsoft.com/en-us/microsoft-edge/platform/status/…
Моріс Мюллер

Немає підтримки для Safari Mobile (ios) у 2018-04-18: developer.mozilla.org/en-US/docs/Web/API/TextDecoder
чоловік із бронзи

Однорядний: var encoder = 'TextEncoder' in window ? new TextEncoder() : {encode: function(str){return Uint8Array.from(str, function(c){return c.codePointAt(0);});}};так що ви можете простоvar array = encoder.encode('hello');
Yeti

1
Справа в TextEncoderтому, що якщо у вас є двійкові дані в рядку (наприклад, зображення), ви не хочете використовувати TextEncoder(мабуть). Символи з кодовими точками більше 127 створюють два байти. Чому я маю двійкові дані в рядку? cy.fixture(NAME, 'binary')( cypress) створює рядок.
x-yuri

175

Хоча рішення Dennis та gengkev із використанням роботи Blob / FileReader, я б не пропонував використовувати такий підхід. Це асинхронний підхід до простої проблеми, і це набагато повільніше, ніж пряме рішення. Я створив публікацію в html5rocks з більш простим і (набагато швидшим) рішенням: http://updates.html5rocks.com/2012/06/How-to-convert-ArrayBuffer-to-and-from-String

І рішення таке:

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

Редагувати:

Кодування API допомагає вирішити перетворення рядка проблеми. Перегляньте відповідь Джеффа Посніка на Html5Rocks.com на вищеописану оригінальну статтю.

Витяг:

API Encoding спрощує переклад між необробленими байтами та рідними рядками JavaScript, незалежно від того, з яким із багатьох стандартних кодувань вам потрібно працювати.

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

16
На жаль, мій коментар щодо html5rocks ще не затверджений. Тому коротка відповідь тут. Я все ще думаю, що це не правильний шлях, тому що ви пропускаєте безліч символів, тим більше, що сьогодні більшість сторінок перебувають у кодуванні UTF-8. З одного боку, для більш спеціальних символів (скажімо, азіатських) функція charCodeAt повертає 4-байтове значення, тому вони будуть рубані. З іншого боку, прості англійські символи виростуть ArrayBuffer вдвічі (ви використовуєте 2 байти на кожен 1-байтний символ). Уявіть, що надсилати англійський текст через WebSocket, це знадобиться двічі (не добре в режимі реального часу).
Денніс

9
Три приклади: (1) This is a cool text!20 байт у UTF8 - 40 байт у Unicode. (2) ÄÖÜ6 байт в UTF8 - 6 байт в Unicode. (3) ☐☑☒9 байт в UTF8 - 6 байт в Unicode. Якщо ви хочете зберегти рядок як файл UTF8 (через API інтерфейсу Blob і File Writer), ви не можете використовувати ці два способи, оскільки ArrayBuffer буде в Unicode, а не в UTF8.
Денніс

3
Я отримую помилку: Uncaught RangeError: Максимальний розмір стека викликів перевищений. У чому може бути проблема?
Яків

6
@Dennis - Рядки JS використовують UCS2, а не UTF8 (або навіть UTF16) - значить charCodeAt () завжди повертає значення 0 -> 65535. Будь-яка точка коду UTF-8, яка потребує 4 байтових кінців, буде представлена ​​сурогатними парами (див. En.wikipedia .org / wiki /… ) - тобто два окремих 16-бітних значення UCS2.
брофа

6
@jacob - Я вважаю, що помилка полягає в тому, що існує обмеження довжини масиву, яке може бути передано методу apply (). Наприклад, String.fromCharCode.apply(null, new Uint16Array(new ArrayBuffer(246300))).lengthпрацює для мене в Chrome, але якщо ви замість цього використовуєте 246301, я отримую ваш виняток
RangeError

71

Для перетворення рядка в і з ArrayBuffers ви можете використовувати TextEncoderі TextDecoderзі стандарту Encoding , який переповнюється бібліотекою stringencoding:

var uint8array = new TextEncoder().encode(string);
var string = new TextDecoder(encoding).decode(uint8array);

2
До речі, це доступно у Firefox за замовчуванням: developer.mozilla.org/en-US/docs/Web/API/TextDecoder.decode
Джоель Річард

2
Великі пальці для нових API, які набагато краще, ніж дивні способи вирішення!
Томаш Зато - Відновіть Моніку

1
Це не працюватиме з усіма типами символів там.
Девід

5
npm install text-encoding, var textEncoding = require('text-encoding'); var TextDecoder = textEncoding.TextDecoder;. Ні, дякую.
Еван Ху

бурчить ... якщо у мене є існуючий масив масиву, я хочу написати рядок, я думаю, що мені доведеться взяти uint8array і скопіювати його вдруге ??
shaunc

40

Blob набагато повільніше, ніж String.fromCharCode(null,array);

але це не вдається, якщо буфер масиву стає занадто великим. Найкраще рішення, яке я знайшов, - це використовувати String.fromCharCode(null,array);та розділити його на операції, які не роздують стек, але швидше, ніж один знак за один раз.

Найкращим рішенням для буфера великого масиву є:

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;

}

Я виявив, що це приблизно в 20 разів швидше, ніж використання blob. Він також працює для великих струн понад 100 Мб.


3
Ми повинні піти з цим рішенням. Як це вирішує ще одну прецеденту , ніж загальноприйнятому один
одного

24

На основі відповіді gengkev я створив функції обома способами, оскільки BlobBuilder може обробляти String та 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());
}

і

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

Простий тест:

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

У arrayBuffer2String (), ти мав на увазі викликати зворотний виклик (...) замість console.log ()? Інакше аргумент зворотного дзвінка не використовується.
Дан Філлімор

Це схоже на дорогу - дякую генкеву та Деннісу. Начебто нерозумно, що немає синхронного способу досягти цього, але що ти можеш зробити ...
kpozin

JavaScript є однопоточним. Тому FileReader є асинхронним з двох причин: (1) він не блокує виконання іншого JavaScript під час завантаження (величезного) файлу (уявіть собі складніший додаток) і (2) не блокує інтерфейс користувача / браузер (поширена проблема з довгим виконанням коду JS). Багато API є асинхронними. Навіть у XMLHttpRequest 2 синхронний видаляється.
Денніс

Я дуже сподівався, що це буде працювати для мене, але перетворення з рядка в ArrayBuffer не працює надійно. Я створюю ArrayBuffer з 256 значеннями і можу перетворити це на рядок довжиною 256. Але тоді, якщо я спробую перетворити це назад у ArrayBuffer - залежно від вмісту мого початкового ArrayBuffer - я виймаю 376 елементів. Якщо ви хочете спробувати відтворити мою проблему, я розглядаю свій ArrayBuffer як сітку 16x16 в Uint8Array, зі значеннями, обчисленими, як a[y * w + x] = (x + y) / 2 * 16; я намагався getBlob("x"), з багатьма різними міметиками - не щастить.
Метт Крейкшанк

18
У нових браузерах BlobBuilder застаріло. Змініть new BlobBuilder(); bb.append(buf);на new Blob([buf]), передайте ArrayBuffer у другій функції на UintArray через new UintArray(buf)(або що підходить для базового типу даних), а потім позбудьтеся getBlob()викликів. Нарешті, для чистоти перейменуйте bb на blob, тому що це вже не BlobBuilder.
sowbug

18

Все далі йдеться про отримання двійкових рядків з буферів масиву

Я б рекомендував не використовувати

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

бо це

  1. аварії на великих буферах (хтось писав про "чарівний" розмір 246300, але я отримавMaximum call stack size exceeded помилку в буфері 120000 байт (Chrome 29))
  2. він має дуже низьку продуктивність (див. нижче)

Якщо вам точно потрібно синхронне рішення, використовуйте щось на кшталт

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

він такий же повільний, як і попередній, але працює правильно. Схоже, на даний момент написання цього питання не існує досить швидкого синхронного рішення цієї проблеми (всі бібліотеки, згадані в цій темі, використовують однаковий підхід для своїх синхронних функцій).

Але те, що я дійсно рекомендую, - це використовувати 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' }));
}

єдиний недолік (не для всіх) полягає в тому, що він асинхронний . І це приблизно в 8-10 разів швидше, ніж попередні рішення! (Деякі деталі: синхронне рішення в моєму середовищі займало 950-1050 мс для буфера 2,4 Мб, але рішення з FileReader було разів приблизно 100-120 мс для тієї ж кількості даних. І я протестував обидва синхронні рішення на буфері 100 Кб, і вони взяли майже в той же час, тому цикл не набагато повільніше використовує "застосовувати".)

BTW тут: Як перетворити ArrayBuffer в і з String автор порівнює два підходи, як я, і отримує абсолютно протилежні результати ( його тестовий код тут ) Чому такі різні результати? Можливо, через його тестовий рядок довжиною 1 Кб (він назвав його "veryLongStr"). Мій буфер був дійсно великим зображенням JPEG розміром 2,4 Мб.


13

( Оновлення Будь ласка, дивіться другу частину цієї відповіді, де я (сподіваюся) запропонував більш повне рішення.)

Я також зіткнувся з цим питанням, наступні роботи для мене в FF 6 (для одного напрямку):

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

На жаль, звичайно, ви закінчуєте ASCII текстове подання значень масиву, а не символів. Це все ж (має бути) набагато ефективніше, ніж цикл. напр. У наведеному вище прикладі результат 0004000000, а не кілька нульових символів & a chr (4).

Редагувати:

Подивившись на MDC тут , ви можете створити ArrayBufferз 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;

Щоб відповісти на своє первісне запитання, це дозволяє вам конвертувати ArrayBuffer<-> Stringтаким чином:

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

Для зручності тут є functionперетворення необробленого Unicode Stringв ArrayBuffer(працює лише з ASCII / однобайтовими символами)

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

Наведене вище дозволяє перейти від ArrayBuffer-> String& назад до ArrayBufferзнову, де рядок може зберігатися, наприклад. .localStorage:)

Сподіваюся, це допомагає,

Ден


1
Я не думаю, що це ефективний метод (з точки зору часу чи простору), і це дуже незвичний спосіб зберігання бінарних даних.
kpozin

@kpozin: Наскільки я знаю, немає іншого способу зберігання бінарних даних у localStorage
Дан Філлімор

1
Що з використанням кодування base64?
Нік Сотірос

13

На відміну від рішень тут, мені потрібно було конвертувати в / з даних UTF-8. Для цього я зашифрував дві наступні функції, використовуючи (не) escape / (en) декодерURIComponent хитрість. Вони досить марнотратять пам’ять, виділяючи в 9 разів більше довжини закодованого utf8-рядка, хоча їх слід відновити gc. Просто не використовуйте їх для тексту в форматі 100 Мб.

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

Перевірка, що вона працює:

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

8

Якщо у вас є двійкові дані в рядку (отримані від nodejs+ readFile(..., 'binary'), або cypress+ cy.fixture(..., 'binary')тощо), ви не можете їх використовувати TextEncoder. Він підтримує лишеutf8 . >= 128Кожен байт зі значеннями перетворюється на 2 байти.

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"


7

Я виявив, що у мене виникли проблеми з таким підходом, в основному тому, що я намагався записати вихід у файл, і він не був закодований належним чином. Оскільки, схоже, JS використовує кодування UCS-2 ( джерело , джерело) ), нам потрібно розтягнути це рішення на крок далі, ось моє розширене рішення, яке працює для мене.

У мене не було труднощів із загальним текстом, але коли він перейшов до арабської чи корейської, у вихідному файлі не було всіх символів, а натомість відображалися символи помилок

Виведення файлу: ","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}

Оригінал: ","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}

Я взяв інформацію з рішення dennis і цю посаду я знайшов.

Ось мій код:

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

Це дозволяє мені зберегти вміст у файл без проблем з кодуванням.

Принцип роботи: В основному це займає одиночні 8-байтові фрагменти, що складають символ UTF-8, і зберігає їх як окремі символи (отже, побудований таким чином символ UTF-8 може складатися з 1-4 цих символів). UTF-8 кодує символи у форматі, який змінюється в довжину від 1 до 4 байтів. Що ми робимо тут, це кодування жала в компоненті URI, а потім беремо цей компонент і переводимо його у відповідний 8-байтний символ. Таким чином ми не втрачаємо інформацію, надану символами UTF8, довжиною більше 1 байта.


6

якщо ви використовували величезний приклад масиву, arr.length=1000000 ви можете використовувати цей код, щоб уникнути проблем зворотного виклику стеків

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
}

зворотній функції мангіні відповідь зверху

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

4

Ну, ось дещо перекручений спосіб зробити те саме:

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

Редагувати: BlobBuilder давно застарів на користь конструктора Blob, якого не існувало, коли я вперше написав це повідомлення. Ось оновлена ​​версія. (І так, це завжди було дуже дурним способом зробити конверсію, але це було просто для розваги!)

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

3

Після гри з рішенням мангіні для перетворення ArrayBufferна String- ab2str(що є найелегантнішим і найкориснішим, що я знайшов - дякую!), У мене виникли деякі проблеми при обробці великих масивів. Більш чітко, виклик String.fromCharCode.apply(null, new Uint16Array(buf));кидає помилку:

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

Для того, щоб вирішити це (байпас), я вирішив обробляти вхідними ArrayBufferфрагментами. Отже, модифікованим рішенням є:

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

Розмір шматка встановлений, 2^16тому що саме цей розмір я знайшов працювати в моєму ландшафті розвитку. Встановлення більш високого значення спричинило повторення тієї ж помилки. Це можна змінити, встановивши CHUNK_SIZEзмінну на інше значення. Важливо мати парне число.

Примітка щодо продуктивності - я не робив жодних тестів на ефективність цього рішення. Однак, оскільки він заснований на попередньому рішенні і може обробляти великі масиви, я не бачу причин, щоб не використовувати його.


ви можете використовувати typedarray.subarray, щоб отримати шматок у визначеному положенні та розмірі, це те, що я роблю, щоб читати заголовки бінарних форматів у js
Nikos M.

2

Дивіться тут: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays/StringView (C-подібний інтерфейс для рядків на основі інтерфейсу JavaScript ArrayBuffer)


2
Цей код під GPLv3. Я думаю, що Mozilla досить непрофесійно навіть поєднувати цей код зі своєю документацією, що відповідає стандартам.
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;
  }

цей код є помилковим, якщо рядок містить символи unicode. приклад:arrayBufferToString(stringToArrayBuffer('🐴'))==='44'
xmcp

2

Для node.js, а також для браузерів за допомогою 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'))
}

Примітка. Рішення тут не працювали для мене. Мені потрібно підтримувати node.js та браузери та просто серіалізувати UInt8Array до рядка. Я міг би її серіалізувати як число [], але це займає зайвий простір. З цим рішенням мені не потрібно турбуватися про кодування, оскільки це base64. Про всяк випадок, якщо інші люди борються з тією ж проблемою ... Мої два копійки


2

Скажімо, у вас є масивBuffer binaryStr:

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

а потім ви присвоюєте текст державі.


1

"Народний" двійковий рядок, який atob () повертає, є масивом 1 байт на символ.

Тому ми не повинні зберігати 2 байти в символі.

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


0

Я рекомендую НЕ використовувати застарілі API, такі як BlobBuilder

BlobBuilder давно застарів об'єктом Blob. Порівняйте код у відповіді Денніса - де використовується BlobBuilder - з кодом нижче:

function arrayBufferGen(str, cb) {

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

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

  f.readAsArrayBuffer(b);

}

Зверніть увагу, наскільки це чистіший і менш здутий в порівнянні зі застарілим методом ... Так, тут, безумовно, є що враховувати.


Я маю на увазі, так, але конструктор Blob насправді не використовувався в 2012 році;)
gengkev


0

Я використав це і працює на мене.

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;
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.