Бінарні дані в рядку JSON Щось краще, ніж Base64


614

Формат JSON спочатку не підтримує двійкові дані. Бінарні дані слід уникати, щоб вони могли бути розміщені в рядковому елементі (тобто нульовому чи більше символів Unicode в подвійних лапках з використанням зворотних косої риски) в JSON.

Очевидним методом уникнення двійкових даних є використання Base64. Однак Base64 має високі витрати на обробку. Крім того, він розширює 3 байти на 4 символи, що призводить до збільшення розміру даних приблизно на 33%.

Одним із випадків використання для цього є версія v0.8 специфікації API хмарного зберігання CDMI . Ви створюєте об'єкти даних через REST-веб-сервіс за допомогою JSON, наприклад

PUT /MyContainer/BinaryObject HTTP/1.1
Host: cloud.example.com
Accept: application/vnd.org.snia.cdmi.dataobject+json
Content-Type: application/vnd.org.snia.cdmi.dataobject+json
X-CDMI-Specification-Version: 1.0
{
    "mimetype" : "application/octet-stream",
    "metadata" : [ ],
    "value" :   "TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz
    IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg
    dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu
    dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo
    ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=",
}

Чи є кращі способи та стандартні методи кодування бінарних даних у рядки JSON?


30
Для завантаження: ви робите це лише один раз, тому справа не така вже й велика. Для завантаження вас може здивувати, наскільки добре base64 стискається під gzip , тому якщо на вашому сервері увімкнено gzip, ви, ймовірно, добре.
cloudfeet

2
Ще одне гідне рішення msgpack.org для хардкор- оленів
nicolallias

2
@cloudfeet, раз на користувача за дію . Дуже велика угода.
Pacerier

2
Зауважте, що символи, як правило, по 2 байти пам'яті . Таким чином, base64 може дати + 33% (4/3) накладних витрат на провід, але для розміщення цих даних про провід, його вилучення та використання їх знадобиться + 166% (8/3) накладних витрат . Справа в суті: якщо рядок Javascript має максимальну довжину 100k знаків, ви можете представляти лише 37,5k байтів даних, використовуючи base64, а не 75k байт даних. Ці цифри можуть бути вузьким місцем у багатьох частинах програми, наприклад, JSON.parseтощо. ......
Pacerier

5
@Pacerier "зазвичай 2 байти пам'яті [на символ]" не є точним. Наприклад, v8 містить рядки OneByte та TwoByte. Двобайтні рядки використовуються лише там, де це необхідно, щоб уникнути гротескного споживання пам'яті. Base64 кодується однобайтовими рядками.
ZachB

Відповіді:


459

Є 94 символи Unicode, які можуть бути представлені як один байт відповідно до специфікації JSON (якщо ваш JSON передається як UTF-8). Зважаючи на це, я думаю, що найкраще, що можна зробити з простору, це база85, яка представляє чотири байти у вигляді п'яти символів. Однак це лише 7% -ве поліпшення порівняно з base64, обчислити дорожче, а реалізація зустрічається рідше, ніж для base64, тому, ймовірно, це не виграш.

Ви також можете просто зіставити кожен вхідний байт на відповідний символ у U + 0000-U + 00FF, а потім виконати мінімальне кодування, необхідне стандарту JSON для передачі цих символів; Перевага тут полягає в тому, що необхідне декодування є нульовим за межі вбудованих функцій, але ефективність простору погана - розширення на 105% (якщо всі вхідні байти однаково вірогідні) проти 25% для base85 або 33% для base64.

Остаточний вердикт: base64 виграє, на мою думку, на тій підставі, що це звичайно, легко і не дуже погано , щоб вимагати заміни.

Дивіться також: Base91 та Base122


5
Зачекайте, як просто використовувати фактичний байт при кодуванні символів цитати - 105% розширення, а base64 - лише 33%? Чи не base64 133%?
jjxtra

17
Base91 - це погана ідея для JSON, оскільки вона містить цитати в алфавіті. У гіршому випадку (всі котирування) після кодування JSON, це 245% від початкового корисного навантаження.
jarnoh

25
Python 3.4 включає base64.b85encode()і b85decode()зараз. Просте вимірювання часу синхронізації кодування + декодування показує, що b85 в 13 разів повільніше, ніж b64. Таким чином, ми маємо 7% виграш, але 1300% втрати продуктивності.
Пітер Еннес

