Приходится иметь дело с форматом Base64? Тогда этот сайт идеально вам подойдет! Воспользуйтесь нашим невероятно удобным онлайн-инструментом для кодирования или декодирования ваших данных.

Кодирование Base64 в Kotlin: полное руководство

Половина каждого разговора о Base64 - о том, чтобы прочитать упакованные данные обратно в байты. Другая половина, ради которой вы на правильном сайте, - о том, чтобы в первую очередь произвести эти упакованные данные. Где-то в вашем приложении есть байты, которым нужно проехать по каналу, понимающему только текст: строка JSON, HTTP-заголовок, письмо, URL, файл конфигурации. Base64 - классический ответ, и у Kotlin на него есть первоклассный ответ в стандартной библиотеке: класс Base64 в пакете kotlin.io.encoding, стабильный с Kotlin 2.2.

Это руководство проходит по тому, что вам действительно нужно, когда Base64 производите вы: четыре готовых схемы, регулятор заполнителя, правила переноса строк, по которым живут почта и сертификаты, URL-безопасный алфавит, откуда берутся байты, и набор реальных сценариев - в каждом из них Kotlin. Сам формат - как три байта становятся четырьмя символами и откуда берётся =, - разобран на домашней странице, так что здесь мы идём сразу к Kotlin.

Один класс, четыре готовых экземпляра

Весь API - один единственный класс Base64 в пакете kotlin.io.encoding. Нет никакого объекта encoder, который пришлось бы конструировать, и нет билдера. Вместо этого класс отгружается с четырьмя готовыми экземплярами, по одному на каждую схему из RFC, и сопутствующим объектом, который молча подменяет самый частый:

ЭкземплярАлфавитПеренос строк при кодированииЗаполнитель при кодированииКогда применять
Base64.Default+ и /нетвыдаёт =универсальный случай, API, data URI
Base64.UrlSafe- и _нетвыдаёт = (отключите)URL, токены, JWT
Base64.Mime+ и /CRLF каждые 76 символоввыдаёт =тела писем и вложения
Base64.Pem+ и /CRLF каждые 64 символавыдаёт =сертификаты и закрытые ключи

Деталь именования, на которой спотыкаются: это экземпляры, а не фабрики. Каждый экземпляр - неизменяемое значение, и изменение его поведения, например заполнителя, возвращает новый экземпляр вместо того, чтобы мутировать старый. Это делает готовые экземпляры безопасными для общего доступа между потоками и хранения в объектах, и именно поэтому весь класс может быть простым типом значения без внутреннего состояния.

Ваше первое кодирование: байты на входе, строка на выходе

Вот самая маленькая полезная программа на сайте: пять байтов на входе, восьмисимвольная строка на выходе. Вход всегда - ByteArray (или его срез), а результат - самая обычная String, которую можно положить туда, где разрешён текст:

import kotlin.io.encoding.Base64
fun main() {
  val bytes = "Hello".encodeToByteArray()
  val packed = Base64.encode(bytes)
  println(packed)  // SGVsbG8=
}

Эта одна строка делает больше работы, чем кажется. Kotlin даёт несколько форм той же операции, и все они читаются ровно так, как говорит функция:

  • encode(bytes) возвращает String, форма выше.
  • encodeToByteArray(bytes) возвращает ByteArray из ASCII-символов, удобно, когда сама упакованная форма попадёт в другой буфер.
  • encodeIntoByteArray(bytes, destination) пишет в ByteArray, который вы уже выделили, - это экономит одну аллокацию на горячих путях.
  • encodeToAppendable(bytes, builder) дописывает в что угодно, реализующее Appendable, например StringBuilder, - естественный вариант, когда вы собираете более крупный документ.

Все четыре принимают один и тот же опциональный диапазон startIndex и endIndex, так что можно упаковать срез большого буфера, не копируя его заранее. Поскольку Base64.Default - сопутствующий объект, можно ещё и опустить экземпляр и написать Base64.encode(bytes) как сахар; обе формы - один и тот же вызов.

Правило 4/3: насколько длиннее получится?

Прежде чем отправлять кодировщик в работу, стоит точно знать, насколько больше будет вывод, потому что Base64 тратит символы на информацию, которую у него уже была. Математика строгая: каждые 3 входных байта превращаются ровно в 4 выходных символа, так что оставшиеся 1 или 2 байта всё равно съедают полную четвёрку, дописанную = до заполнения группы. Результат для первых нескольких размеров:

Входные байты12345678
Выходные символы4448881212

