거대한 텍스트 파일에서 특정 줄로 이동하는 방법은 무엇입니까?


107

아래 코드에 대한 대안이 있습니까?

startFromLine = 141978 # or whatever line I need to jump to

urlsfile = open(filename, "rb", 0)

linesCounter = 1

for line in urlsfile:
    if linesCounter > startFromLine:
        DoSomethingWithThisLine(line)

    linesCounter += 1

(~15MB)알 수 없지만 길이가 다른 줄이 있는 대용량 텍스트 파일 을 처리하고 있고 미리 알고있는 특정 줄로 이동해야하는 경우? 적어도 파일의 전반부를 무시할 수 있다는 것을 알았을 때 하나씩 처리함으로써 기분이 좋지 않습니다. 더 우아한 솔루션을 찾고 있다면.


파일의 첫 번째 1/2이 "\ n"무리가 아니라 두 번째 절반이 한 줄이라는 것을 어떻게 알 수 있습니까? 왜 이것에 대해 기분이 좋지 않습니까?
Andrew Dalke

7
제목이 오해의 소지가 있다고 생각합니다. tbh 15MB는 정말 "거대한 텍스트 파일"이 아닙니다 ...
pms

답변:


30

라인 캐시 :

linecache모듈은 하나의 파일에서 여러 줄을 읽는 일반적인 경우 인 캐시를 사용하여 내부적으로 최적화를 시도하면서 Python 소스 파일에서 모든 줄을 가져올 수 있습니다. 이는 traceback모듈이 형식화 된 트레이스 백에 포함 할 소스 행을 검색 하는 데 사용됩니다 .


164
방금이 모듈의 소스 코드를 확인했습니다. 전체 파일을 메모리에서 읽습니다! 그래서 나는 파일의 주어진 줄에 빠르게 접근하기 위해이 대답을 확실히 배제 할 것입니다.
MiniQuark

MiniQuark, 나는 그것을 시도했고, 실제로 작동하고, 정말 빨리. 이런 식으로 동시에 수십 개의 파일을 작업하면 어떻게되는지 확인하고 시스템이 종료되는 시점을 찾아야합니다.
user63503

5
당신의 OS의 가상 메모리 관리자는 꽤 도움이되므로 많은 페이지 폴트를 생성하지 않는다면 큰 파일을 메모리로 읽는 것이 느리지 않을 수 있습니다. :) 반대로, "어리석은 방법"으로하고 많은 것을 할당합니다. 메모리가 엄청나게 빠를 수 있습니다. 나는 그것에 덴마크의 FreeBSD 개발자 폴 헤닝 캠프의 기사를 즐겼다 : queue.acm.org/detail.cfm?id=1814327
모르 텐 젠슨에게

13
100G 파일을 사용해보십시오. f.tell (), f.seek (), f.readline ()을 사용해야합니다
whi

114

줄 바꿈이 어디에 있는지 모르기 때문에 적어도 한 번 파일을 읽지 않고는 앞으로 이동할 수 없습니다. 다음과 같이 할 수 있습니다.

# Read in the file once and build a list of line offsets
line_offset = []
offset = 0
for line in file:
    line_offset.append(offset)
    offset += len(line)
file.seek(0)

# Now, to skip to line n (with the first line being line 0), just do
file.seek(line_offset[n])

2
+1, 그러나 이것은 그가 여러 개의 임의의 라인으로 점프 할 때만 유용합니다! 하지만 그가 한 줄로만 점프한다면 이것은 낭비입니다
hasen

3
+1 : 또한 파일이 변경되지 않으면 줄 번호 색인을 피클하고 재사용 할 수있어 파일 스캔의 초기 비용을 추가로 상환 할 수 있습니다.
S.Lott

좋아, 내가 거기로 점프 한 후이 위치에서 시작하여 줄 단위로 어떻게 처리할까요?
user63503

8
한 가지주의 할 점은 (특히 Windows에서) 바이너리 모드로 파일을 열거 나 offset = file.tell ()을 사용하는 것입니다. Windows의 텍스트 모드에서 행은 디스크의 원시 길이보다 바이트가 더 짧습니다 (\ r \ n 대체 : \ n)
Brian

2
@photographer : read () 또는 readline ()을 사용합니다. 탐색에 의해 설정된 현재 위치에서 시작합니다.
S.Lott

22

줄의 길이가 다른 경우에는 옵션이 그렇게 많지 않습니다. 슬프게도 다음 줄로 진행할 때를 알기 위해 줄 끝 문자를 처리해야합니다.

