วิธีการแปลง uint8 Array เป็น base64 Encoded String?


90

ฉันได้รับการสื่อสารแบบ webSocket ฉันได้รับสตริงที่เข้ารหัส base64 แปลงเป็น uint8 และทำงานกับมัน แต่ตอนนี้ฉันต้องส่งกลับฉันได้รับอาร์เรย์ uint8 และต้องแปลงเป็นสตริง base64 ฉันจึงจะสามารถส่งได้ ฉันจะทำการเปลี่ยนแปลงนี้ได้อย่างไร?



คำถาม "สตริงที่เข้ารหัส ArrayBuffer ถึง base64" มีวิธีแก้ปัญหาที่ดีกว่าซึ่งจัดการกับอักขระทั้งหมด stackoverflow.com/questions/9267899/…
Steve Hanov

คำตอบ:


16

แนวทางแก้ไขทั้งหมดที่เสนอไปแล้วมีปัญหารุนแรง โซลูชันบางอย่างไม่สามารถทำงานกับอาร์เรย์ขนาดใหญ่บางตัวให้เอาต์พุตผิดพลาดบางตัวเกิดข้อผิดพลาดในการเรียก btoa หากสตริงกลางมีอักขระหลายไบต์บางตัวใช้หน่วยความจำมากกว่าที่จำเป็น

ดังนั้นฉันจึงใช้ฟังก์ชันการแปลงโดยตรงซึ่งใช้งานได้โดยไม่คำนึงถึงอินพุต มันแปลงประมาณ 5 ล้านไบต์ต่อวินาทีบนเครื่องของฉัน

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


การมี base64abc เป็นอาร์เรย์ของสตริงเร็วกว่าการสร้างสตริงหรือไม่? "ABCDEFG..."เหรอ?
Garr Godfrey

163

หากข้อมูลของคุณอาจมีลำดับแบบหลายไบต์ (ไม่ใช่ลำดับ ASCII ธรรมดา) และเบราว์เซอร์ของคุณมีTextDecoderคุณควรใช้ข้อมูลนั้นเพื่อถอดรหัสข้อมูลของคุณ (ระบุการเข้ารหัสที่จำเป็นสำหรับ TextDecoder):

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

หากคุณต้องการการสนับสนุนเบราว์เซอร์ที่ไม่ได้มี TextDecoder (ปัจจุบันเพียง IE และขอบ) จากนั้นเลือกที่ดีที่สุดคือการใช้polyfill TextDecoder

หากข้อมูลของคุณมี ASCII ธรรมดา (ไม่ใช่หลายไบต์ Unicode / UTF-8) แสดงว่ามีทางเลือกง่ายๆในการใช้String.fromCharCodeที่ควรได้รับการสนับสนุนอย่างเป็นธรรม:

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

และในการถอดรหัสสตริง base64 กลับไปเป็น Uint8Array:

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

หากคุณมีบัฟเฟอร์อาร์เรย์ขนาดใหญ่มากการใช้งานอาจล้มเหลวและคุณอาจต้องแบ่งบัฟเฟอร์ (ตามที่โพสต์โดย @RohitSengar) โปรดทราบอีกครั้งว่านี่จะถูกต้องก็ต่อเมื่อบัฟเฟอร์ของคุณมีเฉพาะอักขระ ASCII ที่ไม่ใช่หลายไบต์

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
สิ่งนี้ใช้ได้ผลสำหรับฉันใน Firefox แต่ Chrome ใช้ "Uncaught RangeError: เกินขนาดสแต็กการโทรสูงสุด" (ทำ btoa)
Michael Paulukonis

3
@MichaelPaulukon ฉันเดาว่าจริงๆแล้วมันคือ String.fromCharCode.apply ที่ทำให้ขนาดสแต็กเกิน หากคุณมี Uint8Array ที่มีขนาดใหญ่มากคุณอาจต้องสร้างสตริงซ้ำ ๆ แทนที่จะใช้การใช้เพื่อทำเช่นนั้น การเรียกใช้ () กำลังส่งผ่านทุกองค์ประกอบของอาร์เรย์ของคุณเป็นพารามิเตอร์ไปยัง fromCharCode ดังนั้นหากอาร์เรย์มีความยาว 128000 ไบต์คุณจะพยายามเรียกใช้ฟังก์ชันด้วยพารามิเตอร์ 128000 ซึ่งมีแนวโน้มที่จะระเบิดสแต็ก
kanaka