Формула за таблицей - 4 * ceil(bytes / 3). В худшем случае один байт становится четырьмя символами - надбавка в 300 процентов; от трёх байтов и выше она сходится примерно к трети лишних данных в сети. Это и есть вся модель стоимости: различий между экземплярами нет, и именно поэтому с Base64-ированием больших нагрузок лучше быть намеренным, а не тянуться за ним по рефлексии.

Заполнитель - это настройка, а не приговор

На стороне кодирования символы = - решение политики, и Kotlin делает его первоклассным. У каждого экземпляра есть PaddingOption, а withPadding отдаёт вам новый экземпляр с передвинутым регулятором. Все четыре готовых экземпляра стартуют с PRESENT, именно поэтому "Hello" выходит как SGVsbG8=, а не как SGVsbG8:

import kotlin.io.encoding.Base64
fun main() {
  val bytes = "Hello".encodeToByteArray()
  val noPad = Base64.Default.withPadding(Base64.PaddingOption.ABSENT)
  println(Base64.encode(bytes))      // SGVsbG8=
  println(noPad.encode(bytes))       // SGVsbG8
}

На регуляторе четыре позиции. Первое слово имени решает, что выдаёт кодировщик; вторая половина решает, насколько строгим будет декодер того же экземпляра, когда вы (или другая сторона) потом повернёте его:

PaddingOptionКодировщик выдаёт =Декодер принимает =
PRESENTдаобязателен, всё остальное падает
ABSENTнетзапрещён, лишний заполнитель падает
PRESENT_OPTIONALдакак угодно
ABSENT_OPTIONALнеткак угодно

Самый частый выбор при кодировании - ABSENT с алфавитом UrlSafe, это ровно та форма, которую ждут JSON Web Token и многие URL-схемы. Мы ещё с ней встретимся.

Base64url: URL, токены и JWT

Классический алфавит содержит + и /, и оба - катастрофа в URL: + в строке запроса обычно читают как пробел, а / - разделитель путей. Раздел 5 RFC 4648 определяет URL-безопасный вариант, подменяя их на - и _, и Base64.UrlSafe - это та самая схема. Кодирование тех же байтов, которые в классическом алфавите дают /, показывает подмену в деле:

import kotlin.io.encoding.Base64
fun main() {
  val bytes = "Hello?".encodeToByteArray()
  println(Base64.encode(bytes))          // SGVsbG8/
  println(Base64.UrlSafe.encode(bytes))  // SGVsbG8_
}

Канонический пользователь из реального мира - JWT, у которого заголовок и пелод - base64url без заполнителя, склеенные точками. Вот кодирующая половина его построения - форма, которую стоит понимать, даже если подпись финального токена делает библиотека:

import kotlin.io.encoding.Base64
fun main() {
  val header = """{"alg":"HS256","typ":"JWT"}"""
  val payload = """{"sub":"1234567890","name":"John Doe"}"""
  val noPad = Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT)
  val h = noPad.encode(header.encodeToByteArray())
  val p = noPad.encode(payload.encodeToByteArray())
  val token = "$h.$p.Ym9nVXNlZlNpZ25hdHVyZUZvckRlbW8"
  println(token)
}

Печатает:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.Ym9nVXNlZlNpZ25hdHVyZUZvckRlbW8

Два предупреждения тут уместны. Первое: третий сегмент - подпись, и настоящую создают только по-настоящему криптографическими средствами (подписчик JCA/JCE или JWT-библиотека), никогда кустарными байтами; сниппет выше лишь демонстрирует форму кодирования. Второе: если вы на JVM и по привычке тянетесь за java.util.Base64.getUrlEncoder(), заметьте, что по умолчанию он добавляет заполнитель, так что для JWT-стиля там нужен .withoutPadding(); Kotlin-пресет из коробки дополняет так же явно, а выход из этого - вызов withPadding.

Перенос строк: готовые экземпляры Mime и Pem

Два из четырёх готовых экземпляров переносят свой вывод на короткие строки, и причина историческая. Старые почтовые транспорты портили длинные строки, поэтому раздел 6.8 RFC 2045 ограничивает MIME base64 семьюдесятью шестью символами в строке; инструменты PKI, следуя более старой PEM-традиции, используют шестьдесят четыре. Kotlin впекает оба правила прямо в пресеты: разделитель строк - CRLF, перенос падает ровно на пределе, а завершающего разделителя в самом конце нет. Пелод в 200 байт через каждый из них выглядит так:

import kotlin.io.encoding.Base64
fun main() {
  val data = ByteArray(200) { (it % 251).toByte() }
  println(Base64.Mime.encode(data).lines().maxOf { it.length })  // 76
  println(Base64.Pem.encode(data).lines().maxOf { it.length })   // 64
}

Для этого входа Mime производит 4 строки, а Pem - 5. Первая строка Mime:

AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4

Ловушка, которую стоит помнить, - в обратном направлении от того, под которое вы пишете код: перенесённый вывод - это не одна строка. Если сунуть вывод Mime строгому потребителю одной строки, CRLF станут ошибкой декодирования, так что выбирайте обёртку по каналу, а не по удобству. Для API, data URI и всего современного Default - правильный дефолт, а перенос - это история про почту и сертификаты.

Откуда берутся байты?

Кодировщик честен ровно настолько, насколько честны байты, которые вы ему отдаёте, и интересные решения принимаются на шаг до вызова encode. Самый частый источник - текст, и самая частая ошибка - позволить кодировке решиться молча:

  • text.encodeToByteArray() - всегда UTF-8, на любой платформе. Это правильный выбор для JSON, почты и веб-данных и неправильный, если текст - Latin-1 или UTF-16, а другая сторона декодирует соответственно.
  • На JVM можно выбрать явно с помощью инлайн-расширения text.toByteArray(charset), которое живёт в стандартной библиотеке с Kotlin 1.0 и является ответом Kotlin на getBytes(charset) Java. На kotlin.String нет getBytes, так что если напишете text.getBytes() на Kotlin-строке, компилятор вам скажет; расширение - правильный путь.
import kotlin.io.encoding.Base64
fun main() {
  val text = "héllo"
  println(Base64.encode(text.encodeToByteArray()))               // aMOpbGxv
  println(Base64.encode(text.toByteArray(Charsets.ISO_8859_1)))  // aOlsbG8=
}

Одни и те же пять букв, две разные упакованные формы, потому что байты были разными ещё до какого-либо Base64. Если декодер потом предположит UTF-8, Latin-1-версия декодируется в кракозябры, и ни один Base64-трюк ни на одном конце не починит несовпадение кодировок.

Прочие источники байтов следуют той же форме. Файл - file.readBytes() или path.readBytes(), затем encode. Заранее выделенный буфер - encodeIntoByteArray(bytes, destination). Документ, который ещё собирается, - encodeToAppendable(bytes, builder); он возвращает приёмник, так что вызовы цепляются как методы билдера:

import kotlin.io.encoding.Base64
fun main() {
  val sb = StringBuilder("prefix-")
  Base64.encodeToAppendable("Hello".encodeToByteArray(), sb)
  println(sb)  // prefix-SGVsbG8=
}

А на JVM есть и потоковая форма для вводов, которые не помещаются в памяти, - всё ещё помеченная экспериментальной и импортируемая под своим именем. Перевёртыш в именовании: encodingWith оборачивает выходной поток, так что записи, сделанные через него, выходят как base64, а байты base64 ложатся в лежащий под ним поток:

import java.io.ByteArrayOutputStream
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.io.encoding.encodingWith
@OptIn(ExperimentalEncodingApi::class)
fun main() {
  val raw = ByteArray(10_000) { (it % 251).toByte() }
  val packed = ByteArrayOutputStream()
  packed.encodingWith(Base64.Default).use { encoded ->
    encoded.write(raw)
  }
  println(packed.size())  // 13336
}

Правило-ориентир: encode в памяти для всего, что помещается, encodingWith для потоков, которые не помещаются, и явная кодировка всякий раз, когда байты - на самом деле текст.

Полевые заметки: Basic auth HTTP

Basic auth HTTP - самый древний сценарий применения Base64 в интернете, и она до сих пор повсюду в сервис-сервисном трафике. RFC 7617 определяет схему: возьмите пользователя и пароль, склейте одним двоеточием, прогоните результат через base64 и отправьте как Basic плюс пробел плюс упакованная строка в заголовке Authorization. На Kotlin:

import kotlin.io.encoding.Base64
fun main() {
  val credentials = "alice:s3cr3t"
  val header = "Basic " + Base64.encode(credentials.encodeToByteArray())
  println(header)  // Basic YWxpY2U6czNjcjN0
}

