2008-11-27 7 views
34

Khi thiết kế định dạng tệp để ghi dữ liệu nhị phân, bạn sẽ nghĩ định dạng nào sẽ có thuộc tính nào? Cho đến nay, tôi đã đưa ra một số điểm quan trọng sau:Điểm quan trọng khi thiết kế định dạng tệp (nhị phân) là gì?

  • có một số "byte ma thuật" để bắt đầu nhận dạng tệp (trong trường hợp cụ thể của tôi, điều này cũng giúp phân biệt các tệp từ tệp "cũ")
  • có số phiên bản tệp ở đầu để định dạng tệp có thể bị thay đổi sau này mà không vi phạm tính tương thích
  • xác định độ dài và kích thước của tất cả các mục dữ liệu; hoặc: bao gồm một số không gian để mô tả độ dài/kích thước của dữ liệu (tôi có xu hướng hướng về trước)
  • có thể đặt trước một số khoảng trống cho các thuộc tính cho mỗi tệp có thể cần thiết trong tương lai?

Điều gì khác sẽ hữu ích để làm cho định dạng có tính minh bạch hơn trong tương lai và giảm thiểu nhức đầu trong tương lai?

Trả lời

22

Hãy xem qua số PNG spec. Định dạng này có một số lý do rất tốt đằng sau nó.

Ngoài ra, hãy quyết định điều gì quan trọng đối với định dạng tương lai của bạn: độ gọn nhẹ, khả năng tương thích, cho phép nhúng các định dạng khác (các thuật toán nén khác nhau) bên trong nó. Một ví dụ thú vị khác là Google's protocol buffers, nơi mà kích thước của dữ liệu được chuyển là vua.

Đối với tính cuối cùng, tôi khuyên bạn nên chọn một tùy chọn và gắn bó với nó, không cho phép các đơn hàng byte khác nhau. Nếu không, các thư viện đọc và viết sẽ chỉ phức tạp hơn và chậm hơn.

+0

PNG là một ví dụ. Những người khác có cấu trúc tương tự là IFF (Định dạng tệp trao đổi, chủ yếu được sử dụng trên Commodore Amiga) và RIFF (ví dụ: WAV hoặc AVI). – BlaM

3

Tôi sẽ xem xét xác định cấu trúc con mà cấp cao hơn sử dụng để lưu trữ dữ liệu, giống như một hệ thống tệp nhỏ bên trong tệp.

Ví dụ: mặc dù định dạng tệp của bạn sẽ lưu trữ dữ liệu ứng dụng cụ thể, tôi sẽ xem xét xác định hồ sơ/luồng v.v. bên trong tệp theo cách mã ứng dụng bất khả tri có thể hiểu bố cục của tập tin, nhưng không phải tất nhiên hiểu được tải trọng mờ đục.

Hãy chuẩn bị kỹ hơn một chút. Hãy xem xét các cách lưu trữ dữ liệu thông thường trong bộ nhớ: thông thường chúng có thể được luộc xuống các mảng/danh sách có thể mở rộng liền kề, biểu đồ con trỏ/tham chiếu và nhị phân dữ liệu ở định dạng cụ thể.

Do đó, có thể có hiệu quả để xác định định dạng tệp nhị phân dọc theo các dòng tương tự. Sử dụng tiêu đề bản ghi cho biết độ dài và thành phần của dữ liệu sau, cho dù đó là dạng mảng (danh sách các bản ghi được nhập chính xác), tham chiếu (bù đắp cho các bản ghi khác trong tệp), hoặc blobs dữ liệu (ví dụ: dữ liệu chuỗi trong một mã hóa cụ thể, nhưng không chứa bất kỳ tham chiếu nào).