3
@hobbs JSON заявляє, що контрольні символи повинні бути уникнуті. Розділ 5.2 RFC20 визначає DELяк контрольний символ.
Тіно

2
@Tino ECMA-404 спеціально перераховує символи, які потрібно уникнути: подвійну цитату U + 0022, зворотну косу рису U + 005C та "контрольні символи U + 0000 до U + 001F".
варення

249

Я зіткнувся з тією ж проблемою, і думав, що поділюсь рішенням: multipart / form-data.

Відправляючи багаточастинну форму, ви надсилаєте спочатку як рядок метадані JSON , а потім окремо надсилаєте як необроблені бінарні файли (зображення (і), WAV) тощо), індексовані іменем Content-Disposition .

Ось хороший підручник про те, як це зробити в obj-c, і ось стаття в блозі, де пояснюється, як розділити рядкові дані за межею форми та відокремити їх від двійкових даних.

Єдина зміна, яку вам дійсно потрібно зробити, - це на стороні сервера; вам доведеться зафіксувати свої метадані, які повинні відповідним чином посилатись на бінарні дані POST (за допомогою межі вмісту-диспозиції).

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

IMHO відправлення закодованих даних base64 - це хак; RFC багаточастинні / форма-дані були створені для таких питань: пересилання бінарних даних у поєднанні з текстовими або метаданими.


4
До речі, API Google Drive робить це таким чином: developers.google.com/drive/v2/reference/files/update#examples
Mathias Conradt

2
Чому ця відповідь настільки низька, коли вона використовує нативні функції замість того, щоб намагатися втиснути круглий (бінарний) кілочок у квадратний (ASCII) отвір? ...
Марк К Коуан

5
надсилання кодованих даних base64 є хак, як і багаточастинні / форма-дані. Навіть пов’язана з вами стаття в блозі зазначає, що, використовуючи багаточастинні / форма-дані типу вмісту, ви заявляєте, що те, що ви надсилаєте, це фактично форма. Але це не так. тому я думаю, що хакер base64 не тільки набагато простіший для реалізації, але і більш надійний. Я бачив деякі бібліотеки (наприклад, для Python), у яких жорсткий код змісту типу "багатосторонні / форми-дані".
t3chb0t

4
@ t3chb0t Тип носія мультичастин / форм-даних був створений для транспортування даних форми, але сьогодні він широко використовується за межами світу HTTP / HTML, зокрема для кодування вмісту електронної пошти. Сьогодні він пропонується як синтаксис загального кодування. tools.ietf.org/html/rfc7578
lorenzo

3
@MarkKCowan Ймовірно, тому що, хоча це корисно для мети питання, воно не відповідає на запитання, яке задається, що ефективно "Низьке накладне кодування бінарного тексту для використання в JSON", ця відповідь повністю відкидає JSON.
Chinoto Vokro

34

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

Як наслідок, якщо для кодування значення байта в діапазоні [0..127] знадобиться лише один байт у кодуванні UTF-8, кодування значення байта в діапазоні [128..255] вимагало б 2 байти! Гірше за це. У JSON символи керування "та \ не дозволяють відображатись у рядку. Тому двійкові дані потребують певного перетворення для правильного кодування.

Подивимося. Якщо припустити рівномірно розподілені значення випадкових байтів у наших бінарних даних, то в середньому половина байтів буде закодована в одному байті, а інша половина в двох байтах. Бінарні дані, кодовані UTF-8, мали б 150% від початкового розміру.

Кодування Base64 зростає лише до 133% від початкового розміру. Тож кодування Base64 є більш ефективною.

Що з використанням іншого базового кодування? У UTF-8 кодування 128 значень ASCII є найбільш ефективною для простору. У 8 бітах ви можете зберігати 7 біт. Отже, якщо ми вирізаємо бінарні дані в 7-ти бітних фрагментах, щоб зберегти їх у кожному байті кодованої рядки UTF-8, кодовані дані виростуть лише до 114% від початкового розміру. Краще, ніж Base64. На жаль, ми не можемо використовувати цей простий трюк, оскільки JSON не дозволяє деякі символи ASCII. 33 символи управління ASCII ([0..31] та 127) та "і \ повинні бути виключені. Це залишає нам лише 128-35 = 93 символів.

