为什么没有换行符读取文件会更快?

时间:2017-09-25 23:22:28

标签: python python-3.x file-io

在Python 3.6中,如果有换行符,则需要更长的时间来读取文件。如果我有两个文件,一个有换行符,另一个没有换行符(否则它们有相同的文本),那么带换行符的文件大约需要100-200%的时间来读取。我提供了一个具体的例子。

步骤1:创建文件

sizeMB = 128
sizeKB = 1024 * sizeMB

with open(r'C:\temp\bigfile_one_line.txt', 'w') as f:
    for i in range(sizeKB):
        f.write('Hello World!\t'*73)  # There are roughly 73 phrases in one KB

with open(r'C:\temp\bigfile_newlines.txt', 'w') as f:
    for i in range(sizeKB):  
        f.write('Hello World!\n'*73)

步骤2:使用一行和时间性能

读取文件

IPython的

%%timeit
with open(r'C:\temp\bigfile_one_line.txt', 'r') as f:
    text = f.read()

输出

1 loop, best of 3: 368 ms per loop

步骤#3:读取具有许多行和时间性能的文件

IPython的

%%timeit
with open(r'C:\temp\bigfile_newlines.txt', 'r') as f:
    text = f.read()

输出

1 loop, best of 3: 589 ms per loop

这只是一个例子。我已经针对很多不同的情况对它进行了测试,他们做了同样的事情:

  1. 不同文件大小从1MB到2GB
  2. 使用file.readlines()而不是file.read()
  3. 在单行文件中使用空格而不是制表符(' \ t')(即“#Hello Hello!'”)
  4. 我的结论是,带有新行字符(' \ n')的文件比没有它们的文件需要更长的时间。但是,我希望所有角色都被视为相同。在读取大量文件时,这会对性能产生重要影响。 有谁知道为什么会这样?

    我正在使用Python 3.6.1,Anaconda 4.3.24和Windows 10.

3 个答案:

答案 0 :(得分:10)

当您在文本模式下使用Python打开文件(默认设置)时,它会使用它所调用的文件"通用换行符" (随PEP 278一起介绍,但稍后随着Python 3的发布而有所改变)。通用换行符的含义是,无论文件中使用何种换行符,您都只能在Python中看到\n。因此,包含foo\nbar的文件与包含foo\r\nbarfoo\rbar的文件相同(因为\n\r\n\r都是行某些操作系统上使用的结束约定。)

提供支持的逻辑可能是导致性能差异的原因。即使文件中的\n字符没有被转换,代码也需要比非换行符更仔细地检查它们。

如果您在二进制模式下打开文件而没有提供此类新行支持,我怀疑您看到的性能差异将会消失。您还可以在Python 3中将newline参数传递给open,这可能具有多种含义,具体取决于您给出的确切值。我不知道任何特定值会对性能产生什么影响,但是如果您看到的性能差异对您的程序真正重要,则可能值得测试。我尝试传递newline=""newline="\n"(或者您平台的传统线路结尾)。

答案 1 :(得分:5)

  

但是,我希望所有角色都被视为相同。

嗯,他们不是。换行是特别的。

换行符并不总是表示为\n。原因很长,可以追溯到早期的物理电传打字机,我不会进入这里,但故事结束的地方是Windows使用\r\n,Unix使用{{1}和经典Mac OS曾经使用过\n

如果您以文字模式打开文件,文件使用的换行符会在您阅读时翻译为\r\n将转换为您的操作系统行写作时打破常规。在大多数编程语言中,这是通过操作系统级代码动态处理的,并且相当便宜,但Python以不同的方式做事。

Python有一个名为universal newlines的功能,无论您使用什么操作系统,它都会尝试处理所有换行符约定。即使文件包含\n\r\n换行符的混合,Python也会识别所有换行符并将其转换为\r\n。除非您使用\n参数newline配置特定的行结束约定,否则Python 3中默认启用通用换行符。

在通用换行模式下,文件实现必须以二进制模式读取文件,检查open个字符的内容,

construct a new string object with line endings translated

如果找到\r\n\r行结尾。如果它只找到\r\n个结尾,或者根本找不到行结尾,则不需要执行转换传递或构造新的字符串对象。

构造新字符串并翻译行结尾需要时间。使用选项卡读取文件,Python不必执行翻译。

答案 2 :(得分:3)

在Windows上,以文本模式打开时,写入时会将'\n'个字符转换为'\r\n',而当读取时则相反。

所以,我做了一些实验。我现在在MacOS上,所以我的“原生”行结尾是'\n',所以我为你做了类似的测试,除了使用非原生的Windows行结尾:

sizeMB = 128
sizeKB = 1024 * sizeMB

with open(r'bigfile_one_line.txt', 'w') as f:
    for i in range(sizeKB):
        f.write('Hello World!!\t'*73)  # There are roughly 73 phrases in one KB

with open(r'bigfile_newlines.txt', 'w') as f:
    for i in range(sizeKB):
        f.write('Hello World!\r\n'*73)

结果:

In [4]: %%timeit
   ...: with open('bigfile_one_line.txt', 'r') as f:
   ...:     text = f.read()
   ...:
1 loop, best of 3: 141 ms per loop

In [5]: %%timeit
   ...: with open('bigfile_newlines.txt', 'r') as f:
   ...:     text = f.read()
   ...:
1 loop, best of 3: 543 ms per loop

In [6]: %%timeit
   ...: with open('bigfile_one_line.txt', 'rb') as f:
   ...:     text = f.read()
   ...:
10 loops, best of 3: 76.1 ms per loop

In [7]: %%timeit
   ...: with open('bigfile_newlines.txt', 'rb') as f:
   ...:     text = f.read()
   ...:
10 loops, best of 3: 77.4 ms per loop

与您的非常相似,请注意,当我以二进制模式打开时,性能差异会消失。好的,如果相反,我使用* nix line-endings呢?

with open(r'bigfile_one_line_nix.txt', 'w') as f:
    for i in range(sizeKB):
        f.write('Hello World!\t'*73)  # There are roughly 73 phrases in one KB

with open(r'bigfile_newlines_nix.txt', 'w') as f:
    for i in range(sizeKB):
        f.write('Hello World!\n'*73)

使用这些新文件的结果:

In [11]: %%timeit
    ...: with open('bigfile_one_line_nix.txt', 'r') as f:
    ...:     text = f.read()
    ...:
10 loops, best of 3: 144 ms per loop

In [12]: %%timeit
    ...: with open('bigfile_newlines_nix.txt', 'r') as f:
    ...:     text = f.read()
    ...:
10 loops, best of 3: 138 ms per loop

啊哈!性能差异消失了!所以是的,我认为使用非原生的行结尾会影响性能,这对于文本模式的行为是有意义的。