如何快速读取二进制文件

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何快速读取二进制文件相关的知识,希望对你有一定的参考价值。

dim mybyte(100000) as byte
...
get #1,100000 ,mybyte()
...
能行吗?
朋友们有什么好办法?
四楼ljl88900 - 探花 十一级 ,有些实用,但我是任意文件对象,如对大图片:
s = StrConv(BB, vbUnicode) '读出来的字符变量 是空的.
k4me - 状元 十四级
wangwei_sa - 魔法师 四级
都是很好的建议.
谢谢.
ljl88900 - 探花 十一级
ReDim BB(FileLen(FileName) - 1)中
FileLen(FileName)的大小是否限制?如<32767?

  
  快速读取二进制文件的方法如下:
  1、打开文件。

  打开文件可以有两种方式,第一种可以使用fstream类的构造函数。fstream file("test.dat",ios_base::in|ios_base::out|ios_base::app); 另外一种方法就是使用
  open函数。fstream file;file.open("test.dat",ios_base::in|ios_base::out|ios_base::app);
  这样就可以打开一个可读写的文件了。如果文件不存在的话,就会创建一个新文件并且以读写方式打开。这里需要说明一点,如果文件不存在的话,open函数中第二个参数必须包含ios_base::out|ios_base::app,否则就不能正确创建文件。
  
  2、写文件。
  先进性写文件的操作否则读一个空文件是没有意义的。
  既然是写二进制文件可以向文件中写入一个整形值。写二进制字符只能使用write
  函数。但是write函数的原形是write(const char * ch, int size)。第一个参数是char *
  类型,所以需要把将要写入文件的int类型转换成char *类型。这里的转换困扰了我好几天,不过终于弄明白了。代码如下。int temp; file.write((char *)(&temp),sizeof(temp));
  3、读文件。
  可以写文件了,读文件就好办多了。读文件需要用到read函数。其参数和write
  大致相同,read(const char * ch, int size)。要把内容读到int类型变量中同样涉及到一个类型转换的问题。和写文件一样。int readInt; file.read((char(&readInt),sizeof(readInt)); 这样文件中的int值就读入到int型变量
  readInt中了。
  
  4、文件指针。
  在文件的读写过程中往往需要对文件进行选择性读取。所以需要进行文件指针的移动。这是需要用到seekg和seekp函数。在fstream类中有两个文件指针,一个是读取文件的指针
  ,一个是写文件的指针分别用tellg和tellp文件来取得指针的位置。同样seekg和seekp两个函数分别是对这两个指针进行移动的函数。这两个函数的参数都是一样的。先对几个枚举类型进行一下说明:
  ios_base::beg ——文件开始位置ios_base::cur ——文件当前位置ios_base::end ——文件末尾位置下面以seekg为例说明一下指针移动的方法:file.seekg(3) ——指针移动到第三个字符的位置file.seekg(ios_base::beg) ——指针移动到文件开头file.seekg(ios_base::end) ——指针移动到文件末尾file.seekg(-3,ios_base::cur) ——
  指针当前位置向前移动三个字符file.seekg(3,ios_base::cur) ——指针当前位置向后移动三个字符file.seekg(3,file.tellg()) ——指针当前位置向后移动三个字符file.seekg(file.tellg()+3) ——指针当前位置向后移动三个字符
  
  5、对文件操作完毕后别忘了关闭文件。
  
参考技术A Visual C++
如何:读取二进制文件

下面的代码示例演示如何从文件中读取二进制数据。使用了 System.IO 命名空间中的两个类:FileStream 和 BinaryReader。FileStream 表示实际的文件。BinaryReader 为允许二进制访问的流提供接口。

下面的代码示例使用由如何:编写二进制文件中的代码创建的称为 data.bin 的文件。
示例

// binary_read.cpp
// compile with: /clr
#using<system.dll>
using namespace System;
using namespace System::IO;

int main()

String^ fileName = "data.bin";
try

FileStream^ fs = gcnew FileStream(fileName, FileMode::Open);
BinaryReader^ br = gcnew BinaryReader(fs);

Console::WriteLine("contents of 0:", fileName);
while (br->BaseStream->Position < br->BaseStream->Length)
Console::WriteLine(br->ReadInt32().ToString());

fs->Close( );

catch (Exception^ e)

if (dynamic_cast<FileNotFoundException^>(e))
Console::WriteLine("File '0' not found", fileName);
else
Console::WriteLine("Exception: (0)", e);
return -1;

return 0;


★★补充★★
手上的一个VB项目(过程中发现,.Net果然是好啊),需要在一个ActiveX中实现HTTP下载功能,我是采用InternetreadFile这个API来实现,一开始的代码我是这么写的

Function Gethttpdownload(sUrl As String) As boolen

Dim s As String
Dim hOpen As Long
Dim hOpenUrl As Long
Dim bDoLoop As Boolean
Dim bRet As Boolean
Dim sReadBuffer As String * 2048
Dim lNumberOfBytesRead As Long
hOpen = InternetOpen(scUserAgent, INTERNET_OPEN_TYPE_PRECONFIG, vbNullString, vbNullString, 0)
hOpenUrl = InternetOpenUrl(hOpen, sUrl, vbNullString, 0, INTERNET_FLAG_RELOAD, 0)
bDoLoop = True
Do While bDoLoop
sReadBuffer = vbNullString
bRet = InternetReadFile(hOpenUrl, sReadBuffer, Len(sReadBuffer), lNumberOfBytesRead)
s = s & Left$(sReadBuffer, lNumberOfBytesRead)
If Not CBool(lNumberOfBytesRead) Then bDoLoop = False
Loop
FileName = "E:\BitSpirit\Torrent\121212.torrent"
F1 = FreeFile
Open FileName For Binary As F1
Put F1, , s
Close F1
If hOpen <> 0 Then InternetCloseHandle (hOpen)
Gethttpdownload = true
End Function

上面方法,用来获取服务器上的文本类型的文件一点问题,都没有,但是用来下载二进制文件的时候 就出现问题了,里面的数据怎么也不对,研究了下载下来的文件后发现,问题外话应该是在接收数据的变量是个STRING的定长字符串上。但是在网上查了好久,甚至M$ MSDN上的一个用VB来实现下载的程也是用 string类型来接收数据的而且网上的代码写法,基本上也都是这个样,好来才好现,都是从MSDN上的哪个例程上演变过来的.
我想如果能用一个byte数组来代替定长字符串,哪可能就没有问题了,但是查看了一个VB 对Internetreadfile的申明
Public Declare Function InternetReadFile Lib "wininet.dll" (ByVal hFile As Long, ByVal sBuffer As String, ByVal lNumBytesToRead As Long, lNumberOfBytesRead As Long) As Integer
发现其定义ByVal sBuffer As String 看来只能用String了,在网上查找过程中,发现人家用VC写的程序中这人参数可以是其它的,所以查看了一下Internetreadfile的原型。发现的确可以, 所以我把internetreadfile的定义修改了一下,为了通用,我为新的internetreadfile定义了一个别名。Internetreadfilebyte申明如下:
Public Declare Function InternetReadFileByte Lib "wininet.dll" Alias "InternetReadFile" (ByVal hFile As Long, ByRef sBuffer As Byte, ByVal lNumBytesToRead As Long, lNumberOfBytesRead As Long) As Integer
试了一下的确可以,重新修改函数,(在这过程中发现,如果要取到正确数据,还必须取得文件大小。所以增加了HttpQueryInfo的定义)最后完整的函数
Function FileDownload(sUrl As Variant) As Boolean
Dim b(99) As Byte
Dim EndByte() As Byte
Dim s As String
Dim hOpen As Long
Dim hOpenUrl As Long
Dim bDoLoop As Boolean
Dim bRet As Boolean
Dim bbuffer As Byte
Dim sReadBuffer As String
Dim FileName As String
Dim lNumberOfBytesRead As Long
Dim F1 As Integer
Dim strsize As String
Dim size As Long
strsize = String$(1024, " ")

F1 = FreeFile
stTotal = vbNullString
FileName = "E:\BitSpirit\Torrent\121212.torrent"
Open FileName For Binary As F1
hOpen = InternetOpen(scUserAgent, INTERNET_OPEN_TYPE_PRECONFIG, vbNullString, vbNullString, 0)
hOpenUrl = InternetOpenUrl(hOpen, sUrl, vbNullString, 0, INTERNET_FLAG_RELOAD, 0)
bDoLoop = True
HttpQueryInfo hOpenUrl, HTTP_QUERY_CONTENT_LENGTH Or HTTP_QUERY_FLAG_NUMBER, ByVal strsize, Len(strsize), 0
size = CLng(Trim(strsize))
For j = 1 To size \ 100
bDoLoop = InternetReadFileByte(hOpenUrl, b(0), 100, lNumberOfBytesRead)

Put F1, , b
If Not CBool(lNumberOfBytesRead) Then Exit For
Next
If size Mod 100 <> 0 Then
tmp = (size Mod 100) - 1
ReDim EndByte(tmp)
bDoLoop = InternetReadFileByte(hOpenUrl, EndByte(0), tmp + 1, lNumberOfBytesRead)
Put F1, , EndByte
End If

If hOpenUrl <> 0 Then InternetCloseHandle (hOpenUrl)
If hOpen <> 0 Then InternetCloseHandle (hOpen)

Close #1
FileDownload = True
End Function
测试了一下,完全成功:)本回答被提问者采纳
参考技术B 100K太少,目前机器缓冲可以做到20M以上,可以大幅降低磁盘读取次数.如果为了转移文件,每次Get 20000000.最好检测是否同盘,如果是在两个物理驱动器之间,可以同时操作读写. 参考技术C 根据你的反馈,修改如下:
可以,但需要改进。这样做的好处时速度很快,特别适用于大型文件的读取。
举例如下:

'读文件至变量
Private Sub Command1_Click()
dim FileName As String
Dim i As Integer, s As String, BB() As Byte
FileName="c:\1.dat"
If Dir(FileName) = "" Then Exit sub
i = FreeFile
ReDim BB(FileLen(FileName) - 1)'按文件长度读取
Open FileName For Binary As #i
Get #i, , BB
Close #i
s=bb '*****适用于非文本文件*****
's = StrConv(BB, vbUnicode) '*****适用于文本文件*****
End Sub
参考技术D 其实一般的方法,主要代码也就这么几句(但希望得到尽可能的优化):
while(1)
if( bitLeft <= 0 )
cByte = *inP++;
bitLeft = 8;

code = ( cByte >> ( 8 - bitLeft ) );

while( cSize > bitLeft )

cByte = *inP++;
code |= ( cByte << bitLeft );
bitLeft += 8;

bitLeft -= cSize;
code &= code_mask[cSize];
........


其中cSize表示码的长度(如10),从inP中读数据到一个code

从论坛上面借鉴的,不知道是不是你想要的

在 C/C++ 中快速读取多个文件的某些字节

【中文标题】在 C/C++ 中快速读取多个文件的某些字节【英文标题】:Fast read of certain bytes of multiple files in C/C++ 【发布时间】:2010-05-20 14:20:13 【问题描述】:

我一直在网上搜索这个问题,虽然有很多关于 C/C++ 读/写的类似问题,但我还没有找到关于这个特定任务的信息。

我希望能够从多个文件(256x256 文件)仅读取位于每个文件特定位置的sizeof(double) 字节。现在我的解决方案是,对于每个文件:

    打开文件(读取,二进制模式):

    fstream fTest("current_file", ios_base::out | ios_base::binary);

    求我想读的位置:

    fTest.seekg(position*sizeof(test_value), ios_base::beg);

    读取字节:

    fTest.read((char *) &amp;(output[i][j]), sizeof(test_value));

    并关闭文件:

    fTest.close();

这大约需要 350 msfor for 结构中运行,迭代次数为 256x256(每个文件一个)。


问:你认为有没有更好的方法来实现这个操作?你会怎么做?

【问题讨论】:

如果我理解正确,您有 65536 个文件要读取,但您从哪里获得文件名?目录列表?预填充数组? API 调用?还有,你能不能把“外部”结构合并修改成不需要读取65536个文件? 我知道性能越多越好,但 350ms 真的有问题吗?至少您当前的解决方案是可移植的。为了优化,您可能需要查看是否存在依赖于操作系统的技巧或其他数据组织。 @roygbiv:现在它在所有迭代中使用相同的文件(这只是一个预测试)。另一方面,我不确定我是否能够优化流程以停止调用 65536 文件。 @stefaanv:错误,如果您只需要运行一次代码,350 毫秒就足够了。但我做这个操作 256x256 次,这意味着我有 14 个小时的生命等待它结束:( 我无法理解的是为什么您只需要 65536 个文件中的一个 8 字节值。无法以其他格式获取此数据? 如果您的 65536 个文件都在一个目录中,那可能会大大降低速度。尝试使用树形结构,使文件W_i_j 存储在i/W_i_j 中。 【参考方案1】:

如果可能,我建议重新组织数据。例如,将所有这些双打放在一个文件中,而不是将它们分散到多个文件中。

如果您需要多次运行程序并且数据不变,您可能需要创建一个工具来首先优化数据。

文件的性能问题是:

    开销提升硬盘驱动器。 overhead定位文件。 在文件中定位。 读取数据。 关闭文件对 表演。

在大多数使用大量数据的基于文件的系统中,读取数据经过优化,其持续时间比任何开销都长。请求将被缓存和排序以获得最佳磁盘访问。不幸的是,在您的情况下,您没有读取足够的数据,因此开销现在比读取的持续时间更长。

我建议尝试对数据的读取操作进行排队。占用 4 个线程,每个线程打开一个文件并读取双精度数,然后将它们放入缓冲区。这里的想法是错开操作。

线程 1 打开一个文件。 线程 2 打开文件,而线程 1 正在定位。 线程 3 打开文件,而线程 2 是定位,线程 1 是 读取数据。 线程 4 打开一个文件,线程 3 位置,线程 2 读取,线程 1 关闭。

希望这些线程可以保持硬盘驱动器足够繁忙而不会变慢;持续的活动。您也许可以先在单个线程中尝试此操作。如果您需要更好的性能,您可能需要考虑将命令直接发送到磁盘驱动器(先订购它们)。

【讨论】:

【参考方案2】:

也许线程会有所帮助。

但首先你可以尝试一些更简单的方法。制作程序的两份副本,一份读取前 32768 个文件,另一份读取后半部分。同时运行这两个程序。这需要不到 14 小时吗?

如果没有,那么添加线程可能是无用的。正如上面 roygiv 建议的那样,碎片整理可能会有所帮助。

添加:14 小时显然是错误的,因为这几乎是每个文件 1 秒。 Alejandro 上面的评论说,使用固态驱动器,每个文件的时间只有 0.1 毫秒,总共 6.5 秒。这对我来说似乎很快。

所以我猜 Alejandro 必须重复此操作大约 7000 次,每次使用来自 65536 个文件的不同数据片段。如果是这样,还有两个建议是:

编写一个程序将文件分类到一个 新文件。你可能有足够的 SSD 上的空间来执行此操作,因为您的 其他 SO 问题表明 32 GB 数据,而 SSD 可能是几个 次。然后每次运行只使用 这个巨大的文件,它删除 65535 打开和关闭。

而且,不仅仅是串联, 在创建大文件时 可以“反转行和列” 或“条带化数据”,提供 地点。

进一步补充:您可能已经考虑过这一点,您的短语“将读取的数据写入单个文件”。

【讨论】:

如果您的文件存储在单个驱动器上,您的 I/O 子系统可能会饱和。但是,如果它们位于不同的驱动器上,您可能会看到性能略有提高。 我不会使用 for 循环 - 我会使用队列,然后让不同的线程从队列中拉出文件进行处理。 @roygbiv 是正确的,但是如果您使用本地驱动器并且如果您在网络上运行,那么您的 I/O 将会饱和,那么您的网络连接将会饱和。如果不进一步了解您的系统的方式和原因,我们将无法提供更多帮助。 如果可能的话。运行此测试,其中一半文件位于一个物理硬盘驱动器上,另一半位于另一个物理硬盘驱动器上。 你是对的,我错了 14 小时。在回家的路上,我正在考虑这个问题,将文件连接成一个文件的想法是我的建议之一,现在我看到你同意了!这可能会恢复读取中的 350 毫秒(考虑到文件大小可能会改变查找时间)。【参考方案3】:

如果你真的想优化它,你可能想放弃 C++ fstream 的东西,或者至少关闭它的缓冲。 fstream 做了很多内存分配和释放,缓冲可能会读取比需要更多的数据。操作系统可能需要读取整个页面才能获得所需的几个字节,但 fstream 可能希望它至少将那么多(可能更多,需要更多读取)复制到其缓冲区,这需要时间。

现在,我们可以继续取得更大的胜利。您可能想直接使用操作系统的 IO 例程。如果您使用的是 POSIX 系统(如 Linux),那么 openlseekreadclose 是一个很好的首选,如果您没有下一个系统调用,则可能需要.

如果您尝试读取的所有文件都位于一个目录(文件夹)或一个目录下,那么您可能会发现使用opendiropen("directory_name", O_DIRECTORY) 打开目录(取决于您是否需要阅读自己的目录条目)然后调用openat,它将目录条目文件描述符作为其参数之一将加快打开每个文件的速度,因为操作系统不会努力查找您尝试打开的文件每次(该数据可能会在操作系统的文件系统缓存中,但仍然需要时间并且需要进行大量测试)。

然后您可以使用pread 系统调用读取您的数据,而无需对您需要的数据的位置进行任何查找。 pread 采用偏移量,而不是使用操作系统的当前搜索点概念。这将至少为您节省一个系统调用。

编辑

如果您的系统支持异步 IO,这应该会加快速度,因为您可以继续并在检索之前让操作系统知道您想要什么(这可以让操作系统更好地安排磁盘读取,尤其是对于旋转磁盘),但这可能会变得复杂。不过,它可能会为您节省大量时间。

【讨论】:

那么,使用 openlseekreadclose 而不是 C++ fstream 函数真的相关吗?就我个人而言,我觉得纯 C 更舒服,但我被推荐使用 C++ 来达到这个目的。我会做一些测试。 如果您想从中获得最高性能,可以。来自 C++ 的流非常复杂,并且在幕后做了很多事情。你做了很多次非常具体的事情。如果您在带有strace 的系统上运行它,请尝试用它运行它进行几次迭代,看看它会吐出什么。您也可以对 ltrace 执行相同的操作,并查看在您打开、查找、读取和关闭流时,mallocfree 被调用了多少次。【参考方案4】:

鉴于问题的性质,我不确定您可以从中挤出多少性能。如果文件分布在几个不同的磁盘上,那么我可以看到为每个磁盘创建一个线程;这样你就可以一次并行化多个读取。但是,如果它们都在同一个磁盘上,那么在某种程度上,所有读取都将被序列化(我认为;我不是存储专家)。

I/O 是您的限制因素,而不是算法。

【讨论】:

【参考方案5】:

默认情况下 fstream API 不包括缓冲吗?我想知道是否将 API 切换到不使用缓冲的 API 或使用 setvbuf 禁用缓冲可能会加快速度。底层操作系统的缓存操作很可能意味着没有区别,但知道会很有趣。

【讨论】:

【参考方案6】:

颠倒迭代顺序。或者至少从磁盘读取一整页数据(例如每个文件 4kB)并将其存储在内存中,直到下一次通过。然后,您只需要在每 512 次通过时实际触摸文件系统。它将花费 256MB 的 RAM,但会节省数百 GB 的文件 I/O(即使您只请求 8 个字节,磁盘也必须将整个页面传输到缓存中)。并且你的操作系统的磁盘缓存替换算法很可能会删除 65k 调用打开旧的文件,所以不要相信它会为你做优化。

【讨论】:

以上是关于如何快速读取二进制文件的主要内容,如果未能解决你的问题,请参考以下文章

C如何简单的读写文件?二进制文件如何读写?如何移动文件指针?

如何使用 .NET 快速比较 2 个文件?

在python中如何从二进制文件中读取信息

在 C/C++ 中快速读取多个文件的某些字节

如何使用fread函数循环读取二进制文件

PHP如何将从二进制文件中读取的字节转换为数字