Nếu được thiết kế cẩn thận, điều này có thể cho phép định dạng tệp không chỉ để duy trì dữ liệu trong và ngoài tất cả trong một lần mà còn trên cơ sở gia tăng khi cần thiết. Nếu cấu trúc con được thiết kế đúng cách, nó có thể là ứng dụng thuyết bất khả tri nhưng vẫn cho phép ví dụ: một ứng dụng thu gom rác được viết, hiểu các đốm màu, mảng và các loại bản ghi tham chiếu, và có thể theo dõi qua tệp và loại bỏ các bản ghi không sử dụng (tức là các bản ghi không còn được trỏ tới).

Đó chỉ là một ý tưởng. Những nơi khác để tìm kiếm ý tưởng nằm trong các thiết kế hệ thống tệp chung, hoặc các chiến lược lưu trữ vật lý cơ sở dữ liệu quan hệ.

Tất nhiên, tùy thuộc vào yêu cầu của bạn, điều này có thể là quá mức cần thiết. Bạn có thể chỉ đơn giản là sau một định dạng nhị phân để lưu trữ dữ liệu trong bộ nhớ, trong trường hợp đó, cách tiếp cận để xem xét là các bản ghi được gắn thẻ.

Trong phương pháp này, mọi phần dữ liệu được bắt đầu bằng thẻ. Thẻ cho biết loại dữ liệu sau ngay lập tức và có thể là chiều dài và tên của nó. Danh sách có thể được thêm vào thẻ "danh sách cuối" không có trọng tải. Thẻ có thể có số nhận dạng được nhúng, do đó các thẻ không được hiểu có thể bị bỏ qua bởi cơ chế tuần tự hóa khi đọc. Thứ này giống như XML theo khía cạnh này, ngoại trừ việc sử dụng thành ngữ nhị phân thay thế.

Thực ra, XML là một nơi tốt để tìm kiếm tuổi thọ lâu dài của định dạng tệp. Nhìn vào khả năng không gian tên của nó. Nếu bạn xây dựng mã đọc và viết một cách cẩn thận, có thể viết các ứng dụng lưu giữ vị trí và nội dung của dữ liệu được gắn thẻ (đệ quy) mà chúng không hiểu, có thể do nó được viết bởi phiên bản sau của cùng một ứng dụng.

2

Một cách để chứng minh trong tương lai tệp sẽ là cung cấp cho các khối. Ngay sau khi dữ liệu tiêu đề tệp của bạn, bạn có thể bắt đầu khối đầu tiên. Khối có thể có một byte hoặc mã từ cho loại khối, sau đó một kích thước tính theo byte. Bây giờ bạn có thể tùy ý thêm các kiểu khối mới và bạn có thể bỏ qua đến cuối khối.

11

Tất cả phụ thuộc vào mục đích của định dạng, tất nhiên.

Một cách tiếp cận linh hoạt là cấu trúc toàn bộ tệp dưới dạng cặp ba lần (Giá trị độ dài thẻ). Ví dụ, làm cho tập tin của bạn comprized hồ sơ, mỗi bản ghi bắt đầu với một tiêu đề 4-byte:

1 byte = record type 
3 bytes = record length 
followed by record content 

Về endianness, nếu bạn lưu trữ chỉ endianness trong file, tất cả các ứng dụng của bạn sẽ phải hỗ trợ tất cả endianness định dạng. Mặt khác, nếu bạn chỉ định một endianness cụ thể cho các tập tin của bạn, chỉ các ứng dụng trên nền tảng với endiannes không phù hợp sẽ phải làm thêm công việc, và nó có thể được quyết định tại thời gian biên dịch (bằng cách sử dụng biên dịch có điều kiện).

+0

TLV dường như ở khắp mọi nơi, rất nhiều nơi ở khắp mọi nơi mà tôi không thấy nó ở mọi nơi. – Cheery

2

Tôi đồng ý với đề xuất của atzz về việc sử dụng hệ thống Giá trị độ dài thẻ. Để tương thích trong tương lai, bạn có thể lưu trữ một bộ "con trỏ" vào mục TLV khi bắt đầu (hoặc có thể là Thẻ, Con trỏ và có con trỏ trỏ tới Độ dài, Giá trị; hoặc có lẽ Thẻ, Độ dài, Con trỏ và sau đó có tất cả dữ liệu cùng nhau ở đâu?).

