【问题标题】:How to read a binary file entirely and quickly in Ada?如何在 Ada 中完整快速地读取二进制文件?
【发布时间】:2012-12-19 16:40:55
【问题描述】:

我想读取几个 MB 的二进制文件的内容并将其存储到缓冲区中。这是我的函数原型(如果需要,我可以更改它):

procedure GET_BIN_CONTENT_FROM_PATH(PATH    : in UNBOUNDED_STRING;
                                    CONTENT : out UNBOUNDED_STRING);

到目前为止,我已经尝试了两种方法,都使用 Direct_IO 包。在第一种方法中,我是逐字符读取文件;它起作用了,但是速度非常慢。为了加快进程,我尝试按 MB 读取文件:

procedure GET_BIN_CONTENT_FROM_PATH (PATH    : in UNBOUNDED_STRING;
                                     CONTENT : out UNBOUNDED_STRING) is

   BIN_SIZE_LIMIT : constant NATURAL := 1000000;
   subtype FILE_STRING is STRING (1 .. BIN_SIZE_LIMIT);
   package FILE_STRING_IO is new ADA.DIRECT_IO (FILE_STRING);
   FILE : FILE_STRING_IO.FILE_TYPE;
   BUFFER : FILE_STRING;

begin
   FILE_STRING_IO.OPEN (FILE, MODE => FILE_STRING_IO.IN_FILE,
                        NAME => TO_STRING (C_BASE_DIR & PATH));
   while not FILE_STRING_IO.END_OF_FILE (FILE) loop
      FILE_STRING_IO.READ (FILE, ITEM => BUFFER);
      APPEND (CONTENT, BUFFER);
   end loop;
   FILE_STRING_IO.CLOSE (FILE);
end GET_BIN_CONTENT_FROM_PATH;

不幸的是,如果文件中剩余的空间少于 1MB,READ 操作似乎不会发生。结果,大文件 (>1MB) 会被截断,而小文件根本不会被读取。在处理图像时尤其明显。

所以,我的问题是:快速完整地读取二进制文件的正确方法是什么?

提前致谢。

【问题讨论】:

    标签: file io binaryfiles ada


    【解决方案1】:

    让Bin_Size等于Ada.Directories.Size(my_file),一口气读完。

    如果它对于堆栈分配来说太大(你会得到 Storage_Error)用 New 分配它,并使用重命名技巧

    my_image : bin_array renames my_image_ptr.all;
    

    所以没有什么需要知道的......
    但如果它只有几 MB,那就没有必要了。

    【讨论】:

    • 为一个变量分配几 MB 的堆栈数据可能很危险。在某些系统上,这可能会耗尽您的堆栈,尤其是在递归调用该函数或您已经用完大部分堆栈的情况下。
    • 在 PC 上,“堆栈太大”的限制在数百 MB 左右。 (显然,没有递归!)。所以在很多情况下,它是最好的选择,也是最简单的。小型系统和嵌入式软件,您必须针对系统的约束进行设计(在嵌入式系统中,可能会禁止动态堆分配)。
    【解决方案2】:

    Ada.Streams.Stream_IO.Read 读入Stream_Element_Array 并告诉您最后读取的元素;如果数组未填充(因为您已到达文件末尾),Last 将小于 Item'Last

    纯粹主义者会注意到Ada.Streams.Stream_Element'Size 可能与Character'Size 不同,但对于任何普通的处理器芯片都是如此,因此您可以在Stream_Element_Array 的使用部分和@987654328 之间进行未经检查的转换@ 在附加到您的 Content 之前具有相同大小。

    【讨论】:

    • 这是我过去用于此确切要求的方法。为了效率,您的目标应该是在单个 I/O 中执行整个读取。
    【解决方案3】:

    有许多“正确”的方法,但这里有一个您可能会喜欢的方法。尤其是在读取大文件时,读取整个文件的有效方法是使用mmap映射内存。

    根据您的许可需求,您可以向第三方 GPLd 解决方案开放。 AdaCore 提供了GNATColl 集合,它为mmap 提供了一个很好的接口。您可以映射整个文件并复制内容。

    declare
       File : Mapped_File;
       Str  : Str_Access;
    begin
       File := Open_Read ("/tmp/file_on_disk");
       Read (File);  --  read the whole file
       Str := Data (File);
       for S in 1 .. Last (File) loop
           Put (Str (S));
       end loop;
       Close (File);
    end;
    

    如果您的系统不支持 mmap 调用,则库将回退到 read(2) 实现。

    【讨论】:

    • 我喜欢使用内存映射的想法。我认为这可能是实现这一目标的更优化的方式。由于某些原因,我不想依赖第 3 方,但我会查看 GNATCool 代码,看看我是否可以直接使用 mmap。谢谢。
    • 如果你不想使用 GANTColl,你可以尝试自己实现它。这将涉及pragma import将 mmap 调用添加到您的代码库中。
    【解决方案4】:

    正如其他人所提到的,Ada.Streams.Stream_IO.Read 是要走的路。这是我放在系统上的一个示例。假设您有足够的内存可用于动态分配,这能够读取大于堆栈大小的文件。

    我还没有深入研究 Stream.IO.Read 代码的内部结构,但我怀疑 Stream_IO 包正在使用 4k 内存块(从堆中分配)来缓冲读取操作。

    with Ada.Directories;  use Ada.Directories;
    with Ada.Direct_IO;
    with Ada.Unchecked_Deallocation;
    with Ada.Streams.Stream_IO;
    
    procedure Read_Input_File is
    
       type Byte is mod 2 ** 8;
       type Byte_Array is array (File_Size range <>) of Byte;
       type Byte_Array_Access is access Byte_Array;
          
       procedure Delete is new Ada.Unchecked_Deallocation 
                               (Byte_Array, Byte_Array_Access);
     
       function Read_Binary_File (Filename : String)
          return Byte_Array_Access
       is
          package SIO renames Ada.Streams.Stream_IO;
     
          Binary_File_Size : File_Size := Ada.Directories.Size (Filename);
          Binary_File_Data : Byte_Array_Access; 
          S                : SIO.Stream_Access;   
          File             : SIO.File_Type;
     
       begin
          -- Allocate memory from the heap 
          Binary_File_Data := new Byte_Array (1 .. Binary_File_Size);
     
          SIO.Open (File, SIO.In_File, Filename);
          S := SIO.Stream (File);
     
          -- Read entire file into the buffer
          Byte_Array'Read (S, Binary_File_Data.all);
     
          SIO.Close (File);
     
          return Binary_File_Data;
       end;
    
       File_Data : Byte_Array_Access;
    
    begin
    
       File_Data := Read_Binary_File ("File_Name.bin");
    
       -- Do something with data
    
       Delete (File_Data);
    
    end Read_Input_File;
    

    【讨论】:

      猜你喜欢
      • 2019-04-24
      • 2018-03-23
      • 2011-11-17
      • 2012-12-25
      • 1970-01-01
      • 2020-06-03
      • 1970-01-01
      • 2012-05-01
      • 1970-01-01
      相关资源
      最近更新 更多