Тож теоретично ми могли б визначити кодування Base93, яке збільшило б кодований розмір до 8 / log2 (93) = 8 * log10 (2) / log10 (93) = 122%. Але кодування Base93 було б не так зручно, як кодування Base64. Base64 вимагає скоротити послідовність вхідних байтів на 6-ти бітних відрізках, для яких проста бітова операція працює добре. Крім 133% - це не набагато більше 122%.

Тому я прийшов незалежно до єдиного висновку, що Base64 - це справді найкращий вибір для кодування бінарних даних у JSON. Моя відповідь представляє виправдання для цього. Я погоджуюсь, що це не дуже привабливо з точки зору продуктивності, але врахуйте також перевагу використання JSON, оскільки його читабельне людське представлення рядків легко маніпулювати усіма мовами програмування.

Якщо продуктивність критична, ніж чисте двійкове кодування слід розглядати як заміну JSON. Але з JSON я зробив висновок, що Base64 - найкращий.


Що з Base128, але тоді дозволяти серіалізатору JSON вийти з "і \? Я думаю, розумно очікувати, що користувач використовуватиме реалізацію json parser.
jcalfee314

1
@ jcalfee314, на жаль, це неможливо, оскільки символи з кодом ASCII нижче 32 заборонені в рядках JSON. Кодування з базою між 64 і 128 вже визначені, але необхідні обчислення вище, ніж base64. Посилення в кодованому розмірі тексту не варто.
chmike

Якщо завантаження великої кількості зображень у base64 (скажімо, 1000) або завантаження по-справжньому повільному з’єднанню, то base85 або base93 коли-небудь платять за зменшений мережевий трафік (без / або без gzip)? Мені цікаво, якщо наступить момент, коли більш компактні дані підготують один із альтернативних методів.
vol7ron

Я підозрюю, що швидкість обчислення важливіша, ніж час передачі. Зображення, очевидно, повинні бути попередньо обчислені на стороні сервера. У всякому разі, висновок такий: JSON поганий для двійкових даних.
chmike

Повторно " Кодування Base64 зростає лише до 133% від початкового розміру. Отже, кодування Base64 є більш ефективним ", це абсолютно неправильно, оскільки символи зазвичай мають по 2 байти. Див розробку на stackoverflow.com/questions/1443158 / ...
Pacerier

34

BSON (Бінарний JSON) може працювати для вас. http://en.wikipedia.org/wiki/BSON

Редагувати: FYI .NET-бібліотека json.net підтримує читання та запис bson, якщо ви шукаєте любов до сервера C #.


1
"У деяких випадках BSON використовуватиме більше місця, ніж JSON, через префікси довжини та явні індекси масиву." en.wikipedia.org/wiki/BSON
Pawel Cioch

Хороша новина: BSON в основному підтримує такі типи, як Binary, Datetime та кілька інших (особливо корисно, якщо ви використовуєте MongoDB). Погана новина: його кодування - це двійкові байти ... тож це невідповідь ОП. Однак це було б корисно для каналу, який підтримує бінарні бінарні файли, такі як повідомлення RabbitMQ, повідомлення ZeroMQ або користувальницький сокет TCP або UDP.
Dan H

19

Якщо ви вирішили проблеми з пропускною здатністю, спробуйте спершу стиснути дані на стороні клієнта, а потім base64-it.

Хороший приклад такої магії - на веб-сайті http://jszip.stuartk.co.uk/, і більше обговорення цієї теми знаходиться на JavaScript на Gzip.


2
ось реалізація zip JavaScript, яка вимагає кращої продуктивності: zip.js
Janus Troelsen

Зауважте, що ви можете (і слід) ще стискати після (як правило, через Content-Encoding), оскільки base64 стискає досить добре.
Махмуд Аль-Кудсі

@ MahmoudAl-Qudsi ви мали на увазі, що ви base64 (zip (base64 (zip (дані)))))? Я не впевнений, що додавання ще одного zip, а потім base64 його (щоб мати змогу надсилати його як дані) є гарною ідеєю.
andrej

18

yEnc може працювати для вас:

http://en.wikipedia.org/wiki/Yenc

"yEnc - це схема кодування двійкового тексту для передачі бінарних файлів у [text]. Це зменшує накладні витрати над попередніми методами кодування на основі US-ASCII, використовуючи 8-бітний розширений метод кодування ASCII. Накладні витрати yEnc часто (якщо кожне значення байтів виявляється приблизно з однаковою частотою в середньому) лише 1–2%, порівняно з 33% –40% накладних витрат для 6-бітних методів кодування, таких як uuencode та Base64 ... До 2003 року yEnc став стандартним фактором система кодування бінарних файлів на Usenet. "