Vì vậy, tập tin của tôi có thể trông giống như:

magic number/file id 
version 
tag for first data entry 
pointer to first data entry --------+ 
tag for second data entry   | 
pointer to second data entry  | 
...         | 
length of first data entry <--------+ 
value for first data entry 
... 

ma thuật số, phiên bản, thẻ, con trỏ và độ dài tất cả sẽ là một chiều dài bộ được xác định trước, để dễ dàng giải mã. Nói, 2 byte. Hoặc 4, tùy thuộc vào những gì bạn cần. Họ không cần phải giống nhau (ví dụ, tất cả các thẻ là 1 byte, con trỏ là 4 vv).

Thẻ cho bạn biết nội dung đang được lưu trữ. Con trỏ cho bạn biết nơi (hoặc một giá trị bù đắp hoặc tuyệt đối, tính bằng byte), các dài cho bạn biết cách lớn dữ liệu được, và giá trịdài byte dữ liệu kiểu thẻ. Nếu bạn sử dụng bộ giải mã MyFileFormat v1 trên tệp MyFileFormat v2, con trỏ cho phép bạn bỏ qua các phần mà trình giải mã v1 không hiểu. Nếu bạn chỉ đơn giản là bỏ qua các thẻ không hợp lệ, bạn có thể chỉ đơn giản là sử dụng TLV thay vì TPLV.

tôi sẽ hoặc là tay mã một cái gì đó như thế, hoặc có thể xác định định dạng của tôi trong ASN.1 và tạo ra một codec (Tôi làm việc trong ngành viễn thông, vì vậy ASN.1/TLV có ý nghĩa với tôi :-D)

3

Hãy chắc chắn rằng bạn dự trữ mã thẻ (hoặc tốt hơn là đặt trước một chút trong mỗi thẻ) chỉ định khối/đoạn đã xóa/miễn phí. Sau đó, các khối có thể bị xóa bằng cách thay đổi mã thẻ hiện tại của khối thành mã thẻ đã xóa hoặc đặt bit đã xóa của thẻ. Bằng cách này bạn không cần phải ngay lập tức cơ cấu lại tệp của bạn khi bạn xóa một khối.

Đặt một chút trong thẻ cung cấp tùy chọn có thể hủy xóa khối (nếu bạn không thay đổi dữ liệu của khối).

Để bảo mật, tuy nhiên bạn có thể muốn xóa dữ liệu của khối đã xóa, trong trường hợp này, bạn sẽ sử dụng thẻ bị xóa/miễn phí đặc biệt.

Tôi đồng ý với Stepan, rằng bạn nên chọn một endianess, nhưng tôi cũng sẽ có một chỉ số endianess trong tập tin. Nếu bạn sử dụng chỉ báo endianess, bạn có thể cân nhắc sử dụng một trong số UniCode Byte Order Marks cũng như một dấu hiệu của bất kỳ mã hóa văn bản UniCode nào được sử dụng cho bất kỳ khối văn bản nào. BOM thường là vài byte đầu tiên của các tệp văn bản UniCoded, vì vậy nếu BOM của bạn là mục nhập đầu tiên trong tệp của bạn, có thể có sự cố về một số tiện ích xác định tệp của bạn dưới dạng văn bản UniCode (tôi không nghĩ rằng đây là vấn đề) . Tôi sẽ coi/dự trữ BOM là một trong các thẻ thông thường của bạn (sử dụng UTF16 BOM nếu sử dụng thẻ 16 bit hoặc UTF32 BOM nếu sử dụng thẻ 32 bit) với khối 0 độ dài/đoạn.

Xem thêm http://en.wikipedia.org/wiki/File_format

15

Tôi đồng ý rằng đây là những ý tưởng tốt:

  1. Magic số lúc đầu. Khá nhiều required trong * nix:

  2. Số phiên bản tệp để tương thích ngược.

  3. Đặc điểm kỹ thuật cuối cùng.

