Преобразование между строками и ArrayBuffers - PullRequest
222 голосов
/ 06 августа 2011

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

Ответы [ 20 ]

169 голосов
/ 16 июня 2012

Хотя решения 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 кодирования упрощаетпереводить между необработанными байтами и собственными строками JavaScript независимо от того, с каким из множества стандартных кодировок вам нужно работать.

<code><pre id="results">
if ('TextDecoder' в окне) {// Theлокальные файлы для извлечения, сопоставленные с кодировкой, которую они используют.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 = 'Ваш браузер не поддерживает API кодирования.'} // Используйте XHR для извлечения `file` и интерпретируйте его содержимое как закодированное с помощью` encoding`.function fetchAndDecode (file, encoding) {var xhr = new XMLHttpRequest ();xhr.open ('GET', файл);// Использование 'arraybuffer' в качестве responseType гарантирует, что необработанные данные будут возвращены, // вместо того, чтобы позволить XMLHttpRequest сначала декодировать данные.xhr.responseType = 'arraybuffer';xhr.onload = function () {if (this.status == 200) {// Метод decode () принимает DataView в качестве параметра, который является оберткой поверх ArrayBuffer.var dataView = new DataView (this.response);// Интерфейс TextDecoder задокументирован в http://encoding.spec.whatwg.org/#interface-textdecoder var decoder = new TextDecoder (encoding);var decodedString = decoder.decode (dataView);// Добавить текст декодированного файла в
element on the page.
        document.querySelector('#results').textContent += decodedString + '\n';
      } else {
        console.error('Error while requesting', file, this);
      }
    };
    xhr.send();
  }
</script>
</code>
89 голосов
/ 19 июня 2016

Обновление 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, ... Aдекодер принимает поток байтов в качестве входных данных и выдает поток кодовых точек.

Все доступные типы декодирования можно найти здесь .

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

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

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

var uint8array = new TextEncoder().encode(string);
var string = new TextDecoder(encoding).decode(uint8array);
35 голосов
/ 16 декабря 2013

Blob намного медленнее, чем String.fromCharCode(null,array);

, но это терпит неудачу, если буфер массива становится слишком большим.Лучшее решение, которое я нашел, - это использовать String.fromCharCode(null,array); и разбить его на операции, которые не будут разбивать стек, но быстрее, чем один символ за раз.

Лучшее решение для буфера большого массиваis:

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 МБ.

21 голосов
/ 12 марта 2012

Исходя из ответа 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"
            }
        )
    }
)
14 голосов
/ 05 сентября 2013

Все следующее касается получения двоичных строк из буферов массива

Я бы рекомендовал не использовать

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 мс для того же объема данных. И я проверил оба синхронных решения на 100Kb буфера, и они заняли почти одинаковое время, поэтому цикл не намного медленнее с использованием 'apply'.)

Кстати: Как преобразовать ArrayBuffer в и из String автор сравнивает два подхода, таких как я, и получает совершенно противоположные результаты ( его тестовый код здесь ) Почему такие разные результаты? Вероятно, из-за его тестовой строки длиной 1 КБ (он назвал ее «veryLongStr»). Мой буфер представлял собой действительно большое изображение JPEG размером 2,4 МБ.

13 голосов
/ 10 сентября 2013

В отличие от решений здесь, мне нужно было конвертировать в / из данных 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кирилицаαβγδεζηあいうえお"
12 голосов
/ 25 августа 2011

( Обновление Пожалуйста, смотрите вторую половину этого ответа, где я (надеюсь) предоставил более полное решение.)

Я также столкнулся с этой проблемой, следующие работы для меня в 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).

Edit:

После просмотра 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:)

Надеюсь, это поможет,

Dan

7 голосов
/ 03 июля 2014

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

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

Вывод файла: ","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 байта.

4 голосов
/ 06 января 2016

если вы использовали пример огромного массива 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
}

обратная функция ответ mangini сверху

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