Однак yEnc - це 8-бітове кодування, тому зберігання його в рядку JSON має ті ж проблеми, що і для зберігання вихідних бінарних даних - робити це наївним способом означає приблизно 100% розширення, що гірше ніж base64.


42
Оскільки, здається, багато людей досі переглядають це питання, я хотів би зазначити, що я не думаю, що yEnc насправді допомагає тут. yEnc - це 8-бітове кодування, тому зберігання його в рядку JSON має ті ж проблеми, що і для зберігання вихідних бінарних даних - це робити наївним способом означає приблизно 100% розширення, що гірше ніж base64.
варення

У випадках, коли використання кодувань, таких як yEnc з великими алфавітами з даними JSON, вважається прийнятним, безвідмовний спосіб може стати хорошою альтернативою, що забезпечує фіксований загальновідомий наперед накладні витрати.
Іван Косарев

10

Хоча це правда, що base64 має ~ 33% коефіцієнта розширення, не обов'язково вірно, що обробка накладних витрат значно більше, ніж це: це дійсно залежить від бібліотеки / інструментарію JSON, який ви використовуєте. Кодування та декодування є простими прямолінійними операціями, і їх можна навіть оптимізувати кодування символів wrt (оскільки JSON підтримує лише UTF-8/16/32) - символи base64 завжди є однобайтовими для записів рядка JSON. Наприклад, на платформі Java є бібліотеки, які можуть виконувати цю роботу досить ефективно, так що накладні витрати в основному пов'язані із збільшенням розміру.

Я згоден з двома попередніми відповідями:

  • base64 - це простий, загальноприйнятий стандарт, тому навряд чи можна знайти щось краще спеціально для використання з JSON (base-85 використовується постскриптом тощо; але переваги в кращому випадку незначні, якщо ви думаєте про це)
  • стиснення перед кодуванням (і після декодування) може мати багато сенсу, залежно від даних, які ви використовуєте

10

Формат посмішки

Кодування, декодування та компактність дуже швидко

Порівняння швидкості (на основі Java, але все-таки значущо): https://github.com/eishay/jvm-serializers/wiki/

Крім того, це розширення до JSON, що дозволяє пропускати кодування base64 для байтових масивів

Рядки, закодовані посмішкою, можна отримати, коли простір є критичним


3
... і посилання мертва. Це здається актуальним: github.com/FasterXML/smile-format-specification
Zero3

4

( Редагуйте через 7 років: Google Gears пішов. Ігноруйте цю відповідь.)


Колектив Google Gears зіткнувся з проблемою відсутності бінарних даних і намагався вирішити цю проблему:

API Blob

У JavaScript є вбудований тип даних для текстових рядків, але нічого для двійкових даних. Об'єкт Blob намагається вирішити це обмеження.

Можливо, ви можете якось сплести це.


Отже, який статус блобів у Javascript та json? Це було скинуто?
chmike

w3.org/TR/FileAPI/#blob-section Не настільки ефективний, як base64 для простору, якщо прокрутити вниз, ви виявите, що він кодує за допомогою utf8 map (як варіант, показаний у відповіді гоббса). І ніякої підтримки json, наскільки я знаю
Даніеле Крушані

3

Оскільки ви шукаєте можливість підключення бінарних даних до строго текстового та дуже обмеженого формату, я думаю, що накладні витрати Base64 мінімальні порівняно з зручністю, яку ви очікуєте підтримувати в JSON. Якщо обробка та пропускна здатність викликає занепокоєння, то, ймовірно, вам доведеться переглянути свої формати файлів.


2

Просто для додання точки зору ресурсу та складності до дискусії. Оскільки PUT / POST та PATCH зберігають нові ресурси та змінюють їх, слід пам’ятати, що передача вмісту - це точне подання вмісту, який зберігається і який отримується шляхом операції GET.

Повідомлення з декількох частин часто використовується як рятівник, але для простоти та для більш складних завдань я віддаю перевагу ідеї дати вміст у цілому. Це самопояснюється і це просто.

І так, JSON - це щось калічить, але врешті-решт JSON є багатослівним. А накладні витрати на картографування до BASE64 - це шлях до малого.

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