Почему здесь Base64, а не что-то посильнее? Потому что значение заголовка должно быть одним печатаемым токеном, и Base64 это гарантирует. Честное предупреждение: Base64 - кодирование, а не шифрование. Любой клиент развернёт YWxpY2U6czNjcjN0 обратно в alice:s3cr3t одним шагом, поэтому Basic auth годится только для TLS-соединений, в идеале с токенами, а не с человеческими паролями. Когда разбираете такой заголовок, делите по двоеточию ровно один раз, потому что в пароле законно могут встречаться двоеточия.

Полевые заметки: изображения и data URI

data URI встраивают бинарные ресурсы прямо в HTML, CSS и JSON, чтобы браузеру не пришлось делать второй запрос. Форма такая: медийный тип, запятая, слово base64, ещё одна запятая и упакованные байты:

import kotlin.io.encoding.Base64
fun main() {
  val png = byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47, 0x0D.toByte(), 0x0A.toByte(), 0x1A.toByte(), 0x0A.toByte())
  val dataUrl = "data:image/png;base64," + Base64.encode(png)
  println(dataUrl)  // data:image/png;base64,iVBORw0KGgo=
}

Байты выше - первые восемь байт файла PNG, магическое число, которое проверяет любой декодер. Почему здесь подходит Base64: пелод должна быть URL-безопасным текстовым токеном внутри разметки, а Base64 - единственное широко поддерживаемое бинарно-текстовое кодирование со стабильной грамматикой. Ловушка - размер. Логотип на 300 килобайт превращается примерно в 400 килобайт разметки, и каждый лишний килобайт оплачивается при каждой загрузке страницы, которая его включает. data URI - отличный инструмент для иконок, аватаров и мелких спрайтов; ужасный - для видео и даже посредственный - для большого фото. Замеряйте, прежде чем встраивать.

Полевые заметки: почтовые вложения

SMTP - текстовый протокол, старше самого понятия бинарных данных, поэтому каждое вложение в каждом письме, которое вы когда-либо получали, - это Base64, перенесённый на строки в 76 символов и объявленный заголовком Content-Transfer-Encoding: base64. Минимальная MIME-часть с мелким бинарным вложением выглядит так, со слотом тела, заполненным сгенерированным Kotlin выводом для 5-байтного заголовка %PDF-:

From: sender@example.com
To: receiver@example.com
Subject: report
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="cut-here"

--cut-here
Content-Type: text/plain; charset="utf-8"

The quarterly report follows as an attachment.

--cut-here
Content-Type: application/pdf
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="report.pdf"

JVBERi0=
--cut-here--

(Тело JVBERi0= - это base64 пяти байтов %PDF-; настоящий отчёт перенёсся бы на много строк по 76 символов.) Kotlin-сторона - одна строка, когда у вас есть байты файла:

import kotlin.io.encoding.Base64
fun main() {
  val pdf = byteArrayOf(0x25, 0x50, 0x44, 0x46, 0x2D)  // "%PDF-"
  println(Base64.Mime.encode(pdf))  // JVBERi0=
}

Ловушки тут - дисциплина канала. Для тела используйте Mime, а не Default, потому что строгий MIME-парсер ждёт переносов, а обычная 10 000-символьная строка Default будет отклонена или порвана некоторыми транспортами. Держите регистр заголовка ровно base64 в строке Content-Transfer-Encoding и помните, что перенос - часть формата: неперенесённый вывод и перенесённый вывод - разные представления одних и тех же байтов, и парсер на другой стороне должен знать, какое именно он ест.

Полевые заметки: JSON API и загрузки

Когда API хочет бинарных данных в JSON-документе, договорённость - строковое поле с base64, и это один из самых удобных паттернов экосистемы, потому что у JSON для текста и так есть дом. С kotlinx.serialization круговой рейс выглядит так:

import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlin.io.encoding.Base64
@Serializable
data class UploadRequest(val name: String, val payload: String)
fun main() {
  val icon = byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47)
  val request = UploadRequest("icon.png", Base64.encode(icon))
  val json = Json.encodeToString(UploadRequest.serializer(), request)
  println(json)  // {"name":"icon.png","payload":"iVBORw=="}
}

Почему здесь Base64: в JSON нет бинарного типа, поэтому пелод должна быть текстом, а Base64 - наименее неожиданная бинарная грамматика, которую потребитель API узнает без документации. Ловушка - масштаб. Надбавка 4/3 уплачивается в каждом запросе и в каждом ответе, и загрузка в 10 мегабайт становится JSON-строкой в 13,3 мегабайта, которую ваш парсер должен удержать, экранировать и проверить в памяти. Для больших файлов multipart/form-data или бинарное тело почти всегда лучший формат для канала; оставьте base64-в-JSON для миниатюр, иконок, подписей и мелких кусков, где удобство перевешивает налог.

