Tính chiều dài Base64?


155

Sau khi đọc wiki cơ sở64 ...

Tôi đang cố gắng tìm hiểu làm thế nào công thức làm việc:

Cho một chuỗi có độ dài bằng n, độ dài cơ sở64 sẽ lànhập mô tả hình ảnh ở đây

Đó là: 4*Math.Ceiling(((double)s.Length/3)))

Tôi đã biết rằng độ dài cơ sở64 phải %4==0cho phép bộ giải mã biết độ dài văn bản gốc là gì.

Số lượng đệm tối đa cho một chuỗi có thể là =hoặc ==.

wiki: Số byte đầu ra trên mỗi byte đầu vào xấp xỉ 4/3 (33% phí)

Câu hỏi:

Làm thế nào để thông tin trên giải quyết với chiều dài đầu ra nhập mô tả hình ảnh ở đây?

Câu trả lời:


210

Mỗi ký tự được sử dụng để biểu diễn 6 bit ( log2(64) = 6).

Do đó, 4 ký tự được sử dụng để đại diện 4 * 6 = 24 bits = 3 bytes.

Vì vậy, bạn cần 4*(n/3)ký tự đại diện cho nbyte và điều này cần được làm tròn thành bội số của 4.

Số lượng ký tự đệm không được sử dụng dẫn đến làm tròn lên bội số của 4 rõ ràng sẽ là 0, 1, 2 hoặc 3.


đệm ở đây là ở đâu?
Royi Namir

1
Xem xét nếu bạn có một byte đầu vào. Điều đó sẽ tạo ra bốn ký tự đầu ra. Nhưng chỉ cần hai ký tự đầu ra để mã hóa đầu vào. Vì vậy, hai nhân vật sẽ được đệm.
David Schwartz

2
Độ dài đầu ra luôn được làm tròn lên bội số của 4, vì vậy 1, 2 hoặc 3 byte đầu vào => 4 ký tự; 4, 5 hoặc 6 byte đầu vào => 8 ký tự; 7, 8 hoặc 9 byte đầu vào => 12 ký tự.
Paul R

5
Tôi đã giải thích tất cả điều này trong câu trả lời ở trên: (i) mỗi char đầu ra đại diện cho 6 bit đầu vào, (ii) 4 ký tự đầu ra do đó đại diện cho 4 * 6 = 24 bit , (iii) 24 bit là 3 byte , (iv) 3 byte do đó, đầu vào dẫn đến 4 ký tự đầu ra, (v) tỷ lệ ký tự đầu ra cho các byte đầu vào là 4 / 3.
Paul R

2
@ techie_28: Tôi tạo ra 27.30 ký tự cho 20 * 1024 byte, nhưng tôi chưa uống cà phê sáng nay.
Paul R

60

4 * n / 3 cho chiều dài không đệm.

Và làm tròn đến bội số gần nhất của 4 để đệm, và vì 4 là lũy thừa 2 có thể sử dụng các phép toán logic bitwise.

((4 * n / 3) + 3) & ~3

1
Bạn đúng rồi! -> 4 * n / 3 cho chiều dài không được đệm! Câu trả lời trên không đúng. -> ((4 * n / 3) + 3) & ~ 3 trả về kết quả đúng
Cadburry

Không hoạt động như một đầu vào cho API CryptBinaryToStringA của cửa sổ.
TarmoPikaro

để đánh vần nó cho những người sử dụng shell:$(( ((4 * n / 3) + 3) & ~3 ))
starfry

1
4 * n / 3đã thất bại tại n = 1, một byte được mã hóa bằng hai ký tự và kết quả rõ ràng là một ký tự.
Maarten Bodewes

1
@Crog Vì nó được viết ra nếu n = 1 thì bạn sẽ nhận được 4/3 = 1 bằng cách sử dụng các số nguyên. Như bạn đã chỉ ra, kết quả dự kiến ​​là 2, không phải 1.
Maarten Bodewes

