在 UNIX(可能还有其他操作系统)下的 C 中,通常有两个缓冲区,至少在您给定的场景中是这样。
第一个存在于 C 运行时库中,其中要写入的信息在传送到操作系统之前被缓冲。
第二个是在操作系统本身中,信息被缓冲,直到可以物理地写入底层媒体。
例如,我们在很多个月前编写了一个日志库,它强制将信息写入磁盘,以便在程序崩溃或操作系统崩溃时它会在那里。
这是通过以下顺序实现的:
fflush (fh); fsync (fileno (fh));
其中第一个实际上确保信息从 C 运行时缓冲区传递到操作系统,第二个确保将信息写入磁盘。请记住,这是一项昂贵的操作,只有在您绝对需要立即写入信息时才应执行(我们仅在 SUPER_ENORMOUS_IMPORTANT 日志记录级别执行此操作)。
说实话,我不完全确定为什么你的面试官认为它会很慢,除非你正在写大量的信息。很多。已经存在的两个缓冲级别应该可以很好地执行。如果 是 一个问题,那么您可以自己引入另一个层,将消息写入内存缓冲区,然后在即将溢出时将其传递给单个 fprint-type 调用.
但是,除非您在没有任何函数调用的情况下执行此操作,否则我看不出它比 fprint-type 缓冲已经为您提供的快得多。
在 cmets 中澄清这个问题实际上是关于内核内部的缓冲之后:
基本上,您希望它尽可能快速、高效和可行(不易出现故障或资源短缺)。
最好的选择可能是一个缓冲区,可以是静态分配的,也可以是在启动时动态分配一次(您希望避免动态重新分配失败的可能性)。
其他人建议使用环形(或循环)缓冲区,但我不会(技术上)采用这种方式,原因如下:使用经典的循环缓冲区意味着在数据回绕时写出数据需要两个独立的写入。例如,如果您的缓冲区有:
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|s|t|r|i|n|g| |t|o| |w|r|i|t|e|.| | | | | | |T|h|i|s| |i|s| |t|h|e| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
^ ^
| |
Buffer next --+ +-- Buffer start
那么你必须写"This is the ",然后是"string to write."。
相反,维护next 指针,如果缓冲区中的字节加上要添加的字节小于缓冲区大小,则只需将它们添加到缓冲区,而不对底层媒体进行物理写入。
只有当你要溢出缓冲区时,你才会开始做一些棘手的事情。
您可以采取以下两种方法之一:
- 要么按原样刷新缓冲区,要么将
next 指针设置回起点以处理新消息;或
- 添加部分消息以填满缓冲区,然后刷新缓冲区并将
next 指针设置回起点以处理其余消息。
我可能会选择第二个,因为您将不得不考虑对于缓冲区来说太大的消息。
我在说的是这样的:
initBuffer:
create buffer of size 10240 bytes.
set bufferEnd to end of buffer + 1
set bufferPointer to start of buffer
return
addToBuffer (size, message):
while size != 0:
xfersz = minimum (size, bufferEnd - bufferPointer)
copy xfersz bytes from message to bufferPointer
message = message + xfersz
bufferPointer = bufferPointer + xfersz
size = size - xfersz
if bufferPointer == bufferEnd:
write buffer to underlying media
set bufferPointer to start of buffer
endif
endwhile
这基本上通过减少物理写入的数量来有效地处理任何大小的消息。当然会有优化 - 消息可能已被复制到内核空间,所以如果你要写它,将它复制到缓冲区几乎没有意义。您也可以将内核副本中的信息直接写入底层媒体,并且只将最后一位传输到缓冲区(因为您必须保存它)。
此外,如果有一段时间没有写入任何内容,您可能希望将不完整的缓冲区刷新到底层媒体。这将减少因内核本身崩溃而丢失信息的可能性。
旁白:从技术上讲,我猜这 是 一种循环缓冲区,但它具有特殊情况处理以最大限度地减少写入次数,并且由于优化,不需要尾指针。 p>