Tất cả các tệp đều là nhị phân về mặt kỹ thuật, nghĩa là chúng được tạo thành từ một loạt các số 0 và 1. Tuy nhiên, khi chúng tôi đọc tệp ban đầu, chúng tôi đọc 8 bit hoặc 8 số 0 và 1 liên tiếp tại một thời điểm. 8 bit được gọi là byte và tệp văn bản lưu trữ một ký tự trong một byte. Vì vậy, nếu tôi có tệp văn bản 30 byte, điều đó có nghĩa là tệp chứa 30 ký tự
Khi chúng ta nói về các tệp nhị phân trong ngữ cảnh của bài giảng này, chúng ta đang nói về các tệp không tuân theo định dạng 1 byte, 1 ký tự. Thay vào đó, chúng tôi có định dạng tệp, chẳng hạn như tệp JPEG hoặc MP3. Chúng có định dạng mà các kỹ sư đã thiết kế. Ví dụ: JPEG phải có chiều rộng, chiều cao và danh sách các màu pixel để hiển thị hình ảnh. Có nhiều thứ khác đi vào tệp JPEG, nhưng tôi nghĩ bạn đã hiểu
Cấu trúc tệp nhị phân
Như tôi đã đề cập ở trên, các tệp nhị phân chỉ là các chuỗi 0 và 1. Không có cấu trúc vốn có. Ví dụ: nếu tôi đổi tên tệp JPEG của mình thành tệp TEXT, Windows sẽ cố mở tệp đó dưới dạng tệp văn bản. Điều này là do Windows chỉ nhìn thấy các số 0 và 1, nhưng phần mở rộng [. jpg] yêu cầu Windows mở nó dưới dạng JPEG. Nếu Windows biết điều này, nó có thể chạy một chương trình được thiết kế đặc biệt để đặt một thứ tự nhất định cho tệp JPEG
Ví dụ tệp nhị phân
Một ví dụ mà tôi thường sử dụng để chỉ ra cách hoạt động của tệp nhị phân là tệp bitmap. Đây là các tệp ảnh, giống như các tệp JPEG, nhưng không có giải nén phức tạp
Nhớ lại rằng một byte là 8 bit, là 8 số 0 và 1 ghép lại với nhau. Nói chung, nhưng không may là không phải lúc nào cũng vậy, các tệp nhị phân đơn vị địa chỉ nhỏ nhất sử dụng là một byte
Sau đây là những gì được gọi là tiêu đề, là cấu trúc được đặt thành 0 và 1 để chúng tôi biết những số 0 và 1 đó có nghĩa là gì. Đây là cấu trúc tiêu đề tệp bitmap
type [16 bits, 2 bytes] size [32 bits, 4 bytes] reserved [32 bits, 4 bytes] offset [32 bits, 4 bytes]
Bạn có thể thấy ở trên rằng tất cả các kích thước không giống nhau. Vì vậy, trong tiêu đề tệp, chúng ta có thể biết đây là loại tệp bitmap nào bằng cách xem 16 số 0 và 1 mà tệp bắt đầu bằng
Đọc tập tin nhị phân
Chúng tôi có thể đọc các tệp nhị phân bằng cách thêm b vào chế độ được cung cấp để mở. Nhớ lại rằng chúng ta có thể mở một tệp, chẳng hạn như
value = int[10].to_bytes[length=4, byteorder='little'] print[type[value]]1. Trong trường hợp này, nó tìm kiếm myfile. txt và mở nó ra để đọc, đó là cái mà “r” dành cho. Khi chúng tôi đọc từ tệp, Python sẽ cung cấp cho chúng tôi các chuỗi vì nó nghĩ đây là tệp văn bản. Nhớ lại rằng một chuỗi chỉ là một dãy các ký tự
Nếu chúng ta muốn mở tệp dưới dạng một chuỗi các số 0 và 1 [nhị phân] thay vì một chuỗi ký tự [văn bản], chúng ta có thể thêm một “b” vào chế độ, viết tắt của nhị phân. Ví dụ,
value = int[10].to_bytes[length=4, byteorder='little'] print[type[value]]2 sẽ mở tệp myfile. bin để đọc dưới dạng tệp nhị phân. Bây giờ chúng ta đã thêm “b”, Python sẽ cung cấp cho chúng ta kiểu dữ liệu byte thay vì chuỗi
Kiểu dữ liệu byte
Vì chúng tôi đang làm việc với byte chứ không phải chuỗi, nên Python đã thêm kiểu dữ liệu byte. Đây không phải là trường hợp trong các phiên bản Python cũ hơn, nhưng giờ đây kiểu dữ liệu này giúp việc đọc và ghi byte từ và đến một tệp dễ dàng hơn nhiều
Byte có thể được tạo bằng cách sử dụng một ký tự, trông giống như một chuỗi, nhưng nó bắt đầu bằng b, chẳng hạn như.
value = int[10].to_bytes[length=4, byteorder='little'] print[type[value]]3. Mặc dù nó trông giống như một chuỗi, nhưng khi chúng ta gán chuỗi này cho một biến, đây sẽ là kiểu dữ liệu byte. Hãy nhớ rằng khi chúng ta cắt một chuỗi, chẳng hạn như
value = int[10].to_bytes[length=4, byteorder='little'] print[type[value]]0 hoặc
value = int[10].to_bytes[length=4, byteorder='little'] print[type[value]]1, chúng ta sẽ nhận được một ký tự hoặc chuỗi ký tự. Giống như thế này, chúng ta nhận được một byte đơn [8 bit] hoặc chuỗi byte
Nhận byte từ số
Các số nhị phân có thể được biểu diễn bằng một chuỗi các bit. Tuy nhiên, độ dài của nó hơi tùy ý. Ví dụ: 00000000023 là 23 cũng như 023 là 23. Tuy nhiên, nếu tôi chỉ có hai chữ số, tôi không thể đại diện cho 123. Vì vậy, tôi nói "tùy ý" bởi vì chúng ta có thể sử dụng nhiều chữ số hơn mức cần thiết, nhưng không ít hơn. Đây là lý do tại sao Python cho phép chúng ta chuyển đổi một số nguyên thành một chuỗi byte, nhưng để làm như vậy, chúng ta cần cung cấp cho nó một số tham số
Khi chúng ta có một đối tượng số nguyên [có nghĩa KHÔNG phải là chữ], chúng ta có thể gọi một hàm thành viên có tên là
value = int[10].to_bytes[length=4, byteorder='little'] print[type[value]]2 để chuyển đổi nó thành một chuỗi byte. Tuy nhiên, hãy nhớ những gì tôi đã nói, chúng ta cần cung cấp cho nó một số tham số, cụ thể là hai. kích thước và tuổi thọ
Kích thước
Hầu hết các chuỗi byte trong máy tính đều có lũy thừa là 2. 1, 2, 4 hoặc 8 byte. Số byte chính xác cho mỗi trường tùy thuộc vào cấu trúc tệp mà bạn cần biết trước khi ghi hoặc đọc từ đó
độ bền
Endianness không khó hiểu đến thế, nhưng nó khác với những gì chúng ta đã nói trước đây. Endianness có nghĩa là phần cuối của số nào sẽ đến trước. Nó có hai hương vị. lớn hay nhỏ, nghĩa là chúng ta lưu đầu nhỏ [các chữ số ngoài cùng bên phải] trước hay đầu lớn [các chữ số ngoài cùng bên trái]. Hình dưới đây cho thấy có hai cách chúng ta có thể lưu trữ một số. lớn hay nhỏ
Ví dụ về đơn đặt hàng byte cuối lớn và nhỏ
Con người đọc bằng big-endian nếu bạn đọc từ trái sang phải. Bạn có thể thấy rằng số chúng tôi đang lưu trữ là 1a_2b_3c_4d_5e_6f_70_80. Đây là số cơ số 16 [thập lục phân]. Tuy nhiên, trong little endian, nó được lưu ngược lại, vì đầu nhỏ [byte ngoài cùng bên phải] được lưu trước
Chuyển đổi số nguyên thành byte
Một lần nữa, chúng ta có thể sử dụng to_bytes để tạo một chuỗi byte. Ví dụ
value = int[10].to_bytes[length=4, byteorder='little'] print[type[value]]
Trong đoạn mã trên, chúng tôi bắt buộc phải sử dụng int[10] vì hàm to_bytes hoạt động trên một đối tượng số nguyên chứ không phải số nguyên. Cũng giống như mọi thứ khác trong Python, có vô số cách để thực hiện thao tác này, tuy nhiên, tôi thấy cách này là dễ nhất
Có hai tham số bắt buộc đối với to_bytes, độ dài, là số byte và thứ tự byte, xác định xem phần này sẽ được lưu trữ đầu nhỏ hay đầu lớn trước. Tham số byteorder lấy một chuỗi là 'lớn' hoặc 'nhỏ'
Khi chúng ta in kiểu biến mà chúng ta lấy lại, nó sẽ in như sau
Vì vậy, chúng ta có một lớp byte một cách hợp pháp, chứa hàm to_bytes[] của chúng ta. Chúng tôi có thể ghi vào một tệp bằng cách sử dụng cùng một thao tác ghi, ngoại trừ bây giờ chúng tôi chuyển nó bytes[]. Ví dụ,
i = 100 j = 200 k = 0xdeadbeef i_bytes = i.to_bytes[length=1, byteorder='little'] j_bytes = j.to_bytes[length=2, byteorder='little'] k_bytes = k.to_bytes[length=4, byteorder='little'] f = open["myfile.bin", "wb"] f.write[i_bytes] f.write[j_bytes] f.write[k_bytes] f.close[]
Lưu ý rằng khi tôi viết, số byte để ghi vào tệp đến từ biến XX_bytes chứ không phải chính hàm ghi. Trong trường hợp này, chúng ta có thể chuyển đổi số nguyên thành byte bằng cách sử dụng to_bytes. Chúng tôi có thể chỉ định bao nhiêu byte chúng tôi muốn bằng cách chỉ định độ dài
Nếu bạn cố gắng chuyển đổi thành_bytes bằng cách sử dụng độ dài mà số nguyên không vừa, Python sẽ đưa ra lỗi OverflowError như bạn có thể thấy bên dưới
>>> int[2000].to_bytes[length=1, byteorder='little'] Traceback [most recent call last]: File "", line 1, in OverflowError: int too big to convert
Khi tôi chỉ định độ dài = 1, tôi đang nói với to_bytes rằng tôi muốn số nguyên vừa với một byte. Tuy nhiên, một byte [8 bit] sẽ đạt tối đa \[2^{8}-1=255\]. Điều này không chính xác 100% trong máy tính, nhưng tôi không muốn làm phức tạp mọi thứ. Khi chúng tôi lưu trữ các số âm, chúng tôi sử dụng một chút để lưu trữ dấu hiệu, vì vậy nó không hoàn toàn như thế này. Tuy nhiên, tôi chỉ đưa ra ví dụ này để cho thấy rằng có vấn đề khi chúng tôi lưu trữ các số lớn với độ dài nhỏ hơn
Từ byte
Phần trước đã hướng dẫn cách chuyển đổi từ kiểu số nguyên sang kiểu dữ liệu byte. Tuy nhiên, điều này thực sự chỉ hữu ích khi chúng ta viết byte. Điều gì xảy ra khi chúng ta muốn đọc byte từ tệp nhị phân?
f = open["myfile.bin", "rb"] four_bytes = f.read[4] two_bytes = f.read[2] one_byte = f.read[1] f.close[] print["Four bytes is:", int.from_bytes[four_bytes, byteorder='little']] print["Two bytes is:", int.from_bytes[two_bytes, byteorder='little']] print["One byte is:", int.from_bytes[one_byte, byteorder='little']]
Bạn có thể thấy ở trên, chúng ta phải chỉ định byteorder một lần nữa khi chuyển đổi từ byte thành số nguyên. Chúng tôi không chỉ định độ dài vì chúng tôi biết độ dài từ chính đối tượng byte
Mảng byte
Một đối tượng bytes là bất biến, có nghĩa là chúng ta không thể sửa đổi nó tại chỗ, giống như một bộ dữ liệu. Chúng ta có thể tạo một đối tượng bytes mới để thực hiện một số phép biến đổi, nhưng nếu chúng ta chỉ muốn thay đổi một phần của đối tượng bytes thì sao? . Các đối tượng này rất giống với các đối tượng byte, tuy nhiên chúng có thể được thay đổi tại chỗ, giống như một danh sách
Chúng ta có thể thay đổi đối tượng bytes thành đối tượng bytearray bằng cách sử dụng bytearray[bytes_object] và ngược lại
value = int[10].to_bytes[length=4, byteorder='little'] print[type[value]]0
Đoạn mã trên lấy một đối tượng bytes, sao chép nó vào một mảng byte, thay đổi 's' thành 'z' và sao chép lại. Lưu ý rằng tôi phải sử dụng ord[“z”], đây là một hàm đặc biệt cung cấp cho tôi biểu diễn số nguyên của chữ thường z
GHI CHÚ. Đọc và ghi vào tệp nhị phân sử dụng đối tượng byte bất biến. Tuy nhiên, như bạn có thể thấy ở trên, bạn có thể chuyển đổi sang và từ