25

Để tham khảo, công thức độ dài của bộ mã hóa Base64 như sau:

Công thức độ dài của bộ mã hóa Base64

Như bạn đã nói, một bộ mã hóa Base64 được cung cấp nbyte dữ liệu sẽ tạo ra một chuỗi các 4n/3ký tự Base64. Nói cách khác, cứ 3 byte dữ liệu sẽ tạo ra 4 ký tự Base64. EDIT : Một nhận xét chỉ ra một cách chính xác rằng đồ họa trước đây của tôi không tính đến phần đệm; công thức đúng là Ceiling(4n/3) .

Bài viết Wikipedia cho thấy chính xác cách chuỗi ASCII Man được mã hóa thành chuỗi Base64 TWFutrong ví dụ của nó. Chuỗi đầu vào có kích thước 3 byte hoặc 24 bit, do đó, công thức dự đoán chính xác đầu ra sẽ dài 4 byte (hoặc 32 bit) : TWFu. Quá trình mã hóa mỗi 6 bit dữ liệu thành một trong 64 ký tự Base64, do đó, đầu vào 24 bit chia cho 6 kết quả trong 4 ký tự Base64.

Bạn hỏi trong một bình luận kích thước của mã hóa 123456sẽ là gì. Hãy nhớ rằng mỗi ký tự của chuỗi đó có kích thước 1 byte hoặc 8 bit (giả sử mã hóa ASCII / UTF8), chúng tôi đang mã hóa 6 byte hoặc 48 bit dữ liệu. Theo phương trình, chúng tôi hy vọng chiều dài đầu ra là (6 bytes / 3 bytes) * 4 characters = 8 characters.

Đưa 123456vào bộ mã hóa Base64 MTIzNDU2, dài 8 ký tự, như chúng ta mong đợi.


5
Sử dụng công thức này, lưu ý rằng nó không cho chiều dài đệm. Vì vậy, bạn có thể có một chiều dài dài hơn.
Spilarix

Để tính toán các byte được giải mã dự kiến ​​từ văn bản base64, tôi sử dụng công thức floor((3 * (length - padding)) / 4). Kiểm tra các ý chính sau đây .
Kurt Vangraefschepe

13

Số nguyên

Nói chung, chúng tôi không muốn sử dụng gấp đôi vì chúng tôi không muốn sử dụng ops dấu phẩy động, lỗi làm tròn, v.v ... Chúng không cần thiết.

Đối với điều này, bạn nên nhớ cách thực hiện phép chia trần: ceil(x / y)trong hai lần có thể được viết là (x + y - 1) / y(trong khi tránh các số âm, nhưng hãy cẩn thận khi tràn).

Có thể đọc được

Nếu bạn muốn đọc, tất nhiên bạn cũng có thể lập trình như thế này (ví dụ trong Java, đối với C, bạn có thể sử dụng macro, tất nhiên):

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));
    }
}

Nội tuyến

Đệm

Chúng tôi biết rằng chúng tôi cần 4 khối ký tự tại mỗi thời điểm cho mỗi 3 byte (hoặc ít hơn). Vì vậy, công thức trở thành (cho x = n và y = 3):

blocks = (bytes + 3 - 1) / 3
chars = blocks * 4

hoặc kết hợp:

chars = ((bytes + 3 - 1) / 3) * 4

trình biên dịch của bạn sẽ tối ưu hóa 3 - 1, vì vậy hãy để nó như thế này để duy trì khả năng đọc.

Không đệm

Ít phổ biến hơn là biến thể không được đệm, vì điều này chúng ta nhớ rằng mỗi chúng ta cần một ký tự cho mỗi 6 bit, được làm tròn lên:

bits = bytes * 8
chars = (bits + 6 - 1) / 6

hoặc kết hợp:

chars = (bytes * 8 + 6 - 1) / 6

tuy nhiên chúng ta vẫn có thể chia cho hai (nếu chúng ta muốn):

