Преобразование между строками и ArrayBuffers

265

Существует ли общепринятый метод эффективного преобразования строк JavaScript в ArrayBuffers и наоборот? В частности, я хотел бы иметь возможность записывать содержимое ArrayBuffer localStorageи читать его обратно.

kpozin
источник
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, отличными от стандартных DOMStrings JavaScript UTF-16 по умолчанию

давая гораздо больше гибкости. Тем не менее, это потребовало бы от нас ссылку на или встроить эту библиотеку время 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
бронзовый человек
One-liner: 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-
176

Хотя решения Дениса и Генгкева с использованием 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 кодирования упрощает перевод между необработанными байтами и собственными строками 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), вы не можете использовать эти 2 метода, потому что 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

Вы можете использовать TextEncoderи TextDecoderиз стандарта Encoding , который заполняется библиотекой stringencoding , для преобразования строки в ArrayBuffers и из нее:

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;. Нет, спасибо.
Эван Ху
ворчать ... если у меня есть существующий буфер массива, я хочу написать строку, я думаю, я должен взять массив uint8 и скопировать его во второй раз ??
Шон
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 (), вы хотели вызвать callback (...) вместо 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.
свинья
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 Кб, и они взяли почти в то же время, поэтому цикл не намного медленнее с использованием 'apply'.)

Кстати: как конвертировать 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. Для этой цели я кодировал следующие две функции, используя трюк (un) escape / (en) decodeURIComponent. Они довольно бесполезно расходуют память, выделяя в 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»

user3832931
источник
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}

Я взял информацию из решения Дениса и этот пост, который я нашел.

Вот мой код:

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 байта.

Dieghito
источник
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]));
gengkev
источник
3
  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;
  }
Адмир
источник
этот код глючит, если строка содержит символы Юникода. пример:arrayBufferToString(stringToArrayBuffer('🐴'))==='44'
xmcp
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
Никос М.
2

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

pascov
источник
2
Этот код находится под GPLv3. Я думаю, что в Mozilla довольно непрофессионально даже смешивать этот код со своей соответствующей стандартам документацией.
user239558
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. На всякий случай другие люди борются с той же проблемой ... Мои два цента

cancerbero
источник
2

Допустим, у вас есть arrayBuffer 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;
}
wdhwg001
источник
1

Да:

const encstr = (`TextEncoder` in window) ? new TextEncoder().encode(str) : Uint8Array.from(str, c => c.codePointAt(0));
Денис Гиффелер
источник
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);

}

Обратите внимание, насколько он чище и менее раздутый по сравнению с устаревшим методом ... Да, здесь определенно стоит рассмотреть.

realkstrawn93
источник
Я имею в виду, да, но этот конструктор 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;
}
Элиас Варгас
источник