Полевые заметки: конфигурация и командная строка

Два последних паттерна - маленькие, а встречаются в каждой кодовой базе. Значения конфигурации, токены, лицензионные ключи, иногда мелкие секреты часто ездят через переменные окружения и property-файлы именно как base64, потому что транспорт чисто текстовый, а в значении могут встретиться кавычки или переносы строк. Чтение обратно - тот же двухшаговый танец в обратную сторону: System.getenv или поиск в свойствах, затем декодирование. В командной строке кодирование файла для транспортировки или осмотра - программа на десять строк:

import java.io.File
import kotlin.io.encoding.Base64
fun main(args: Array<String>) {
  require(args.isNotEmpty()) { "usage: b64encode <file>" }
  val bytes = File(args[0]).readBytes()
  val encoded = Base64.encode(bytes)
  File(args[0] + ".b64").writeText(encoded)
  println("Wrote ${encoded.length} characters to ${args[0]}.b64")
}

Запущенное на файле с десятью байтами hello file, оно напишет 16 символов: aGVsbG8gZmlsZQ==. Ловушки в обоих случаях - те же две: Base64 в конфигурации - не хранилище, значение в одном шаге от открытого текста и его всё равно стоит считать секретом в сети, а самописный CLI-инструмент должен осознанно решать вопрос с алфавитом, потому что пользователю, который прогонит ваш вывод в URL, понадобится UrlSafe, а не Default.

Что может пойти не так при кодировании

Кодирование снисходительно к содержимому: любая последовательность байтов - валидный вход, поэтому ошибки вида «неверный символ», как у декодеров, здесь нет. Что действительно бросает исключение, - так это геометрия, и сообщения достаточно точны, чтобы быть полезными:

СитуацияИсключениеСообщение
endIndex за концом массиваIndexOutOfBoundsExceptionstartIndex: 0, endIndex: 100, size: 5
startIndex больше endIndexIllegalArgumentExceptionstartIndex: 3 > endIndex: 2
массив назначения слишком мал для encodeIntoByteArrayIndexOutOfBoundsExceptionThe destination array does not have enough capacity, destination offset: 0, destination size: 2, capacity needed: 8

Рядом с ними сидят две специфичные Kotlin-ловушки. Первая - классическая ошибка int + String: bytes.size + " bytes" не компилируется, потому что плюс от Int строки не склеивает; форма интерполяции "${bytes.size} bytes" - именно Kotlin-путь. Вторая - поиск кодировки, который бросает UnsupportedCharsetException на имени, которое JVM не знает, вроде Charset.forName("utf-9"), так что опечатка в имени кодировки - это исключение времени выполнения, а не ошибка компиляции, и проявляется оно там, где работает кодировщик, а не там, где имя вводили.

Ловушки, в Kotlin-стиле

Ловушки ниже - именно те, которые особенно кусают Kotlin-разработчиков, впервые тянущихся к стандартной библиотеке:

  • Позволять encodeToByteArray() выбирать кодировку за вас. Она всегда UTF-8 и молча, и Latin-1 или UTF-16 исходник упаковывается в байты, которые декодер не сможет прочитать обратно. Решайте кодировку осознанно - на JVM, когда это не UTF-8, через toByteArray(charset).
  • Тянуться за java.util.Base64 из мышечной памяти. Его getUrlEncoder() по умолчанию добавляет заполнитель, что для JWT - неправильная форма, если вы не помните .withoutPadding(); Kotlin-пресет делает выбор явным на обеих сторонах.
  • Кормить перенесённым выводом Mime или Pem потребителя одной строки. CRLF - часть представления, и строгий декодер, ожидающий одну строку, упадёт; переносите только тогда, когда канал ждёт переноса.
  • Писать text.getBytes() на Kotlin-строке. Метод Java на kotlin.String не виден; инлайн-расширение toByteArray(charset), доступное с Kotlin 1.0, - замена.
  • Гонять старый тулчейн. Системный Kotlin на некоторых дистрибутивах всё ещё 1.3, и он целиком предшествует Base64 стандартной библиотеки; классу нужны 1.8.20, чтобы существовать, 2.0.20 - для управления заполнителем и 2.2 - чтобы стать стабильным.
  • Относиться к Base64 как к слою безопасности. Это транспортное кодирование с публичным одношаговым обратным ходом. Всё секретное должно шифроваться до упаковки, а не просто упаковываться.