chars = (bytes * 4 + 3 - 1) / 3

Không thể đọc được

Trong trường hợp bạn không tin tưởng trình biên dịch của mình thực hiện các tối ưu hóa cuối cùng cho bạn (hoặc nếu bạn muốn gây nhầm lẫn cho đồng nghiệp của mình):

Đệm

((n + 2) / 3) << 2

Không đệm

((n << 2) | 2) / 3

Vì vậy, chúng tôi có hai cách tính toán hợp lý và chúng tôi không cần bất kỳ nhánh, bit-op hoặc modulo ops - trừ khi chúng tôi thực sự muốn.

Ghi chú:

  • Rõ ràng bạn có thể cần thêm 1 vào các tính toán để bao gồm một byte kết thúc null.
  • Đối với Mime, bạn có thể cần phải quan tâm đến các ký tự kết thúc dòng có thể và như vậy (tìm kiếm các câu trả lời khác cho điều đó).

5

Tôi nghĩ rằng các câu trả lời đã bỏ lỡ điểm của câu hỏi ban đầu, đó là cần phân bổ bao nhiêu dung lượng để phù hợp với mã hóa base64 cho một chuỗi nhị phân có độ dài n byte cho trước.

Câu trả lời là (floor(n / 3) + 1) * 4 + 1

Điều này bao gồm phần đệm và ký tự null kết thúc. Bạn có thể không cần cuộc gọi sàn nếu bạn đang thực hiện số học số nguyên.

Bao gồm phần đệm, một chuỗi base64 yêu cầu bốn byte cho mỗi đoạn ba byte của chuỗi gốc, bao gồm bất kỳ đoạn nào. Thêm một hoặc hai byte ở cuối chuỗi vẫn sẽ được chuyển đổi thành bốn byte trong chuỗi base64 khi thêm phần đệm. Trừ khi bạn có một cách sử dụng rất cụ thể, tốt nhất là thêm phần đệm, thường là một ký tự bằng. Tôi đã thêm một byte bổ sung cho một ký tự null trong C, bởi vì các chuỗi ASCII không có điều này hơi nguy hiểm và bạn cần phải mang riêng độ dài chuỗi.


5
Công thức của bạn sai. Hãy xem xét n = 3, kết quả mong đợi (không có phần đệm null) là 4, nhưng công thức của bạn trả về 8.
CodeInChaos 23/03 '

5
Tôi cũng nghĩ rằng bao gồm cả terminator null là ngớ ngẩn, đặc biệt là khi chúng ta đang nói về .net ở đây.
CodeInChaos

Hoạt động chính xác trong windows, sử dụng CryptBinaryToStringA. Phiếu bầu của tôi cho điều này.
TarmoPikaro

5

Đây là một hàm để tính kích thước ban đầu của tệp Base 64 được mã hóa dưới dạng Chuỗi trong 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;
}

3

Trong khi mọi người khác đang tranh luận về các công thức đại số, tôi chỉ muốn sử dụng chính BASE64 để nói với tôi:

$ 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

Vì vậy, có vẻ như công thức của 3 byte được biểu thị bằng 4 ký tự base64 có vẻ đúng.


1
Tôi đã có một cái gì đó chống lại các tính toán đòi hỏi nhiều bộ nhớ và thời gian CPU trong khi các tính toán có thể được thực hiện trong 1 ns và một hoặc hai thanh ghi.
Maarten Bodewes

Vì vậy, khi bạn đang cố gắng xử lý lượng dữ liệu nhị phân không xác định - điều này giúp ích như thế nào?
UKMonkey

Câu hỏi là tất cả về công thức, giúp tính toán kích thước đầu ra mà không cần thực hiện cơ sở64. Mặc dù câu trả lời này hữu ích trong một số tình huống, nhưng nó không giúp ích gì cho câu hỏi này.
Alejandro

3