그러나 마지막 매개 변수를 "open"으로 변경하여 0이 아닌 것으로 변경함으로써 속도를 크게 높이고 메모리 사용량을 줄일 수 있습니다.

0은 파일 읽기 작업이 버퍼링되지 않음을 의미하며 이는 매우 느리고 디스크 집약적입니다. 1은 파일이 라인 버퍼링되어 개선되었음을 의미합니다. 1 이상 (예 : 8k .. 즉 : 8096 이상)은 파일 청크를 메모리로 읽습니다. 여전히를 통해 액세스 for line in open(etc):하지만 파이썬은 한 번에 조금만 이동하여 처리 된 각 버퍼링 된 청크를 버립니다.


6
8K는 8192이며, 안전한 편이 되려면 8 << 10을 쓰는 것이 좋습니다. :)
언 와인드

혹시 버퍼 크기가 바이트에 지정되어 있는지 알고 있습니까? 적절한 형식은 무엇입니까? '8k'를 쓸 수 있을까요? 아니면 '8096'이어야합니까?
user63503

1
HAHAHA ... 금요일이 틀림 없어 ... 나는 분명히 수학을 못해. 버퍼 크기는 실제로 바이트를 표현하는 정수이므로 8 대신 8192 (8096 아님 :-))를 작성하십시오.
Jarret Hardie

내 기쁨-잘 되길 바랍니다. 최신 시스템에서는 버퍼 크기를 상당히 늘릴 수 있습니다. 8k는 내가 식별 할 수없는 어떤 이유로 내 기억의 홀드 오버 일뿐입니다.
Jarret Hardie

여기에서 몇 가지 테스트를 수행했으며 -1 (기본값은 8k이지만 종종 말하기 어렵습니다)으로 설정하면 가능한 한 빠른 것 같습니다. 즉, 그 중 일부는 가상 서버에서 테스트하고 있다는 것입니다.
Oscar Smith

12

나는 아마도 풍부한 램에 의해 망쳐 졌을 것입니다. 그러나 15M은 크지 않습니다. 로 메모리로 읽는 readlines() 것은 일반적으로이 크기의 파일로 수행하는 작업입니다. 그 후 라인에 액세스하는 것은 사소합니다.


전체 파일을 읽는 데 약간 주저하는 이유-여러 프로세스가 실행 중일 수 있으며 그중 12 개가 각각 15MB의 파일 12 개를 읽는다면 좋지 않을 수 있습니다. 하지만 작동하는지 확인하기 위해 테스트해야합니다. 감사합니다.
user63503

4
흠, 1GB 파일이면 어떻게 되나요?
Noah

@photographer : 15MB 파일을 읽는 "여러"프로세스조차도 일반적인 최신 컴퓨터에서는 중요하지 않습니다 (물론 정확히 무엇을 사용하는지에 따라 다름).
Jacob Gabrielson

제이콥, 그래, 그냥 해봐야 겠어 vm이 충돌하지 않으면 프로세스가 몇 주 동안 가상 머신에서 실행 중입니다. 불행히도 지난번에는 6 일 후에 충돌했습니다. 갑자기 멈춘 곳에서 계속해야합니다. 여전히 그것이 어디에 남아 있는지 찾는 방법을 알아 내야합니다.
user63503

@ 노아 :하지만 그렇지 않습니다! 더 나아 가지 그래? 128TB 파일이면 어떨까요? 많은 OS가 지원하지 못할 것입니다. 그들이 올 때 문제를 해결하지 않는 이유는 무엇입니까?
SilentGhost

7

아무도 islice를 언급하지 않았다는 것이 놀랍습니다.

line = next(itertools.islice(Fhandle,index_of_interest,index_of_interest+1),None) # just the one line

또는 나머지 파일 전체를 원하는 경우

rest_of_file = itertools.islice(Fhandle,index_of_interest)
for line in rest_of_file:
    print line

또는 파일에서 다른 모든 줄을 원할 경우

rest_of_file = itertools.islice(Fhandle,index_of_interest,None,2)
for odd_line in rest_of_file:
    print odd_line

5

모든 줄을 읽지 않고 길이를 결정할 수있는 방법이 없기 때문에 출발 선 앞에서 모든 줄을 반복 할 수밖에 없습니다. 당신이 할 수있는 일은 멋지게 보이게하는 것입니다. 파일이 정말 큰 경우 생성기 기반 접근 방식을 사용할 수 있습니다.