4
ขอบคุณ. สิ่งที่ฉันต้องการคือbtoa(String.fromCharCode.apply(null, myArray))
Glen Little

29
สิ่งนี้ใช้ไม่ได้หากอาร์เรย์ไบต์ไม่ใช่ Unicode ที่ถูกต้อง
Melab

11
ไม่มีตัวอักษรสัญลักษณ์ในสตริง base64 Uint8Arrayหรือในที่มี TextDecoderเป็นสิ่งที่ผิดอย่างยิ่งที่จะใช้ที่นี่เพราะถ้าคุณUint8Arrayมีไบต์ในช่วง 128..255 ตัวถอดรหัสข้อความจะแปลงเป็นอักขระ Unicode อย่างผิดพลาดซึ่งจะทำลายตัวแปลง base64
riv

26

วิธีแก้ปัญหาและทดสอบ 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
น้ำยาสะอาดที่สุด!
realappie

โซลูชั่นที่สมบูรณ์แบบ
Haris ur Rehman

2
มันล้มเหลวในข้อมูลขนาดใหญ่ (เช่นรูปภาพ) ด้วยRangeError: Maximum call stack size exceeded
Maxim Khohryakov

21

หากคุณใช้ Node.js คุณสามารถใช้รหัสนี้เพื่อแปลง Uint8Array เป็น base64

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

4
นี่เป็นคำตอบที่ดีกว่าจากนั้นฟังก์ชั่นรีดมือข้างต้นในแง่ของประสิทธิภาพ
Ben Liyanage

2
สุดยอด! ขอบคุณ. คำตอบที่ดีที่สุด
อลัน

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

คุณสามารถใช้ฟังก์ชันนี้ได้หากคุณมี Uint8Array ขนาดใหญ่มาก สิ่งนี้มีไว้สำหรับ Javascript ซึ่งมีประโยชน์ในกรณีของ FileReader readAsArrayBuffer


2
ที่น่าสนใจคือใน Chrome ฉันตั้งเวลาไว้ที่บัฟเฟอร์ 300kb + และพบว่าการทำมันเป็นชิ้น ๆ เหมือนกับที่คุณจะช้ากว่าการทำไบต์ทีละไบต์เล็กน้อย สิ่งนี้ทำให้ฉันประหลาดใจ
แมตต์

@ แมทน่าสนใจ. เป็นไปได้ว่าในระหว่างนี้ Chrome ตรวจพบ Conversion นี้แล้วและมีการเพิ่มประสิทธิภาพเฉพาะสำหรับการแปลงนี้และการแบ่งข้อมูลเป็นกลุ่มอาจทำให้ประสิทธิภาพลดลง
kanaka

2
ไม่ปลอดภัยใช่ไหม หากขอบเขตของชิ้นส่วนของฉันตัดผ่านอักขระที่เข้ารหัส UTF8 แบบหลายไบต์จากนั้นfromCharCode ()จะไม่สามารถสร้างอักขระที่เหมาะสมจากไบต์ทั้งสองด้านของขอบเขตได้หรือไม่
Jens

2
String.fromCharCode.apply()วิธีการของ@Jens ไม่สามารถสร้าง UTF-8 ได้: อักขระ UTF-8 อาจมีความยาวแตกต่างกันไปตั้งแต่หนึ่งไบต์ถึงสี่ไบต์ แต่String.fromCharCode.apply()ตรวจสอบ UInt8Array ในส่วนของ UInt8 ดังนั้นจึงถือว่าอักขระแต่ละตัวมีความยาวหนึ่งไบต์โดยไม่ขึ้นกับเพื่อนบ้าน คน หากอักขระที่เข้ารหัสใน UInt8Array อินพุตทั้งหมดอยู่ในช่วง ASCII (ไบต์เดียว) จะทำงานโดยบังเอิญ แต่ไม่สามารถสร้าง UTF-8 แบบเต็มได้ คุณต้องมี TextDecoder หรืออัลกอริทึมที่คล้ายกันสำหรับสิ่งนั้น
Jamie Birch

1
@Jens อักขระที่เข้ารหัส UTF8 แบบหลายไบต์ในอาร์เรย์ข้อมูลไบนารีคืออะไร? เราไม่ได้จัดการกับสตริง Unicode ที่นี่ แต่มีข้อมูลไบนารีตามอำเภอใจซึ่งไม่ควรถือว่าเป็นจุดรหัส utf-8
riv

0

นี่คือฟังก์ชัน JS สำหรับสิ่งนี้:

จำเป็นต้องใช้ฟังก์ชันนี้เนื่องจาก Chrome ไม่ยอมรับสตริงที่เข้ารหัส base64 เป็นค่าสำหรับ applicationServerKey ใน 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
สิ่งนี้จะแปลง base64 เป็น Uint8Array แต่คำถามถามว่าจะแปลง Uint8Array เป็น base64 ได้อย่างไร
Barry Michael Doyle

0

Pure JS - ไม่มีสตริงกลางขั้นตอน (ไม่มี btoa)

ในโซลูชันด้านล่างฉันละเว้นการแปลงเป็นสตริง IDEA กำลังติดตาม:

  • เข้าร่วม 3 ไบต์ (3 องค์ประกอบอาร์เรย์) และคุณจะได้รับ 24 บิต
  • แบ่ง 24 บิตเป็นตัวเลข 6 บิตสี่ตัว (ซึ่งใช้ค่าตั้งแต่ 0 ถึง 63)
  • ใช้ตัวเลขนั้นเป็นดัชนีในตัวอักษร base64
  • กรณีมุม: เมื่ออาร์เรย์ไบต์อินพุตความยาวจะไม่ถูกหารด้วย 3 จากนั้นเพิ่ม=หรือ==เพื่อผลลัพธ์

โซลูชันด้านล่างใช้งานได้กับชิ้นขนาด 3 ไบต์จึงเหมาะสำหรับอาร์เรย์ขนาดใหญ่ โซลูชันที่คล้ายกันในการแปลง base64 เป็นอาร์เรย์ไบนารี (ไม่มีatob) อยู่ที่นี่


ฉันชอบความกะทัดรัด แต่การแปลงเป็นสตริงที่แสดงเลขฐานสองแล้วย้อนกลับนั้นช้ากว่าโซลูชันที่ยอมรับมาก
Garr Godfrey

0

ใช้สิ่งต่อไปนี้เพื่อแปลงอาร์เรย์ uint8 เป็นสตริงที่เข้ารหัส base64

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


-3

หากคุณต้องการเพียงแค่ใช้ JS ของตัวเข้ารหัส base64 เพื่อให้คุณสามารถส่งข้อมูลกลับได้คุณสามารถลองใช้btoaฟังก์ชันนี้ได้

b64enc = btoa(uint);

บันทึกย่อสองสามข้อเกี่ยวกับ btoa - มันไม่ได้มาตรฐานดังนั้นเบราว์เซอร์จึงไม่ได้บังคับให้รองรับ อย่างไรก็ตามเบราว์เซอร์ส่วนใหญ่ทำ คนตัวใหญ่อย่างน้อย atobคือการแปลงตรงกันข้าม

หากคุณต้องการการใช้งานแบบอื่นหรือคุณพบกรณีขอบที่เบราว์เซอร์ไม่รู้ว่าคุณกำลังพูดถึงอะไรการค้นหาตัวเข้ารหัส base64 สำหรับ JS จะไม่ยากเกินไป

ฉันคิดว่ามี 3 คนที่แขวนอยู่บนเว็บไซต์ของ บริษัท ด้วยเหตุผลบางอย่าง


ขอบคุณฉันไม่ได้ลองมาก่อน
Caio Keto

10
บันทึกสองสามข้อ จริงๆแล้ว btoa และ atob เป็นส่วนหนึ่งของกระบวนการสร้างมาตรฐาน HTML5 และเบราว์เซอร์ส่วนใหญ่สนับสนุนพวกเขาในลักษณะเดียวกันอยู่แล้ว ประการที่สอง btoa และ atob ทำงานกับสตริงเท่านั้น การรัน btoa บน Uint8Array ก่อนอื่นจะแปลงบัฟเฟอร์เป็นสตริงโดยใช้ toString () ผลลัพธ์ในสตริง "[object Uint8Array]" นั่นอาจไม่ใช่สิ่งที่ตั้งใจ
kanaka

1
@CaioKeto คุณอาจต้องการพิจารณาเปลี่ยนคำตอบที่คุณเลือก คำตอบนี้ไม่ถูกต้อง
kanaka

-4

npm ติดตั้ง google-closed-library - บันทึก

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.jsจะเขียนAVMbY2Y =ลงในคอนโซล


1
มันตลกว่า-veคำตอบที่ได้รับการโหวตเป็นที่ยอมรับมากกว่าสูง+veหนึ่ง
Vishnudev
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.