从 txt 文件中读取并删除第一行(或最后一行)而不复制
Read and remove first (or last) line from txt file without copying
我想读取并删除 txt 文件的第一行(不复制,这是一个巨大的文件)。
我已经阅读了网络,但每个人都只是将所需的内容复制到一个新文件中。我做不到。
低于第一次尝试。由于没有删除任何行,此代码将陷入循环。如果代码在每次打开时删除文件的第一行,代码将到达结尾。
#include <iostream>
#include <string>
#include <fstream>
#include <boost/interprocess/sync/file_lock.hpp>
int main() {
std::string line;
std::fstream file;
boost::interprocess::file_lock lock("test.lock");
while (true) {
std::cout << "locking\n";
lock.lock();
file.open("test.txt", std::fstream::in|std::fstream::out);
if (!file.is_open()) {
std::cout << "can't open file\n";
file.close();
lock.unlock();
break;
}
else if (!std::getline(file,line)) {
std::cout << "empty file\n"; //
file.close(); // never
lock.unlock(); // reached
break; //
}
else {
// remove first line
file.close();
lock.unlock();
// do something with line
}
}
}
你想做的,确实不容易。
如果不小心打开同一个文件进行读写,最终会读到刚写的,结果不是你想要的。
就地修改文件是可行的:只需打开它,在其中查找,修改并关闭。但是,您想复制文件开头的 K
字节以外的所有文件内容。这意味着您将不得不通过 N
字节的块迭代地读取和写入整个文件。
现在一旦完成,K
字节将保留在需要删除的末尾。我认为没有办法用流来做到这一点。您可以使用 unistd.h
中的 ftruncate
或 truncate
函数,或者为此使用 Boost.Interprocess truncate
。
这里有一个例子(没有任何错误检查,我让你添加):
#include <iostream>
#include <fstream>
#include <unistd.h>
int main()
{
std::fstream file;
file.open("test.txt", std::fstream::in | std::fstream::out);
// First retrieve size of the file
file.seekg(0, file.end);
std::streampos endPos = file.tellg();
file.seekg(0, file.beg);
// Then retrieve size of the first line (a.k.a bufferSize)
std::string firstLine;
std::getline(file, firstLine);
// We need two streampos: the read one and the write one
std::streampos readPos = firstLine.size() + 1;
std::streampos writePos = 0;
// Read the whole file starting at readPos by chunks of size bufferSize
std::size_t bufferSize = 256;
char buffer[bufferSize];
bool finished = false;
while(!finished)
{
file.seekg(readPos);
if(readPos + static_cast<std::streampos>(bufferSize) >= endPos)
{
bufferSize = endPos - readPos;
finished = true;
}
file.read(buffer, bufferSize);
file.seekg(writePos);
file.write(buffer, bufferSize);
readPos += bufferSize;
writePos += bufferSize;
}
file.close();
// No clean way to truncate streams, use function from unistd.h
truncate("test.txt", writePos);
return 0;
}
我真的很想能够为文件的就地修改提供更清晰的解决方案,但我不确定是否有。
这是一个用 C 语言为 Windows 编写的解决方案。
它将立即执行并完成 700,000 行、245MB 的文件。 (0.14 秒)
基本上,我对文件进行了内存映射,这样我就可以使用用于原始内存访问的函数来访问内容。一旦文件被映射,我就使用 strchr 函数在 windows(\n 和 \r)中找到用于表示 EOL 的一对符号中的一个的位置 - 这告诉我们多长时间(以字节为单位)第一行是。
从这里开始,我只是从第二行的第一个字节 memcpy 回到内存映射区的开始(基本上是文件中的第一个字节)。
完成此操作后,文件将取消映射,内存映射文件的句柄将关闭,然后我们使用 SetEndOfFile 函数将文件的长度减少第一行的长度。当我们关闭文件时,它缩小了这个长度并且第一行消失了。
自从我刚刚创建并写入文件以来,文件就已经在内存中,这显然会稍微改变执行时间,但是 windows 缓存机制就是这里的 'culprit' - 完全相同的机制我们正在利用它来快速完成操作。
测试数据为程序源复制10万次保存为testInput2.txt(粘贴10次,select全部,复制粘贴10次-替换原来的10次,为总共 100 次 - 重复直到输出足够大。我在这里停下来是因为更多似乎让 Notepad++ 'bit' 不开心)
此程序中几乎不存在错误检查,预计输入不是 UNICODE,即 - 输入为每个字符 1 个字节。
EOL序列为0x0D, 0x0A (\r, \n)
代码:
#include <stdio.h>
#include <windows.h>
void testFunc(const char inputFilename[] )
{
int lineLength;
HANDLE fileHandle = CreateFile(
inputFilename,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
NULL
);
if (fileHandle != INVALID_HANDLE_VALUE)
{
printf("File opened okay\n");
DWORD fileSizeHi, fileSizeLo = GetFileSize(fileHandle, &fileSizeHi);
HANDLE memMappedHandle = CreateFileMapping(
fileHandle,
NULL,
PAGE_READWRITE | SEC_COMMIT,
0,
0,
NULL
);
if (memMappedHandle)
{
printf("File mapping success\n");
LPVOID memPtr = MapViewOfFile(
memMappedHandle,
FILE_MAP_ALL_ACCESS,
0,
0,
0
);
if (memPtr != NULL)
{
printf("view of file successfully created");
printf("File size is: 0x%04X%04X\n", fileSizeHi, fileSizeLo);
LPVOID eolPos = strchr((char*)memPtr, '\r'); // windows EOL sequence is \r\n
lineLength = (char*)eolPos-(char*)memPtr;
printf("Length of first line is: %ld\n", lineLength);
memcpy(memPtr, eolPos+2, fileSizeLo-lineLength);
UnmapViewOfFile(memPtr);
}
CloseHandle(memMappedHandle);
}
SetFilePointer(fileHandle, -(lineLength+2), 0, FILE_END);
SetEndOfFile(fileHandle);
CloseHandle(fileHandle);
}
}
int main()
{
const char inputFilename[] = "testInput2.txt";
testFunc(inputFilename);
return 0;
}
我想读取并删除 txt 文件的第一行(不复制,这是一个巨大的文件)。
我已经阅读了网络,但每个人都只是将所需的内容复制到一个新文件中。我做不到。
低于第一次尝试。由于没有删除任何行,此代码将陷入循环。如果代码在每次打开时删除文件的第一行,代码将到达结尾。
#include <iostream>
#include <string>
#include <fstream>
#include <boost/interprocess/sync/file_lock.hpp>
int main() {
std::string line;
std::fstream file;
boost::interprocess::file_lock lock("test.lock");
while (true) {
std::cout << "locking\n";
lock.lock();
file.open("test.txt", std::fstream::in|std::fstream::out);
if (!file.is_open()) {
std::cout << "can't open file\n";
file.close();
lock.unlock();
break;
}
else if (!std::getline(file,line)) {
std::cout << "empty file\n"; //
file.close(); // never
lock.unlock(); // reached
break; //
}
else {
// remove first line
file.close();
lock.unlock();
// do something with line
}
}
}
你想做的,确实不容易。
如果不小心打开同一个文件进行读写,最终会读到刚写的,结果不是你想要的。
就地修改文件是可行的:只需打开它,在其中查找,修改并关闭。但是,您想复制文件开头的 K
字节以外的所有文件内容。这意味着您将不得不通过 N
字节的块迭代地读取和写入整个文件。
现在一旦完成,K
字节将保留在需要删除的末尾。我认为没有办法用流来做到这一点。您可以使用 unistd.h
中的 ftruncate
或 truncate
函数,或者为此使用 Boost.Interprocess truncate
。
这里有一个例子(没有任何错误检查,我让你添加):
#include <iostream>
#include <fstream>
#include <unistd.h>
int main()
{
std::fstream file;
file.open("test.txt", std::fstream::in | std::fstream::out);
// First retrieve size of the file
file.seekg(0, file.end);
std::streampos endPos = file.tellg();
file.seekg(0, file.beg);
// Then retrieve size of the first line (a.k.a bufferSize)
std::string firstLine;
std::getline(file, firstLine);
// We need two streampos: the read one and the write one
std::streampos readPos = firstLine.size() + 1;
std::streampos writePos = 0;
// Read the whole file starting at readPos by chunks of size bufferSize
std::size_t bufferSize = 256;
char buffer[bufferSize];
bool finished = false;
while(!finished)
{
file.seekg(readPos);
if(readPos + static_cast<std::streampos>(bufferSize) >= endPos)
{
bufferSize = endPos - readPos;
finished = true;
}
file.read(buffer, bufferSize);
file.seekg(writePos);
file.write(buffer, bufferSize);
readPos += bufferSize;
writePos += bufferSize;
}
file.close();
// No clean way to truncate streams, use function from unistd.h
truncate("test.txt", writePos);
return 0;
}
我真的很想能够为文件的就地修改提供更清晰的解决方案,但我不确定是否有。
这是一个用 C 语言为 Windows 编写的解决方案。 它将立即执行并完成 700,000 行、245MB 的文件。 (0.14 秒)
基本上,我对文件进行了内存映射,这样我就可以使用用于原始内存访问的函数来访问内容。一旦文件被映射,我就使用 strchr 函数在 windows(\n 和 \r)中找到用于表示 EOL 的一对符号中的一个的位置 - 这告诉我们多长时间(以字节为单位)第一行是。
从这里开始,我只是从第二行的第一个字节 memcpy 回到内存映射区的开始(基本上是文件中的第一个字节)。
完成此操作后,文件将取消映射,内存映射文件的句柄将关闭,然后我们使用 SetEndOfFile 函数将文件的长度减少第一行的长度。当我们关闭文件时,它缩小了这个长度并且第一行消失了。
自从我刚刚创建并写入文件以来,文件就已经在内存中,这显然会稍微改变执行时间,但是 windows 缓存机制就是这里的 'culprit' - 完全相同的机制我们正在利用它来快速完成操作。
测试数据为程序源复制10万次保存为testInput2.txt(粘贴10次,select全部,复制粘贴10次-替换原来的10次,为总共 100 次 - 重复直到输出足够大。我在这里停下来是因为更多似乎让 Notepad++ 'bit' 不开心)
此程序中几乎不存在错误检查,预计输入不是 UNICODE,即 - 输入为每个字符 1 个字节。 EOL序列为0x0D, 0x0A (\r, \n)
代码:
#include <stdio.h>
#include <windows.h>
void testFunc(const char inputFilename[] )
{
int lineLength;
HANDLE fileHandle = CreateFile(
inputFilename,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_WRITE_THROUGH,
NULL
);
if (fileHandle != INVALID_HANDLE_VALUE)
{
printf("File opened okay\n");
DWORD fileSizeHi, fileSizeLo = GetFileSize(fileHandle, &fileSizeHi);
HANDLE memMappedHandle = CreateFileMapping(
fileHandle,
NULL,
PAGE_READWRITE | SEC_COMMIT,
0,
0,
NULL
);
if (memMappedHandle)
{
printf("File mapping success\n");
LPVOID memPtr = MapViewOfFile(
memMappedHandle,
FILE_MAP_ALL_ACCESS,
0,
0,
0
);
if (memPtr != NULL)
{
printf("view of file successfully created");
printf("File size is: 0x%04X%04X\n", fileSizeHi, fileSizeLo);
LPVOID eolPos = strchr((char*)memPtr, '\r'); // windows EOL sequence is \r\n
lineLength = (char*)eolPos-(char*)memPtr;
printf("Length of first line is: %ld\n", lineLength);
memcpy(memPtr, eolPos+2, fileSizeLo-lineLength);
UnmapViewOfFile(memPtr);
}
CloseHandle(memMappedHandle);
}
SetFilePointer(fileHandle, -(lineLength+2), 0, FILE_END);
SetEndOfFile(fileHandle);
CloseHandle(fileHandle);
}
}
int main()
{
const char inputFilename[] = "testInput2.txt";
testFunc(inputFilename);
return 0;
}