Nhưng một phần tư của bạn là quá mức cần thiết, bởi vì # 2 cho phép bạn thêm các trường chừng nào bạn thay đổi số phiên bản (và miễn là bạn không cần phải forward compatibility).

  • có thể đặt trước một số khoảng trống cho các thuộc tính cho mỗi tệp có thể cần thiết trong tương lai?

Ngoài ra, ý tưởng áp đặt cấu trúc khối trên tệp của bạn, được thể hiện bằng nhiều câu trả lời khác, có vẻ ít hơn yêu cầu chung cho tệp nhị phân hơn giải pháp cho vấn đề với một số loại tải trọng nhất định.

Ngoài 1-3 ở trên, tôi muốn thêm những:

  • checksum đơn giản hoặc cách nào khác phát hiện rằng nội dung không còn nguyên vẹn. Nếu không, bạn không thể tin tưởng các byte ma thuật hoặc số phiên bản. Hãy cẩn thận để xác định byte nào được bao gồm trong tổng kiểm tra. Thông thường, bạn sẽ bao gồm tất cả các byte trong tệp chưa có phát hiện lỗi.

  • phiên bản phần mềm của bạn (bao gồm số chi tiết nhất mà bạn có, ví dụ: số bản dựng) đã ghi tệp. Bạn sẽ nhận được một báo cáo lỗi với một tập tin đính kèm từ một người không thể mở nó và họ sẽ không có đầu mối khi họ viết các tập tin vì lỗi đã không xảy ra sau đó. Nhưng lỗi là trong phiên bản đã viết nó, không phải trong một trong những cố gắng để đọc nó.

  • Làm rõ trong định dạng này là định dạng nhị phân, nghĩa là tất cả giá trị 0-255 được phép cho tất cả byte (ngoại trừ số ma thuật).

Và đây là một số những tùy chọn:

  • Nếu bạn cần khả năng tương thích về phía trước, bạn cần một số cách thể hiện mà "khối" là "bắt buộc" (như png không), do đó một phiên bản trước của phần mềm của bạn có thể bỏ qua chúng một cách duyên dáng.

  • Nếu bạn mong đợi các tệp này được tìm thấy "trong tự nhiên", bạn có thể xem xét nhúng một số đầu mối để tìm thông số. Hãy tưởng tượng mức độ hữu ích của việc tìm chuỗi http://www.w3.org/TR/PNG/ trong tệp png.

+0

Tại sao phạm vi đầy đủ 0-255 không được chấp nhận đối với các số ma thuật, như bạn đã nêu? Và, phạm vi thích hợp là gì? Cảm ơn trước. – bazz

4

