Làm thế nào để chuyển đổi Mảng uint8 sang Chuỗi mã hóa base64?


90

Tôi nhận được thông tin liên lạc webSocket, tôi nhận được chuỗi được mã hóa base64, chuyển đổi nó thành uint8 và làm việc trên đó, nhưng bây giờ tôi cần gửi lại, tôi nhận được mảng uint8 và cần chuyển nó thành chuỗi base64, vì vậy tôi có thể gửi nó. Làm thế nào tôi có thể thực hiện chuyển đổi này?


Câu trả lời:


15

Tất cả các giải pháp đã được đề xuất đều có vấn đề nghiêm trọng. Một số giải pháp không hoạt động trên các mảng lớn, một số cung cấp đầu ra sai, một số gây ra lỗi khi gọi btoa nếu một chuỗi trung gian chứa các ký tự multibyte, một số giải pháp sử dụng nhiều bộ nhớ hơn mức cần thiết.

Vì vậy, tôi đã triển khai một chức năng chuyển đổi trực tiếp hoạt động bất kể đầu vào. Nó chuyển đổi khoảng 5 triệu byte mỗi giây trên máy của tôi.

https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727


Việc có base64abc dưới dạng một mảng chuỗi có nhanh hơn chỉ làm cho nó thành một chuỗi không? "ABCDEFG..."?
Garr Godfrey

161

Nếu dữ liệu của bạn có thể chứa các chuỗi nhiều byte (không phải chuỗi ASCII thuần túy) và trình duyệt của bạn có TextDecoder , thì bạn nên sử dụng nó để giải mã dữ liệu của mình (chỉ định mã hóa bắt buộc cho TextDecoder):

var u8 = new Uint8Array([65, 66, 67, 68]);
var decoder = new TextDecoder('utf8');
var b64encoded = btoa(decoder.decode(u8));

Nếu bạn cần hỗ trợ các trình duyệt không có TextDecoder (hiện chỉ có IE và Edge), thì tùy chọn tốt nhất là sử dụng đa điền TextDecoder .

Nếu dữ liệu của bạn chứa ASCII thuần túy (không phải Unicode đa byte / UTF-8) thì có một giải pháp thay thế đơn giản sử dụng String.fromCharCodesẽ được hỗ trợ khá phổ biến:

var ascii = new Uint8Array([65, 66, 67, 68]);
var b64encoded = btoa(String.fromCharCode.apply(null, ascii));

Và để giải mã chuỗi base64 trở lại Uint8Array:

var u8_2 = new Uint8Array(atob(b64encoded).split("").map(function(c) {
    return c.charCodeAt(0); }));

Nếu bạn có bộ đệm mảng rất lớn thì việc áp dụng có thể không thành công và bạn có thể cần phải phân đoạn bộ đệm (dựa trên bộ đệm được đăng bởi @RohitSengar). Một lần nữa, lưu ý rằng điều này chỉ đúng nếu bộ đệm của bạn chỉ chứa các ký tự ASCII không phải nhiều byte:

function Uint8ToString(u8a){
  var CHUNK_SZ = 0x8000;
  var c = [];
  for (var i=0; i < u8a.length; i+=CHUNK_SZ) {
    c.push(String.fromCharCode.apply(null, u8a.subarray(i, i+CHUNK_SZ)));
  }
  return c.join("");
}
// Usage
var u8 = new Uint8Array([65, 66, 67, 68]);
var b64encoded = btoa(Uint8ToString(u8));

4
Điều này đang hoạt động đối với tôi trong Firefox, nhưng Chrome bị lỗi với "Uncaught RangeError: Đã vượt quá kích thước ngăn xếp cuộc gọi tối đa" (đang thực hiện btoa).
Michael Paulukonis

3
@MichaelPaulukonis phỏng đoán của tôi là nó thực sự là String.fromCharCode.apply đang khiến kích thước ngăn xếp bị vượt quá. Nếu bạn có một Uint8Array rất lớn, thì có thể bạn sẽ cần tạo chuỗi lặp lại thay vì sử dụng ứng dụng để làm như vậy. Lời gọi apply () đang chuyển mọi phần tử trong mảng của bạn dưới dạng tham số tới fromCharCode, vì vậy nếu mảng dài 128000 byte thì bạn sẽ cố gắng thực hiện một cuộc gọi hàm với 128000 tham số có khả năng làm hỏng ngăn xếp.
kanaka

4
Cảm ơn. Tất cả những gì tôi cần làbtoa(String.fromCharCode.apply(null, myArray))
Glen Little.

29
Điều này không hoạt động nếu mảng byte không phải là Unicode hợp lệ.
Melab

11
Không có ký tự multibyte nào trong chuỗi base64 hoặc trong Uint8Array. TextDecoderhoàn toàn là điều sai lầm khi sử dụng ở đây, bởi vì nếu bạn Uint8Arraycó byte trong phạm vi 128..255, bộ giải mã văn bản sẽ chuyển nhầm chúng thành các ký tự unicode, điều này sẽ phá vỡ trình chuyển đổi base64.
riv