from itertools import dropwhile

def iterate_from_line(f, start_from_line):
    return (l for i, l in dropwhile(lambda x: x[0] < start_from_line, enumerate(f)))

for line in iterate_from_line(open(filename, "r", 0), 141978):
    DoSomethingWithThisLine(line)

참고 :이 접근 방식에서는 인덱스가 0입니다.


4

메모리에있는 전체 파일을 읽지 않으려면 .. 일반 텍스트 이외의 다른 형식이 필요할 수 있습니다.

물론 그것은 당신이 무엇을하려고하는지, 얼마나 자주 파일을 건너 뛸 것인지에 달려 있습니다.

예를 들어 같은 파일에서 여러 줄로 건너 뛰고 작업하는 동안 파일이 변경되지 않는다는 것을 알고 있다면 다음과 같이 할 수 있습니다.
먼저 전체 파일을 통과하고 " 몇 개의 키-라인 번호 (예 : 1000 줄)의 탐색 위치 "를 입력 한
다음 12005 줄을 원할 경우 12000 (기록한) 위치로 이동 한 다음 5 줄을 읽으면 알 수 있습니다. 12005 줄에 있습니다.


3

파일의 위치 (줄 번호가 아니라)를 미리 알고 있다면 file.seek () 를 사용 하여 해당 위치로 이동할 수 있습니다 .

편집 : lineno 라인의 내용을 반환 하는 linecache.getline (filename, lineno) 함수를 사용할 수 있지만 전체 파일을 메모리로 읽은 후에 만 ​​가능합니다. 파일 내에서 무작위로 행에 액세스하는 경우 (파이썬 자체가 역 추적을 인쇄하려고 할 수 있으므로) 좋지만 15MB 파일에는 좋지 않습니다.


요청 된 줄을 반환하기 전에 메모리에서 전체 파일을 읽으므로이 목적으로 linecache를 사용하지 않을 것입니다.
MiniQuark

네, 너무 좋아서 사실이 아니 었습니다. 이 작업을 효율적으로 수행 할 수있는 모듈이 있었으면하지만 대신 file.seek () 메서드를 사용하는 경향이 있습니다.
Noah

3

처리하려는 파일을 생성하는 것은 무엇입니까? 그것이 당신의 통제하에 있다면, 파일이 추가 될 때 인덱스 (어떤 줄이 어느 위치에 있는지)를 생성 할 수 있습니다. 인덱스 파일은 고정 된 라인 크기 (공백 또는 0으로 채워진 숫자) 일 수 있으며 확실히 더 작을 것입니다. 따라서 빠르게 읽고 처리 할 수 ​​있습니다.

  • 어떤 라인을 원하십니까?.
  • 인덱스 파일에서 해당 라인 번호의 바이트 오프셋을 계산합니다 (인덱스 파일의 라인 크기가 일정하기 때문에 가능함).
  • 검색 또는 무엇이든 직접 점프하여 색인 파일에서 줄을 가져옵니다.
  • 실제 파일의 해당 줄에 대한 바이트 오프셋을 얻기 위해 구문 분석합니다.

3

나는 같은 문제가 있었다 (거대한 파일 특정 라인에서 검색해야 함).

분명히 파일의 모든 레코드를 실행할 때마다 카운터가 목표 행과 같을 때 중지 할 수 있지만 특정 행을 여러 개 얻으려는 경우 효과적으로 작동하지 않습니다. 이로 인해 주요 문제가 해결되었습니다-필요한 파일 위치를 직접 처리하는 방법.

다음 결정을 찾았습니다. 먼저 각 줄의 시작 위치로 사전을 완성했습니다 (키는 줄 번호, 값-이전 줄의 누적 길이).

t = open(file,’r’)
dict_pos = {}

kolvo = 0
length = 0
for each in t:
    dict_pos[kolvo] = length
    length = length+len(each)
    kolvo = kolvo+1

궁극적으로 조준 기능 :

def give_line(line_number):
    t.seek(dict_pos.get(line_number))
    line = t.readline()
    return line

t.seek (line_number) – 줄 시작까지 파일 정리를 실행하는 명령입니다. 따라서 다음에 readline을 커밋하면 목표 행을 얻습니다.

이러한 접근 방식을 사용하여 상당한 시간을 절약했습니다.


3

mmap을 사용하여 선의 오프셋을 찾을 수 있습니다. MMap은 파일을 처리하는 가장 빠른 방법 인 것 같습니다.

