今回は、プログラミング能力向上を目指して、自力でBase64エンコーダーを作ってみようと思います。JavaScriptは幅広いユーザーに使われている言語で既に多くのライブラリが存在しています。そのため、意外と立て込んだ処理を自力で実装することは多くないかもしれません。しかし、実際に自分で一から実装してみると学べることが多くあります。
Base64とは?
そもそも「Base64」とは、データを英数字と記号を用いて表現するエンコード方式です。バイナリデータを印字可能なテキストとして表現できるため、電子メールやHTMLなどさまざまな用途で用いられます。
例えば、"hello!"という文字列をBase64で表すと"aGVsbG8h"になります。また、"JavaScript"という文字列を変換すると"SmF2YVNjcmlwdA=="になります。
基本的にアルファベット(大文字と小文字)と記号(+と-)の64文字でデータを表現する符号化方式のため、Base64と呼ばれます。なお、パディング処理に=記号に使うため実際には65文字で表現します。データ量はバイナリデータに比べて約1.3倍となります。
Base64の符号化方式
それでは、バイナリデータをBase64に変換する方法を確認してみましょう。基本的にはバイナリデータを6ビットに分割して規定の文字に変換するという比較的単純な符号化方式です。
実際には以下のような手順で操作を行います。
(1) 文字列であればバイナリデータに変換しておく
(2) データを2進数に変換し6ビットごとに分割する(この時、余った部分は0にする)
(3) 変換表に従って各6ビットをBase64の文字に変換する
(4) 変換後の文字列は必ず4文字ずつにする(足りない部分は"="で埋める)
上記手順の(3)で使う変換表ですが、0から63までの値は、A-Za-z0-9+/の順に並んだもので、次の通りの表です。
いくつかプログラムのテスト用に、Base64への変換結果を用意してみました。(日本語の文字列はUTF-8であることを前提にしています。)この結果が得られるようなプログラムを作っていきましょう。
文字列 | Base64 |
---|---|
HTML | SFRNTA== |
hello! | aGVsbG8h |
JavaScript | SmF2YVNjcmlwdA== |
生姜焼き定食 | 55Sf5aec54S844GN5a6a6aOf |
JavaScriptで実装してみよう
実際にプログラムしてみると次のようになるでしょう。皆さんも、ぜひ答えを見ずに挑戦してみましょう。何分で完成させることができるでしょうか。
なお、以下のプログラムは、ビット操作をするのではなく文字列処理を駆使して作ったものなので、実行効率はあまりよくありませんが、比較的短時間で実装できました。Basse64エンコーダーの仕組みを確認するのに助けになるでしょう。
// Base64の変換テーブル
const table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
// Base64へ変換する
function base64encode(src) {
// 文字列srcをUTF-8バイナリに変換 --- (*1)
const bin = (new TextEncoder()).encode(src)
// 2進数に変換 --- (*2)
let binStr = ''
for (let i = 0; i < bin.length; i++) {
const c = bin[i]
const b = "00000000" + c.toString(2)
const b8 = b.substr(b.length-8)
binStr += b8
}
// 6ビットごとに詰める --- (*3)
if (binStr.length % 6 > 0) {
binStr += "000000".substr(0, 6 - binStr.length % 6)
}
// console.log(hex, "len=", hex.length)
// 6bitに区切って符号化 --- (*4)
let res = ''
while (binStr != '') {
// 6bit取り出す --- (*5)
const b6 = binStr.substr(0, 6)
binStr = binStr.substr(6)
const i6 = parseInt(b6, 2) // 数値変換
const c = table.substr(i6, 1) // Base64テーブルで変換
res += c
// console.log(c, b6, i6, '|', hex)
}
// パディングで詰める --- (*6)
if (res.length % 4 > 0) {
res += "====".substr(0, 4 - res.length % 4)
}
return res
}
// テスト実行
console.log(base64encode("HTML"))
console.log(base64encode("hello!"))
console.log(base64encode("JavaScript"))
console.log(base64encode("生姜焼き定食"))
プログラムを実行するには、本連載2回目で紹介したNode.jsを利用しても良いですし、ブラウザの開発者ツール(デベロッパーツール)にあるコンソールで試すこともできます。
以下はChromeのデベロッパーツールを利用して実行してみたところです。Chromeでは、メニューから「その他のツール > デベロッパーツール > コンソール」を選択し、上記のプログラムを貼り付けて実行します。
次にプログラムを確認してみましょう。プログラムの(*1)では、入力された文字列をUTF-8のバイナリ(Uint8Array)に変換します。
そして、(*2)では各文字列を2進数に変換します。数値のtoString(2)メソッドと値を二進数の文字列に変換できます。なおtoString(2)の変換結果は8字未満の場合もあるので8ビットになるように揃えます。(*3)では、前述変換規則2の通り各値が6ビットになるように0で詰めます。ここまでの処理で、変数binStrにはデータを2進数に変換した文字列が得られます。
続いて、(*4)では文字列を6文字(実際の6ビット)ずつに切り取り、Base64変換表の文字を出力します。「parseInt(二進数文字列, 2)」のようにparseInt関数を使うと二進数文字列を数値に変換できます。これを利用して、Base64変換文字列(table)の文字に変換します。最後(*6)では4の倍数文字に満たない場合に"="で詰めたら完成です。
まとめ
以上、今回はプログラミング力の腕試しとして、Base64エンコーダーを自作する方法を紹介しました。なお、Base64は幅広く使われており、画像データをCSSやHTMLに埋め込むなど、いろいろな場面で利用されています。
そのため、GitHubなどオープンソースのプロジェクトを検索すると、たくさんの実装を見つけられます。
- Base64.js(https://github.com/davidchambers/Base64.js/blob/master/base64.js
- js-base64(https://github.com/dankogai/js-base64/blob/main/base64.js)
実際に自分で頑張って作った後に、達人のプログラムを確認してみましょう。自分の作ったプログラムと比べてみると、反省点もたくさん見つかりとても勉強になります。ぜひ、作ってみてください。
自由型プログラマー。くじらはんどにて、プログラミングの楽しさを伝える活動をしている。代表作に、日本語プログラミング言語「なでしこ」 、テキスト音楽「サクラ」など。2001年オンラインソフト大賞入賞、2004年度未踏ユース スーパークリエータ認定、2010年 OSS貢献者章受賞。技術書も多く執筆している。直近では、「シゴトがはかどる Python自動処理の教科書(マイナビ出版)」「すぐに使える!業務で実践できる! PythonによるAI・機械学習・深層学習アプリのつくり方 TensorFlow2対応(ソシム)」「マンガでざっくり学ぶPython(マイナビ出版)」など。