26

Giải pháp và thử nghiệm rất đơn giản cho JavaScript!

ToBase64 = function (u8) {
    return btoa(String.fromCharCode.apply(null, u8));
}

FromBase64 = function (str) {
    return atob(str).split('').map(function (c) { return c.charCodeAt(0); });
}

var u8 = new Uint8Array(256);
for (var i = 0; i < 256; i++)
    u8[i] = i;

var b64 = ToBase64(u8);
console.debug(b64);
console.debug(FromBase64(b64));

4
Giải pháp sạch nhất!
realappie

Giải pháp hoàn hảo
Haris ur Rehman

2
nó không thành công trên dữ liệu lớn (chẳng hạn như hình ảnh) vớiRangeError: Maximum call stack size exceeded
Maxim Khokhryakov

18
function Uint8ToBase64(u8Arr){
  var CHUNK_SIZE = 0x8000; //arbitrary number
  var index = 0;
  var length = u8Arr.length;
  var result = '';
  var slice;
  while (index < length) {
    slice = u8Arr.subarray(index, Math.min(index + CHUNK_SIZE, length)); 
    result += String.fromCharCode.apply(null, slice);
    index += CHUNK_SIZE;
  }
  return btoa(result);
}

Bạn có thể sử dụng chức năng này nếu bạn có một Uint8Array rất lớn. Điều này dành cho Javascript, có thể hữu ích trong trường hợp FileReader readAsArrayBuffer.


2
Thật thú vị, trong Chrome, tôi đã tính thời gian này trên bộ đệm 300kb + và nhận thấy việc thực hiện nó theo từng phần giống như bạn sẽ hơi chậm hơn so với thực hiện từng byte một. Điều này làm tôi ngạc nhiên.
Matt

@Matt thú vị. Có thể trong thời gian chờ đợi, Chrome hiện đã phát hiện ra chuyển đổi này và có cách tối ưu hóa cụ thể cho nó và việc phân tích dữ liệu có thể làm giảm hiệu quả của nó.
kanaka

2
Điều này không an toàn, phải không? Nếu ranh giới của chunk của tôi cắt qua một ký tự được mã hóa UTF8 nhiều byte, thì fromCharCode () sẽ không thể tạo các ký tự hợp lý từ các byte ở cả hai phía của ranh giới, phải không?
Jens

2
Các String.fromCharCode.apply()phương thức @Jens không thể tái tạo UTF-8: Các ký tự UTF-8 có thể thay đổi độ dài từ một byte đến bốn byte, nhưng vẫn String.fromCharCode.apply()kiểm tra một UInt8Array trong các phân đoạn của UInt8, do đó, nó giả định sai mỗi ký tự dài chính xác một byte và không phụ thuộc vào vùng lân cận những cái. Nếu các ký tự được mã hóa trong UInt8Array đầu vào tình cờ nằm ​​trong phạm vi ASCII (byte đơn), thì nó sẽ hoạt động một cách tình cờ, nhưng không thể tái tạo UTF-8 đầy đủ. Bạn cần TextDecoder hoặc một thuật toán tương tự cho điều đó.
Jamie Birch

1
@Jens những ký tự được mã hóa UTF8 nhiều byte nào trong mảng dữ liệu nhị phân? Chúng tôi không xử lý các chuỗi unicode ở đây, nhưng với dữ liệu nhị phân tùy ý, KHÔNG được coi là codepoints utf-8.
riv

16

Nếu bạn đang sử dụng Node.js thì bạn có thể sử dụng mã này để chuyển đổi Uint8Array sang base64

var b64 = Buffer.from(u8).toString('base64');

4
Đây là một câu trả lời tốt hơn sau đó các chức năng cuộn tay ở trên về mặt hiệu suất.
Ben Liyanage

2
Tuyệt vời! Cảm ơn. Câu trả lời hay nhất từ ​​trước đến nay
Alan

2
Hoàn hảo!! Đây sẽ là câu trả lời được chấp nhận!
m4l490n

1
Đây là câu trả lời chính xác
Pablo Yabo

0

Đây là một hàm JS cho điều này:

Chức năng này là cần thiết vì Chrome không chấp nhận chuỗi được mã hóa base64 làm giá trị cho applicationServerKey trong pushManager.subscribe https://bugs.chromium.org/p/chromium/issues/detail?id=802280

function urlBase64ToUint8Array(base64String) {
  var padding = '='.repeat((4 - base64String.length % 4) % 4);
  var base64 = (base64String + padding)
    .replace(/\-/g, '+')
    .replace(/_/g, '/');

  var rawData = window.atob(base64);
  var outputArray = new Uint8Array(rawData.length);

  for (var i = 0; i < rawData.length; ++i) {
    outputArray[i] = rawData.charCodeAt(i);
  }
  return outputArray;
}

