Après avoir lu le base64 wiki ...
J'essaie de comprendre comment la formule fonctionne:
Avec une chaîne de longueur n
, la longueur en base64 sera
Quel est: 4*Math.Ceiling(((double)s.Length/3)))
Je sais déjà que la longueur en base64 doit être %4==0
pour permettre au décodeur de savoir quelle était la longueur du texte d'origine.
Le nombre maximum de remplissage pour une séquence peut être =
ou ==
.
wiki: le nombre d'octets de sortie par octet d'entrée est d'environ 4/3 (33% surcharge.)
Question:
Comment les informations ci-dessus s'accordent-elles avec la longueur de sortie ?
Chaque caractère est utilisé pour représenter 6 bits (log2(64) = 6
).
Par conséquent, 4 caractères sont utilisés pour représenter 4 * 6 = 24 bits = 3 bytes
.
Donc, vous avez besoin de 4*(n/3)
caractères pour représenter n
octets, qui doit être arrondi à un multiple de 4.
Le nombre de caractères de remplissage non utilisés résultant de l'arrondissement d'un multiple de 4 sera évidemment égal à 0, 1, 2 ou 3.
4 * n / 3
donne une longueur non additionnée.
Et arrondissez au multiple de 4 le plus proche pour le remplissage, et comme 4 est une puissance de 2 peut utiliser des opérations logiques au niveau des bits.
((4 * n / 3) + 3) & ~3
Pour référence, la formule de longueur du codeur Base64 est la suivante:
Comme vous l'avez dit, un encodeur Base64 à l'aide de n
octets de données générera une chaîne de caractères 4n/3
Base64. En d'autres termes, tous les 3 octets de données génèrent 4 caractères Base64. EDIT: Un commentaire souligne correctement que mon graphique précédent ne prenait pas en compte le remplissage; la formule correcte étaitCeiling(4n/3)
.
L'article de Wikipedia montre exactement comment la chaîne ASCII Man
est codée dans la chaîne Base64 TWFu
dans son exemple. La chaîne d'entrée ayant une taille de 3 octets ou 24 bits, la formule prédit correctement que la sortie aura une longueur de 4 octets (ou 32 bits): TWFu
. Le processus code tous les 6 bits de données en un des 64 caractères Base64. L'entrée 24 bits divisée par 6 génère donc 4 caractères Base64.
Vous demandez dans un commentaire quelle serait la taille de l'encodage 123456
. En gardant à l'esprit que chaque caractère de cette chaîne a une taille de 1 octet ou 8 bits (en supposant un codage ASCII/UTF8), nous codons 6 octets ou 48 bits de données. Selon l'équation, nous prévoyons que la longueur de sortie sera (6 bytes / 3 bytes) * 4 characters = 8 characters
.
Mettre 123456
dans un encodeur Base64 crée MTIzNDU2
, qui est composé de 8 caractères, exactement comme nous le pensions.
En règle générale, nous ne voulons pas utiliser les doublons parce que nous ne voulons pas utiliser les ops à virgule flottante, les erreurs d'arrondi, etc. Ils ne sont tout simplement pas nécessaires.
Pour cela, il est bon de rappeler comment effectuer la division de plafond: ceil(x / y)
en double peut être écrit en (x + y - 1) / y
(tout en évitant les nombres négatifs, mais méfiez-vous des débordements).
Si vous optez pour la lisibilité, vous pouvez bien sûr aussi le programmer comme ceci (par exemple en Java, pour le C, vous pouvez utiliser des macros, bien sûr):
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));
}
}
Rembourré
Nous savons que nous avons besoin de 4 blocs de caractères à la fois pour chaque groupe de 3 octets (ou moins). La formule devient alors (pour x = n et y = 3):
blocks = (bytes + 3 - 1) / 3
chars = blocks * 4
ou combiné:
chars = ((bytes + 3 - 1) / 3) * 4
votre compilateur optimisera le 3 - 1
, donc laissez-le comme ceci pour maintenir la lisibilité.
Non rembourré
La variante non-rembourrée est moins courante. Pour cela, nous rappelons que nous avons besoin d’un caractère pour chaque 6 bits, arrondi à la hausse:
bits = bytes * 8
chars = (bits + 6 - 1) / 6
ou combiné:
chars = (bytes * 8 + 6 - 1) / 6
on peut cependant quand même diviser par deux (si on veut):
chars = (bytes * 4 + 3 - 1) / 3
Si vous ne faites pas confiance à votre compilateur pour effectuer les optimisations finales à votre place (ou si vous voulez confondre vos collègues):
Rembourré
((n + 2) / 3) << 2
Non rembourré
((n << 2) | 2) / 3
Voilà donc deux méthodes logiques de calcul, et nous n’avons pas besoin de branches, de bit-op ou de modulo, à moins que nous ne le voulions vraiment.
Remarques:
Je pense que les réponses données manquent le point de la question initiale, à savoir combien d’espace doit être alloué pour s’adapter au codage base64 pour une chaîne binaire donnée de longueur n octets.
La réponse est (floor(n / 3) + 1) * 4 + 1
Cela inclut un remplissage et un caractère nul final. Vous pouvez ne pas avoir besoin de l'appel au sol si vous utilisez l'arithmétique des nombres entiers.
Y compris le remplissage, une chaîne base64 nécessite quatre octets pour chaque bloc de trois octets de la chaîne d'origine, y compris tous les fragments partiels. Un ou deux octets supplémentaires à la fin de la chaîne seront toujours convertis en quatre octets dans la chaîne base64 lorsque le remplissage est ajouté. À moins que vous n'ayez une utilisation très spécifique, il est préférable d'ajouter le remplissage, généralement un caractère égal à. J'ai ajouté un octet supplémentaire pour un caractère nul en C, parce que les chaînes ASCII sans cela sont un peu dangereuses et que vous auriez besoin de transporter la longueur de la chaîne séparément.
Voici une fonction permettant de calculer la taille d'origine d'un fichier encodé en Base 64 sous forme de chaîne en Ko:
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;
}
Il me semble que la bonne formule devrait être:
n64 = 4 * (n / 3) + (n % 3 != 0 ? 4 : 0)
Je crois que celui-ci est une réponse exacte si n% 3 n'est pas nul, non?
(n + 3-n%3)
4 * ---------
3
Version de Mathematica:
SizeB64[n_] := If[Mod[n, 3] == 0, 4 n/3, 4 (n + 3 - Mod[n, 3])/3]
S'amuser
GI
Pendant que tout le monde débat des formules algébriques, je préfère utiliser BASE64 lui-même pour me dire:
$ 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
Il semble donc que la formule de 3 octets représentée par 4 caractères base64 semble correcte.
S'il y a quelqu'un qui est intéressé par la solution @Pedro Silva dans JS, je viens de porter la même solution pour elle:
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
}
Simple implémentation en javascript
function sizeOfBase64String(base64String) {
if (!base64String) return 0;
const padding = (base64String.match(/(=*)$/) || [])[1].length;
return 4 * Math.ceil((base64String.length / 3)) - padding;
}
Dans Windows - je voulais estimer la taille du tampon mime64, mais toutes les formules de calcul précises ne fonctionnaient pas pour moi - j'ai finalement obtenu une formule approximative comme celle-ci:
Mine64 taille d'allocation de chaînes (approximative) = (((4 * ((taille du tampon binaire) + 1))/3) + 1)
Donc dernier +1 - il est utilisé pour ascii-zero - le dernier caractère doit être alloué à la fin du stockage zéro - mais pourquoi "taille du tampon binaire" vaut + 1 - je soupçonne qu'il existe un caractère de terminaison mime64? Ou peut-être est-ce un problème d'alignement.