Також подобається підхід BSON, це не так широко і легко підтримується, як хотілося б.

По суті, ми просто щось тут не вистачаємо, але вбудовування бінарних даних як базових64 є добре встановленим способом, і, якщо ви дійсно не визначили необхідність здійснення справжньої бінарної передачі (що навряд чи трапляється так).


1

Я копаю трохи більше (під час впровадження base128 ) і виявляю , що коли ми надсилаємо символи, коди яких ascii перевищують 128, то браузер (хром) насправді надсилає ДВА символів (байтів) замість одного :( . Причина в тому, що JSON за замовчуванням використовуйте utf8 символів, для яких символи з кодами ascii вище 127 кодуються двома байтами, про що було сказано у відповіді chmike . Я зробив тест таким чином: введіть у chrome url bar chrome: // net-export / , виберіть "Включити сировину байтів ", почніть захоплювати, надсилайте POST-запити (використовуючи фрагмент внизу), зупиняйте захоплення та зберігайте json-файл із необробленими даними запитів. Потім ми заглянемо всередину цього файлу json:

  • Ми можемо знайти наш base64 запит, виявивши рядок, 4142434445464748494a4b4c4d4eце шістнадцяткове кодування, ABCDEFGHIJKLMNі ми це побачимо "byte_count": 639.
  • Ми можемо знайти наш вище127 запит, виявивши рядок, C2BCC2BDC380C381C382C383C384C385C386C387C388C389C38AC38Bце запит-шістнадцятковий utf8 код символів ¼½ÀÁÂÃÄÅÆÇÈÉÊË(однак, шістнадцяткові коди цих символів є c1c2c3c4c5c6c7c8c9cacbcccdce). "byte_count": 703Так 64bytes більше , ніж base64 просити , тому що символи з ASCII - кодами вище 127 код по 2 байта в запиті :(

Тож насправді у нас немає прибутку від надсилання символів з кодами> 127 :(. Для рядків base64 ми не спостерігаємо такої негативної поведінки (можливо, і для base85 - я не перевіряю це) - однак, можливо, якесь рішення цієї проблеми буде відправлення даних у двійковій частині багатосторонніх даних / форм-даних POST, описаних у Ælex відповіді (однак зазвичай у цьому випадку нам взагалі не потрібно використовувати базове кодування ...).

Альтернативний підхід може покладатися на відображення двох байтових даних даних в один дійсний символ utf8 за допомогою коду, використовуючи щось на зразок base65280 / base65k, але, ймовірно, він буде менш ефективним, ніж base64 через специфікацію utf8 ...


0

Тип даних дійсно стосується. Я перевіряв різні сценарії надсилання корисного навантаження з ресурсу RESTful. Для кодування я використовував Base64 (Apache) і для стиснення GZIP (java.utils.zip. *). Корисне навантаження містить інформацію про фільм, зображення та аудіофайл. Я стиснув і закодував файли зображень та аудіо, які різко погіршили продуктивність. Кодування до стиснення вийшло добре. Зображення та звуковий вміст надсилаються у вигляді закодованих та стислих байтів [].


0

Посилайтеся: http://snia.org/sites/default/files/Multi-part%20MIME%20Extension%20v1.0g.pdf

Він описує спосіб передачі двійкових даних між клієнтом CDMI та сервером за допомогою операцій "тип вмісту CDMI" без необхідності перетворення бінарних даних base64.

Якщо ви можете використовувати операцію "Тип вмісту, що не стосується CDMI", ідеально передавати "дані" об'єкту в / з нього. Потім метадані можуть бути додані / отримані до / з об'єкта як наступна операція "Тип вмісту CDMI".


-1

Моє рішення зараз, XHR2 використовує ArrayBuffer. ArrayBuffer як двійкова послідовність містить багаточастинні вміст, відео, аудіо, графіку, текст тощо, з кількома типами вмісту. Все в одну відповідь.

У сучасному браузері, що має DataView, StringView і Blob для різних компонентів. Дивіться також: http://rolfrost.de/video.html для отримання більш детальної інформації.


Ви зробите свої дані на + 100%, серіалізуючи масив байтів
Sharcoux,


Серіалізація байтового масиву в JSON - це щось на кшталт: [16, 2, 38, 89]що дуже неефективно.
Шарку
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.