điểm khác, lấy từ tập tin .xz spec (http://tukaani.org/xz/xz-file-format.txt): một trong số ít byte đầu tiên nên là một tổ chức phi-nhân vật, "để ngăn chặn các ứng dụng từ misdetecting tập tin dưới dạng file văn bản.". Lưu ý chắc chắn có bao nhiêu byte tiêu đề thường được kiểm tra bởi các biên tập viên và các công cụ khác, nhưng việc sử dụng một byte không phải nhị phân trong bốn hoặc tám byte đầu tiên có vẻ hữu ích.

1

Nếu bạn đang xử lý dữ liệu có độ dài thay đổi, thì nhiều hiệu quả hơn để sử dụng con trỏ: Có một chuỗi con trỏ tới dữ liệu của bạn, lý tưởng gần đầu tệp, thay vì lưu trữ dữ liệu trong một mảng trực tiếp.

Vô hướng là thích hợp hơn trong trường hợp này bởi vì nó cho phép truy cập ngẫu nhiên, chỉ có thể nếu tất cả các mục có cùng kích thước. Nếu dữ liệu được lưu trữ trực tiếp trong một mảng, mà không chỉ định vị trí của bất kỳ bản ghi nào, truy cập dữ liệu sẽ mất thời gian O (n) trong trường hợp xấu nhất; để mã đọc tệp của bạn truy cập vào một phần tử cụ thể, nó sẽ phải biết độ dài của tất cả các phần tử trước đó và cách duy nhất để tìm ra điều đó là xem xét từng phần tử. Nếu bạn đang đọc toàn bộ tập tin cùng một lúc, thì bạn vẫn đang làm điều này, vì vậy nó sẽ không là vấn đề. Nhưng nếu bạn chỉ muốn một điều, thì đây không phải là cách để đi.

Trong khi đó với một mảng con trỏ, đó là thời gian O (1) xung quanh: tất cả những gì bạn cần là số chỉ mục và bạn có thể truy xuất và theo con trỏ để lấy dữ liệu của bạn.

Khi viết tệp bằng phương pháp này, bạn sẽ phải xây dựng bảng của bạn trong bộ nhớ trước khi thực hiện bất kỳ văn bản nào.

+0

Tôi thấy câu trả lời của bạn thú vị và hy vọng bạn có thể xây dựng thêm một chút. Tôi hiện đang thiết kế định dạng bin và muốn kết hợp ý tưởng này của bạn. Nếu thời gian của bạn cho phép cảm thấy chào đón PM tôi. –

2

Một trong những điều quan trọng nhất cần biết trước khi bắt đầu ngay cả cách tệp của bạn sẽ được sử dụng như thế nào.

  • Truy cập ngẫu nhiên hoặc tuần tự là tiêu chuẩn?
  • Tần suất đọc dữ liệu?
  • Tần suất dữ liệu sẽ được ghi?
  • Bạn sẽ ghi tệp ra một lần hoặc bạn sẽ làm chậm việc ghi tệp khi dữ liệu được nhập.
  • Tệp có cần phải di chuyển không? Không phải mọi định dạng đều cần.
  • Ứng dụng có cần tương thích với các phiên bản khác không? Có thể cập nhật tệp là đủ.
  • Bạn có cần đọc/ghi dễ dàng không?
  • Kích thước/Tốc độ/Sự cân bằng về tính tương thích.

Hầu hết các câu trả lời ở đây đều cung cấp tư vấn tốt về mặt trước khả năng tương thích/di động để tôi không thêm nhiều hơn nữa. Nhưng hãy xem xét những điều sau đây (thường bị bỏ qua).

  • Một số tệp thường được viết và hiếm khi đọc (bản sao lưu, nhật ký, ...) và bạn có thể muốn tập trung vào các tệp và dễ viết.
  • Tính cuối cùng chuyển đổi chậm (tương đối) nếu tệp của bạn sẽ không bao giờ rời khỏi máy chủ hoặc để lại ít khi chuyển đổi đó là một lựa chọn tốt, bạn có thể tăng hiệu suất đáng kể. Hãy xem xét viết một số như 0x1234 như một phần của tiêu đề để bạn có thể phát hiện (và hướng dẫn người dùng chuyển đổi) nếu đây là trường hợp.
  • Đôi khi việc đọc dễ dàng thực sự hữu ích. Nếu bạn đang làm nhật ký hoặc tài liệu văn bản, hãy xem xét việc nén tất cả trong một lần thay vì nhập mỗi lần để bạn có thể zcat | strings tệp và xem nội dung bên trong.

Có nhiều điều cần ghi nhớ và thiết kế một định dạng tốt có rất nhiều kế hoạch và tầm nhìn xa. Những thứ nhỏ như zcat nhập tệp và nhận thông tin hữu ích hoặc tăng hiệu suất nhỏ từ việc sử dụng số nguyên gốc có thể giúp sản phẩm của bạn trở nên cạnh tranh, tuy nhiên bạn cần phải cẩn thận rằng bạn không hy sinh điều gì đó quan trọng.

5

Chỉ để lưu nội dung, tôi tìm thấy this link, có thể liên quan đến câu hỏi ở trên.