pitón de seek
va a un byte desplazamiento en un archivo, no a una línea offset, simplemente porque eso es los sistemas operativos modernos camino y sus sistemas de ficheros de trabajo - el OS/FS simplemente no registrar o recordar "offsets de línea" de cualquier manera, y no hay forma de que Python (o cualquier otro idioma) los adivine mágicamente. Cualquier operación que pretenda "ir a una línea" inevitablemente tendrá que "recorrer el archivo" (debajo de las coberturas) para hacer la asociación entre los números de línea y las compensaciones de bytes.
Si está de acuerdo con eso y lo quiere ocultar de su vista, la solución es el módulo de biblioteca estándar linecache - pero el rendimiento no será mejor que el del código que podría escribir usted mismo.
Si tiene que leer desde el mismo archivo de gran tamaño en múltiples ocasiones, una gran optimización sería correr vez en ese archivo grande de un script que se acumula y guarda en el disco el número de línea - a - Byte correspondencia offset (técnicamente un archivo auxiliar de "índice"); luego, todas las ejecuciones sucesivas (hasta que el archivo grande cambie) podrían usar muy rápidamente el archivo de índice para navegar con un rendimiento muy alto a través del archivo grande. ¿Es este tu caso de uso ...?
Editar: ya que aparentemente esto puede aplicarse - aquí está la idea general (sin pruebas cuidadosas, comprobación de errores u optimización ;-).Para hacer el índice, utilice makeindex.py
, de la siguiente manera:
import array
import sys
BLOCKSIZE = 1024 * 1024
def reader(f):
blockstart = 0
while True:
block = f.read(BLOCKSIZE)
if not block: break
inblock = 0
while True:
nextnl = block.find(b'\n', inblock)
if nextnl < 0:
blockstart += len(block)
break
yield nextnl + blockstart
inblock = nextnl + 1
def doindex(fn):
with open(fn, 'rb') as f:
# result format: x[0] is tot # of lines,
# x[N] is byte offset of END of line N (1+)
result = array.array('L', [0])
result.extend(reader(f))
result[0] = len(result) - 1
return result
def main():
for fn in sys.argv[1:]:
index = doindex(fn)
with open(fn + '.indx', 'wb') as p:
print('File', fn, 'has', index[0], 'lines')
index.tofile(p)
main()
y después de usarlo, por ejemplo, la siguiente useindex.py
:
import array
import sys
def readline(n, f, findex):
f.seek(findex[n] + 1)
bytes = f.read(findex[n+1] - findex[n])
return bytes.decode('utf8')
def main():
fn = sys.argv[1]
with open(fn + '.indx', 'rb') as f:
findex = array.array('l')
findex.fromfile(f, 1)
findex.fromfile(f, findex[0])
findex[0] = -1
with open(fn, 'rb') as f:
for n in sys.argv[2:]:
print(n, repr(readline(int(n), f, findex)))
main()
He aquí un ejemplo (en mi portátil lenta):
$ time py3 makeindex.py kjv10.txt
File kjv10.txt has 100117 lines
real 0m0.235s
user 0m0.184s
sys 0m0.035s
$ time py3 useindex.py kjv10.txt 12345 98765 33448
12345 '\r\n'
98765 '2:6 But this thou hast, that thou hatest the deeds of the\r\n'
33448 'the priest appointed officers over the house of the LORD.\r\n'
real 0m0.049s
user 0m0.028s
sys 0m0.020s
$
El archivo de ejemplo es un archivo de texto plano de la Biblia king James':
$ wc kjv10.txt
100117 823156 4445260 kjv10.txt
100K líneas, 4.4 MB, como puede ver; esto toma alrededor de un cuarto de segundo para indexar y 50 milisegundos para leer e imprimir tres líneas aleatorias (sin duda, esto puede acelerarse enormemente con una optimización más cuidadosa y una máquina mejor). El índice en la memoria (y también en el disco) toma 4 bytes por línea del archivo de texto que se indexa, y el rendimiento debe escalar de una manera perfectamente lineal, así que si tuviera alrededor de 100 millones de líneas, 4.4 GB, esperaría alrededor de 4-5 minutos para construir el índice, un minuto para extraer e imprimir tres líneas arbitrarias (y los 400 MB de RAM tomados para el índice no deberían ser inconvenientes ni siquiera para una máquina pequeña; incluso mi pequeña laptop lenta tiene 2GB después de todo ;-).
También puede ver que (por velocidad y conveniencia) trato el archivo como binario (y supongo que la codificación utf8 - funciona con cualquier subconjunto como ASCII, por supuesto, por ejemplo, que el archivo de texto KJ es ASCII) y no molesta colapsar \r\n
en un solo carácter si eso es lo que el archivo tiene como terminador de línea (es bastante trivial hacer eso después de leyendo cada línea si quieres).
estaría feliz si hubiera una solución para el caso cuando tengo el mismo número de enteros en cada uno en el archivo. –