(Trong một nỗ lực để đưa ra một dẫn xuất ngắn gọn nhưng đầy đủ.)

Mỗi byte đầu vào có 8 bit, vì vậy với n byte đầu vào, chúng ta nhận được:

bit đầu vào n × 8

Cứ 6 bit là một byte đầu ra, vì vậy:

trần nhà ( n × 8/6) =  ceil ( n × 4/3) byte đầu ra

Đây là không có đệm.

Với phần đệm, chúng ta làm tròn số đó lên đến nhiều byte đầu ra:

trần ( trần ( n × 4/3) / 4) × 4 =  trần ( n × ) × 4 =  trần ( n / 3) × 4 byte đầu ra

Xem Phân chia lồng nhau (Wikipedia) để biết sự tương đương đầu tiên.

Sử dụng số nguyên học, trần ( n / m ) có thể được tính là ( n + m - 1) div m , do đó chúng ta có được:

( n * 4 + 2) div 3 không có phần đệm

( n + 2) div 3 * 4 với phần đệm

Để minh họa:

 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

Cuối cùng, trong trường hợp mã hóa MIME Base64, cần có hai byte bổ sung (CR LF) cho mỗi 76 byte đầu ra, được làm tròn lên hoặc xuống tùy thuộc vào việc có cần một dòng mới kết thúc hay không.


Cảm ơn đã phân tích chi tiết
P Satish Patro

2

Dường như với tôi rằng công thức đúng nên là:

n64 = 4 * (n / 3) + (n % 3 != 0 ? 4 : 0)

Không điền vào tài khoản không được đưa vào tài khoản - không hoạt động trong Windows. (CryptBinaryToStringA)
TarmoPikaro

1

Tôi tin rằng đây là một câu trả lời chính xác nếu n% 3 không bằng 0, không?

    (n + 3-n%3)
4 * ---------
       3

Phiên bản toán học:

SizeB64[n_] := If[Mod[n, 3] == 0, 4 n/3, 4 (n + 3 - Mod[n, 3])/3]

Chúc vui vẻ

GI


1

Thực hiện đơn giản trong javascript

function sizeOfBase64String(base64String) {
    if (!base64String) return 0;
    const padding = (base64String.match(/(=*)$/) || [])[1].length;
    return 4 * Math.ceil((base64String.length / 3)) - padding;
}

1

Đối với tất cả những người nói tiếng C, hãy xem hai macro này:

// 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) 

Lấy từ đây .


1

Tôi không thấy công thức đơn giản hóa trong các phản ứng khác. Logic được trình bày nhưng tôi muốn một hình thức cơ bản nhất cho việc sử dụng nhúng của mình:

  Unpadded = ((4 * n) + 2) / 3

  Padded = 4 * ((n + 2) / 3)

LƯU Ý: Khi tính toán số lượng không được đệm, chúng tôi làm tròn phép chia số nguyên, tức là thêm Divisor-1 là +2 trong trường hợp này


0

Trong các cửa sổ - Tôi muốn ước tính kích thước của bộ đệm có kích thước mime64, nhưng tất cả các công thức tính toán chính xác đều không hiệu quả với tôi - cuối cùng tôi đã kết thúc với công thức gần đúng như thế này:

Kích thước phân bổ chuỗi Mine64 (gần đúng) = (((4 * ((kích thước bộ đệm nhị phân) + 1)) / 3) + 1)

Vì vậy, +1 cuối cùng - nó được sử dụng cho ascii-zero - ký tự cuối cùng cần được phân bổ để lưu trữ kết thúc bằng 0 - nhưng tại sao "kích thước bộ đệm nhị phân" là + 1 - Tôi nghi ngờ rằng có một số ký tự kết thúc mime64? Hoặc có thể đây là một số vấn đề liên kết.


0

Nếu có ai đó quan tâm đến việc đạt được giải pháp @Pedro Silva trong JS, tôi chỉ chuyển giải pháp tương tự cho nó:

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
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.