예:

with open('input_file', "r+b") as f:
    mapped = mmap.mmap(f.fileno(), 0, prot=mmap.PROT_READ)
    i = 1
    for line in iter(mapped.readline, ""):
        if i == Line_I_want_to_jump:
            offsets = mapped.tell()
        i+=1

그런 다음 f.seek (offsets)를 사용하여 필요한 행으로 이동하십시오.


2

행 자체에 색인 정보가 포함되어 있습니까? 각 줄의 내용이 " <line index>:Data"와 seek()같으면 양 Data이 가변적 이더라도 파일을 통해 이진 검색을 수행하는 데 접근 방식을 사용할 수 있습니다 . 파일의 중간 지점을 찾고, 한 줄을 읽고, 색인이 원하는 것보다 높거나 낮은 지 확인하는 등의 작업을 수행합니다.

그렇지 않으면 할 수있는 최선의 방법은 readlines(). 15MB를 모두 읽지 않으려면 sizehint인수를 사용하여 최소한 많은 readline()s를 readlines().


2

linux system 기반 의 텍스트 파일을 다루는 경우 linux 명령을 사용할 수 있습니다. 나를 위해 이것은 잘 작동했습니다!

import commands

def read_line(path, line=1):
    return commands.getoutput('head -%s %s | tail -1' % (line, path))

line_to_jump = 141978
read_line("path_to_large_text_file", line_to_jump)

물론 헤드 / 테일을 지원하지 않는 윈도우 나 리눅스 쉘과는 호환되지 않습니다.
Wizmann

이것이 파이썬에서하는 것보다 빠릅니까?
Shamoon

여러 줄을 얻을 수 있습니까?
Shamoon

1

다음은 'readlines (sizehint)'를 사용하여 한 번에 한 줄씩 읽는 예입니다. DNS는 그 해결책을 지적했습니다. 여기에있는 다른 예제는 한 줄로되어 있기 때문에이 예제를 작성했습니다.

def getlineno(filename, lineno):
    if lineno < 1:
        raise TypeError("First line is line 1")
    f = open(filename)
    lines_read = 0
    while 1:
        lines = f.readlines(100000)
        if not lines:
            return None
        if lines_read + len(lines) >= lineno:
            return lines[lineno-lines_read-1]
        lines_read += len(lines)

print getlineno("nci_09425001_09450000.smi", 12000)

0

특히 만족스러운 답변은 없으므로 여기에 도움이 될 작은 스 니펫이 있습니다.

class LineSeekableFile:
    def __init__(self, seekable):
        self.fin = seekable
        self.line_map = list() # Map from line index -> file position.
        self.line_map.append(0)
        while seekable.readline():
            self.line_map.append(seekable.tell())

    def __getitem__(self, index):
        # NOTE: This assumes that you're not reading the file sequentially.  
        # For that, just use 'for line in file'.
        self.fin.seek(self.line_map[index])
        return self.fin.readline()

사용 예 :

In: !cat /tmp/test.txt

Out:
Line zero.
Line one!

Line three.
End of file, line four.

In:
with open("/tmp/test.txt", 'rt') as fin:
    seeker = LineSeekableFile(fin)    
    print(seeker[1])
Out:
Line one!

여기에는 많은 파일 검색이 포함되지만 전체 파일을 메모리에 맞출 수없는 경우에 유용합니다. 라인 위치를 얻기 위해 한 번의 초기 읽기를 수행하고 (따라서 전체 파일을 읽지 만 메모리에 모두 보관하지는 않음) 각 액세스는 사실을 추적하여 파일을 찾습니다.

사용자의 재량에 따라 MIT 또는 Apache 라이선스에 따라 위의 스 니펫을 제공합니다.


-1

이 함수를 사용하여 라인 n을 반환 할 수 있습니다.

def skipton(infile, n):
    with open(infile,'r') as fi:
        for i in range(n-1):
            fi.next()
        return fi.next()

연속 빈 라인 fi.next () 한 번에 모든 빈 줄을 건너 뛰고있을 경우이 논리는 좋은 :) 그렇지 않으면 일을하지 않습니다
Anvesh Yalamarthy

OP는 줄에 비표준 줄 바꿈이있는 줄이 있다고 언급하지 않습니다. 이 경우 부분 줄 바꿈에 대해 하나 이상의 if 문을 사용하여 각 줄을 구문 분석해야합니다.
ksed
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.