Как выбирать: короткий гид по решениям

Когда сомневаетесь, решение почти всегда принимает канал, а не содержимое. Короткая версия:

  • Base64.Default для API, JSON, data URI и всего, что фактически - одна строка текста. Заполненный вывод - самая совместимая форма на проводах.
  • Base64.UrlSafe с заполнителем ABSENT для токенов, JWT и всего, что попадает в сегмент URL или параметр запроса.
  • Base64.Mime для тел писем и вложений, где строки в 76 символов - жёсткое требование формата.
  • Base64.Pem для сертификатов и закрытых ключей, где строки в 64 символа - то, чего ждёт каждый PKI-инструмент.

Плюс две сквозные привычки: делайте кодировку явной всякий раз, когда вход - текст, и следите за надбавкой 4/3, чтобы большие нагрузки получали бинарный канал, а не base64.

Дорога в стандарт

Путь в стандартную библиотеку, на котором появился Base64, достаточно свеж, чтобы вы сталкивались со старым Kotlin без него. Класс впервые появился в Kotlin 1.8.20 в апреле 2023 года, помеченным экспериментальным, с тремя экземплярами и более простой поверхностью: кодирование всегда добавляло заполнитель, и попросить меньше было негде. Если вы видели код эпохи 1.8, который срезает = с конца строки через removeSuffix, - это был единственный инструмент той эпохи для вывода без заполнителя, и привычку, стоящую за этим, пора бросить. Kotlin 2.2, вышедшая в июне 2025 года, стабилизировала API и добавила последний кусок - экземпляр Pem. Регулятор PaddingOption и withPadding пришли ещё в ветке 2.0, точнее в 2.0.20, - первый первоклассный способ управлять заполнителем в обе стороны. Потоковые вспомогательные функции encodingWith и decodingWith остаются экспериментальными и только для JVM; так стандартная библиотека помечает API, которым нужно больше полевого опыта, прежде чем замораживать их. С ветки 2.4.0 язык перешёл и на 18-месячное окно поддержки стандартной библиотеки, так что проект, закреплённый на компиляторе 2.4.x, вроде стабильного релиза 2.4.10, актуального на момент написания, получает полный API Base64 на всё время этого цикла поддержки.

Маленькие чудеса

Несколько деталей, из-за которых класс становится интереснее, когда их знаешь:

  • Base64.encode(bytes) без экземпляра работает, потому что Base64.Default определён в сопутствующем объекте; сопутствующий объект есть схема по умолчанию, так что сахар и именованная форма - буквально один и тот же объект.
  • Функция encodeToAppendable - в стиле билдера: она возвращает приёмник-appendable, поэтому документированный паттерн - игнорировать возвращаемое значение и продолжать пользоваться своим билдером.
  • Заполнитель никогда не заполняет целую группу: строка base64 заканчивается нулём, одним или двумя символами =, и подсчёт заполнителя говорит вам ровно, сколько байтов осталось у оригинала в последней тройке.
  • Pem - самый молодой из четырёх готовых экземпляров, он присоединился в 2.2; перенос на 64 символа - PKI-конвенция старше правила RFC 2045, с которым она сидит бок о бок.
  • На JVM стандартная библиотека намеренно не делегирует java.util.Base64; две реализации раздельны, что держит поведение идентичным на всех платформах ценой закомментированной оптимизации, которую команда Kotlin хранит в дереве кода на будущее, когда Java API это позволит.
  • Тот же релиз 2.2, что стабилизировал Base64, стабилизировал и HexFormat - класс шестнадцатеричного форматирования в kotlin.text, экспериментальный с Kotlin 1.9, - так что текстовые кодирования на уровне байтов теперь имеют устойчивый дом в стандартной библиотеке.

Итоги и куда идти дальше

Производство Base64 в Kotlin - это короткий список осознанных выборов: выбирайте готовый экземпляр по каналу, решайте вопрос с заполнителем намеренно, держите кодировку явной, когда вход - текст, и уважайте надбавку 4/3, когда нагрузка большая. Всё остальное - файлы, буферы, appendable, потоки - тонкая обёртка вокруг тех же четырёх экземпляров. Обратно, превращение этой упакованной строки в байты, - это свои правила строгости, свои режимы сбоев и свои ловушки, и связанная статья на сестринском сайте подробно разбирает декодирование Base64 в Kotlin.

Последнее обновление: 2026-09-08

Связанная статья: Декодирование Base64 в Kotlin: полное руководство