3
Điều này sẽ chuyển đổi base64 thành Uint8Array. Nhưng câu hỏi hỏi làm thế nào để chuyển đổi Uint8Array để base64
Barry Michael Doyle

0

JS thuần túy - không có chuỗi middlestep (không có btoa)

Trong giải pháp dưới đây, tôi bỏ qua chuyển đổi thành chuỗi. IDEA đang theo dõi:

  • nối 3 byte (3 phần tử mảng) và bạn nhận được 24 bit
  • chia 24 bit thành bốn số 6 bit (nhận các giá trị từ 0 đến 63)
  • sử dụng các số đó làm chỉ mục trong bảng chữ cái base64
  • trường hợp góc: khi đầu vào mảng byte có độ dài không chia cho 3 thì thêm =hoặc ==kết quả

Giải pháp dưới đây hoạt động trên các khối 3 byte vì vậy nó tốt cho các mảng lớn. Giải pháp tương tự để chuyển đổi base64 sang mảng nhị phân (không có atob) là TẠI ĐÂY


Tôi thích sự nhỏ gọn nhưng chuyển đổi thành chuỗi đại diện cho số nhị phân và sau đó quay lại chậm hơn nhiều so với giải pháp được chấp nhận.
Garr Godfrey

0

Sử dụng phần sau để chuyển đổi mảng uint8 thành chuỗi mã hóa base64

function arrayBufferToBase64(buffer) {
            var binary = '';
            var bytes = [].slice.call(new Uint8Array(buffer));
            bytes.forEach((b) => binary += String.fromCharCode(b));
            return window.btoa(binary);
        };


-1

Một cách tiếp cận rất tốt cho điều này được hiển thị tại trang web Mạng nhà phát triển Mozilla :

function btoaUTF16 (sString) {
    var aUTF16CodeUnits = new Uint16Array(sString.length);
    Array.prototype.forEach.call(aUTF16CodeUnits, function (el, idx, arr) { arr[idx] = sString.charCodeAt(idx); });
    return btoa(String.fromCharCode.apply(null, new Uint8Array(aUTF16CodeUnits.buffer)));
}

function atobUTF16 (sBase64) {
    var sBinaryString = atob(sBase64), aBinaryView = new Uint8Array(sBinaryString.length);
    Array.prototype.forEach.call(aBinaryView, function (el, idx, arr) { arr[idx] = sBinaryString.charCodeAt(idx); });
    return String.fromCharCode.apply(null, new Uint16Array(aBinaryView.buffer));
}

var myString = "☸☹☺☻☼☾☿";

var sUTF16Base64 = btoaUTF16(myString);
console.log(sUTF16Base64);    // Shows "OCY5JjomOyY8Jj4mPyY="

var sDecodedString = atobUTF16(sUTF16Base64);
console.log(sDecodedString);  // Shows "☸☹☺☻☼☾☿"


-3

Nếu tất cả những gì bạn muốn là triển khai JS của bộ mã hóa base64, để bạn có thể gửi dữ liệu trở lại, bạn có thể thử btoachức năng này.

b64enc = btoa(uint);

Một vài lưu ý nhanh về btoa - nó không phải là tiêu chuẩn, vì vậy các trình duyệt không bị buộc phải hỗ trợ nó. Tuy nhiên, hầu hết các trình duyệt đều có. Những cái lớn, ít nhất. atoblà chuyển đổi ngược lại.

Nếu bạn cần một triển khai khác hoặc bạn tìm thấy một trường hợp phức tạp mà trình duyệt không biết bạn đang nói gì, việc tìm kiếm bộ mã hóa base64 cho JS sẽ không quá khó.

Tôi nghĩ rằng có 3 người trong số họ quanh quẩn trên trang web của công ty tôi, vì một số lý do ...


Cảm ơn, tôi đã không thử điều đó trước đây.
Caio Keto

10
Vài lưu ý. btoa và atob thực sự là một phần của quá trình chuẩn hóa HTML5 và hầu hết các trình duyệt đều hỗ trợ chúng theo cùng một cách. Thứ hai, btoa và atob chỉ hoạt động với chuỗi. Chạy btoa trên Uint8Array trước tiên sẽ chuyển đổi bộ đệm thành một chuỗi bằng cách sử dụng toString (). Điều này dẫn đến chuỗi "[object Uint8Array]". Đó có lẽ không phải là những gì dự định.
kanaka

1
@CaioKeto, bạn có thể muốn xem xét thay đổi câu trả lời đã chọn của mình. Câu trả lời này không đúng.
kanaka

-4

npm cài đặt google-close-library --save

require("google-closure-library");
goog.require('goog.crypt.base64');

var result =goog.crypt.base64.encodeByteArray(Uint8Array.of(1,83,27,99,102,66));
console.log(result);

$node index.jssẽ ghi AVMbY2Y = vào bảng điều khiển.


1
Thật buồn cười khi một -vecâu trả lời được bình chọn được chấp nhận hơn là một câu trả lời cao +ve.
Vishnudev
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.