回答:
各文字は6ビット(log2(64) = 6
)を表すために使用されます。
したがって、を表すために4文字が使用され4 * 6 = 24 bits = 3 bytes
ます。
したがって、バイト4*(n/3)
を表す文字が必要でありn
、これは4の倍数に切り上げられる必要があります。
4の倍数に切り上げた結果の未使用の埋め込み文字の数は、明らかに0、1、2、または3になります。
4 * n / 3
パディングされていない長さを与えます。
そして、パディングのために最も近い4の倍数に切り上げます。4は2の累乗なので、ビット単位の論理演算を使用できます。
((4 * n / 3) + 3) & ~3
$(( ((4 * n / 3) + 3) & ~3 ))
4 * n / 3
は既にで失敗しn = 1
、1バイトは2文字を使用してエンコードされ、結果は明らかに1文字です。
参考までに、Base64エンコーダーの長さの式は次のとおりです。
あなたが言ったように、n
データのバイトを与えられたBase64エンコーダーは4n/3
Base64文字の文字列を生成します。言い換えると、3バイトのデータごとに4つのBase64文字になります。編集:コメントは、以前のグラフィックがパディングを考慮していないことを正しく指摘しています。正しい式は Ceiling(4n/3)
です。
Wikipediaの記事は、ASCII文字Man
列TWFu
がその例でどのようにBase64文字列にエンコードされたかを正確に示しています。入力文字列のサイズは3バイト、つまり24ビットであるため、式は出力が4バイト(または32ビット)の長さになると正しく予測しますTWFu
。このプロセスでは、6ビットのデータごとに64個のBase64文字のいずれかにエンコードされるため、24ビットの入力を6で割ると、4個のBase64文字になります。
エンコーディングのサイズをコメントで尋ね123456
ます。その文字列のすべての文字のサイズが1バイトまたは8ビット(ASCII / UTF8エンコーディングを想定)であることを念頭に置いて、6バイトまたは48ビットのデータをエンコードします。方程式によれば、出力長はであると予想されます(6 bytes / 3 bytes) * 4 characters = 8 characters
。
パッティング123456
のBase64エンコーダには、作成しMTIzNDU2
、我々は期待と同じように、8文字の長され、。
floor((3 * (length - padding)) / 4)
ます。次の要点を確認してください。
浮動小数点演算や丸め誤差などを使用したくないので、一般的にはdoubleを使用したくありません。それらは単に必要ではありません。
このため、天井分割を実行する方法を覚えておくことをお勧めします。ceil(x / y)
ダブルスは次のように書くことができます。(x + y - 1) / y
(負の数は避けますが、オーバーフローに注意してください)。
読みやすさを追求するなら、もちろん次のようにプログラムすることもできます(Javaの例では、Cの場合はもちろんマクロを使用できます)。
public static int ceilDiv(int x, int y) {
return (x + y - 1) / y;
}
public static int paddedBase64(int n) {
int blocks = ceilDiv(n, 3);
return blocks * 4;
}
public static int unpaddedBase64(int n) {
int bits = 8 * n;
return ceilDiv(bits, 6);
}
// test only
public static void main(String[] args) {
for (int n = 0; n < 21; n++) {
System.out.println("Base 64 padded: " + paddedBase64(n));
System.out.println("Base 64 unpadded: " + unpaddedBase64(n));
}
}
パッド入り
一度に3バイト(またはそれ以下)ごとに4文字のブロックが必要であることはわかっています。したがって、式は次のようになります(x = nおよびy = 3の場合):
blocks = (bytes + 3 - 1) / 3
chars = blocks * 4
または組み合わせ:
chars = ((bytes + 3 - 1) / 3) * 4
コンパイラはを最適化します3 - 1
ので、読みやすくするためにこのままにしておきます。
パッドなし
あまり一般的ではないパッドなしのバリアントです。このため、6ビットごとに切り上げられた文字が必要になることを覚えています。
bits = bytes * 8
chars = (bits + 6 - 1) / 6
または組み合わせ:
chars = (bytes * 8 + 6 - 1) / 6
ただし、2で割ることもできます(必要な場合)。
chars = (bytes * 4 + 3 - 1) / 3
コンパイラが最終的な最適化を信頼していない場合(または同僚を混乱させたい場合):
パッド入り
((n + 2) / 3) << 2
パッドなし
((n << 2) | 2) / 3
つまり、2つの論理的な計算方法があり、本当に必要な場合を除き、分岐、ビット演算、モジュロ演算は必要ありません。
ノート:
与えられた答えは、元の質問の要点、つまり長さnバイトの与えられたバイナリ文字列のbase64エンコーディングに適合するために割り当てられる必要があるスペースの量を欠いていると思います。
答えは (floor(n / 3) + 1) * 4 + 1
これには、パディングと終端のnull文字が含まれます。整数演算を行う場合は、floor呼び出しは必要ありません。
埋め込みを含め、base64文字列は、部分的なチャンクを含め、元の文字列の3バイトのチャンクごとに4バイトを必要とします。文字列の最後に追加された1バイトまたは2バイトは、パディングが追加されると、base64文字列の4バイトに変換されます。特別な用途がない限り、パディングを追加するのが最善です。通常は等号です。Cのnull文字用に1バイト追加しました。これがないASCII文字列は少し危険であり、文字列の長さを個別に運ぶ必要があるためです。
エンコードされたBase 64ファイルの元のサイズを文字列(KB)として計算する関数を次に示します。
private Double calcBase64SizeInKBytes(String base64String) {
Double result = -1.0;
if(StringUtils.isNotEmpty(base64String)) {
Integer padding = 0;
if(base64String.endsWith("==")) {
padding = 2;
}
else {
if (base64String.endsWith("=")) padding = 1;
}
result = (Math.ceil(base64String.length() / 4) * 3 ) - padding;
}
return result / 1000;
}
他の誰もが代数式について議論している間、私はむしろBASE64自体を使用して私に伝えたいと思います:
$ echo "Including padding, a base64 string requires four bytes for every three-byte chunk of the original string, including any partial chunks. One or two bytes extra at the end of the string will still get converted to four bytes in the base64 string when padding is added. Unless you have a very specific use, it is best to add the padding, usually an equals character. I added an extra byte for a null character in C, because ASCII strings without this are a little dangerous and you'd need to carry the string length separately."| wc -c
525
$ echo "Including padding, a base64 string requires four bytes for every three-byte chunk of the original string, including any partial chunks. One or two bytes extra at the end of the string will still get converted to four bytes in the base64 string when padding is added. Unless you have a very specific use, it is best to add the padding, usually an equals character. I added an extra byte for a null character in C, because ASCII strings without this are a little dangerous and you'd need to carry the string length separately." | base64 | wc -c
710
したがって、4バイトのbase64文字で表される3バイトの式は正しいようです。
(簡潔であるが完全な派生を与える試みで。)
すべての入力バイトは8ビットなので、n入力バイトの場合、次のようになります。
n ×8入力ビット
6ビットごとが出力バイトなので、次のようになります。
ceil(n ×8/6)= ceil(n ×4/3)出力バイト
これはパディングなしです。
パディングを使用して、4の倍数の出力バイトに切り上げます。
ceil(ceil(n ×4/3)/ 4)×4 = ceil(n ×4/3/4)×4 = ceil(n / 3)×4出力バイト
最初の等価性については、ネストされた分割(Wikipedia)を参照してください。
整数演算を使用すると、ceil(n / m)は(n + m – 1)div mとして計算できるため、次のようになります。
(n * 4 + 2)div 3(パディングなし)
(n + 2)div 3 * 4パディングあり
説明のため:
n with padding (n + 2) div 3 * 4 without padding (n * 4 + 2) div 3
------------------------------------------------------------------------------
0 0 0
1 AA== 4 AA 2
2 AAA= 4 AAA 3
3 AAAA 4 AAAA 4
4 AAAAAA== 8 AAAAAA 6
5 AAAAAAA= 8 AAAAAAA 7
6 AAAAAAAA 8 AAAAAAAA 8
7 AAAAAAAAAA== 12 AAAAAAAAAA 10
8 AAAAAAAAAAA= 12 AAAAAAAAAAA 11
9 AAAAAAAAAAAA 12 AAAAAAAAAAAA 12
10 AAAAAAAAAAAAAA== 16 AAAAAAAAAAAAAA 14
11 AAAAAAAAAAAAAAA= 16 AAAAAAAAAAAAAAA 15
12 AAAAAAAAAAAAAAAA 16 AAAAAAAAAAAAAAAA 16
最後に、MIME Base64エンコーディングの場合、76出力バイトごとに2つの追加バイト(CR LF)が必要であり、終端の改行が必要かどうかに応じて切り上げまたは切り下げられます。
私には、正しい式は次のようになるはずです:
n64 = 4 * (n / 3) + (n % 3 != 0 ? 4 : 0)
Cを話すすべての人は、次の2つのマクロを見てください。
// calculate the size of 'output' buffer required for a 'input' buffer of length x during Base64 encoding operation
#define B64ENCODE_OUT_SAFESIZE(x) ((((x) + 3 - 1)/3) * 4 + 1)
// calculate the size of 'output' buffer required for a 'input' buffer of length x during Base64 decoding operation
#define B64DECODE_OUT_SAFESIZE(x) (((x)*3)/4)
ここから撮影。
JSで@Pedro Silvaソリューションを実現することに関心のある人がいる場合は、この同じソリューションを移植しただけです。
const getBase64Size = (base64) => {
let padding = base64.length
? getBase64Padding(base64)
: 0
return ((Math.ceil(base64.length / 4) * 3 ) - padding) / 1000
}
const getBase64Padding = (base64) => {
return endsWith(base64, '==')
? 2
: 1
}
const endsWith = (str, end) => {
let charsFromEnd = end.length
let extractedEnd = str.slice(-charsFromEnd)
return extractedEnd === end
}