Làm thế nào để chuyển đến một dòng cụ thể trong một tệp văn bản lớn?


107

Có bất kỳ lựa chọn thay thế nào cho mã dưới đây không:

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

Nếu tôi đang xử lý một tệp văn bản lớn (~15MB)với các dòng không xác định nhưng có độ dài khác nhau và cần chuyển đến một dòng cụ thể mà tôi biết trước số? Tôi cảm thấy tồi tệ khi xử lý từng cái một khi tôi biết mình có thể bỏ qua ít nhất nửa đầu của tệp. Tìm kiếm giải pháp thanh lịch hơn nếu có.


Làm thế nào để bạn biết 1/2 đầu tiên của tệp không phải là một loạt "\ n" trong khi nửa sau là một dòng? Tại sao bạn cảm thấy tồi tệ về điều này?
Andrew Dalke

7
Tôi nghĩ rằng danh hiệu được gây hiểu lầm - tbh 15MB là không thực sự "tập tin văn bản rất lớn", để nói rằng ít nhất ...
PMS

Câu trả lời:


30

bộ nhớ đệm :

Các linecachemô-đun cho phép một để có được bất kỳ dòng từ một tập tin nguồn Python, trong khi cố gắng để tối ưu hóa trong nội bộ, sử dụng một bộ nhớ cache, các trường hợp phổ biến mà nhiều dòng được đọc từ một tập tin duy nhất. Điều này được sử dụng bởi tracebackmô-đun để truy xuất các dòng nguồn để đưa vào truy nguyên đã định dạng ...


164
Tôi vừa kiểm tra mã nguồn của mô-đun này: toàn bộ tệp được đọc trong bộ nhớ! Vì vậy, tôi chắc chắn sẽ loại trừ câu trả lời này với mục đích truy cập nhanh một dòng nhất định trong tệp.
MiniQuark

MiniQuark, tôi đã thử nó, nó thực sự hoạt động và rất nhanh. Tôi sẽ cần xem điều gì sẽ xảy ra nếu tôi làm việc trên hàng chục tệp cùng một lúc theo cách này, tìm hiểu xem hệ thống của tôi chết ở điểm nào.
user63503 Ngày

5
Trình quản lý bộ nhớ ảo của hệ điều hành của bạn hỗ trợ khá nhiều, vì vậy việc đọc các tệp lớn vào bộ nhớ có thể không bị chậm nếu bạn không tạo ra nhiều lỗi trang :) Ngược lại, làm điều đó theo "cách ngu ngốc" và phân bổ rất nhiều bộ nhớ có thể rất nhanh. Tôi rất thích bài viết nhà phát triển FreeBSD danish Poul-Henning Kamp về nó: queue.acm.org/detail.cfm?id=1814327
Morten Jensen

13
thử tập tin 100G, nó tệ. tôi phải sử dụng f.tell (), f.seek (), f.readline ()
whi

114

Bạn không thể tiếp tục mà không đọc tệp ít nhất một lần, vì bạn không biết vị trí ngắt dòng. Bạn có thể làm điều gì đó như:

# 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, nhưng hãy cẩn thận rằng điều này chỉ hữu ích nếu anh ấy sẽ chuyển đến một số dòng ngẫu nhiên! nhưng nếu anh ấy chỉ nhảy đến một dòng, thì điều này thật lãng phí
băm

3
+1: Ngoài ra, nếu tệp không thay đổi, chỉ mục số dòng có thể được chọn và sử dụng lại, phân bổ thêm chi phí quét tệp ban đầu.
S.Lott

OK, sau khi tôi nhảy đến đó, tôi sẽ xử lý như thế nào sau đó từng dòng một bắt đầu từ vị trí này?
user63503 Ngày

8
Một điều cần lưu ý (đặc biệt trên windows): hãy cẩn thận mở tệp ở chế độ nhị phân hoặc sử dụng offset = file.tell (). Trong chế độ văn bản trên windows, dòng sẽ ngắn hơn một byte so với độ dài thô của nó trên đĩa (\ r \ n được thay thế bằng \ n)
Brian

2
@photographer: Sử dụng read () hoặc readline (), chúng bắt đầu từ vị trí hiện tại như được đặt bởi seek.
S.Lott

22

Bạn không thực sự có nhiều lựa chọn nếu các dòng có độ dài khác nhau ... đáng buồn là bạn cần phải xử lý các ký tự kết thúc dòng để biết khi nào bạn đã chuyển sang dòng tiếp theo.

Tuy nhiên, bạn có thể tăng tốc đáng kể điều này VÀ giảm mức sử dụng bộ nhớ bằng cách thay đổi tham số cuối cùng thành "mở" thành một thứ không phải 0.

0 có nghĩa là thao tác đọc tệp không có bộ đệm, rất chậm và tốn nhiều ổ đĩa. 1 có nghĩa là tệp được lưu vào bộ đệm dòng, đây sẽ là một cải tiến. Bất kỳ thứ gì trên 1 (giả sử 8k .. tức là: 8096, hoặc cao hơn) đọc các phần của tệp vào bộ nhớ. Bạn vẫn truy cập nó thông qua for line in open(etc):, nhưng python chỉ đi một chút tại một thời điểm, loại bỏ từng đoạn trong bộ đệm sau khi nó được xử lý.


6
8K là 8192, có lẽ tốt hơn nên viết 8 << 10 để ở bên an toàn. :)
thư giãn

Bạn có tình cờ biết là kích thước bộ đệm được chỉ định trên byte không? Định dạng thích hợp là gì? Tôi có thể viết '8k' không? Hay nó phải là '8096'?
user63503 Ngày

1
HAHAHA ... phải là thứ sáu ... Tôi rõ ràng không thể làm toán. Các kích thước bộ đệm thực sự là một số nguyên thể hiện byte, do đó ghi 8192 (không phải 8096 :-)), chứ không phải là 8
Jarret Hardie

Niềm vui của tôi - hy vọng nó thành công. Trên một hệ thống hiện đại, bạn có thể tăng kích thước bộ đệm lên một chút. 8k chỉ là vật đọng lại trong ký ức của tôi vì một lý do nào đó mà tôi không thể xác định được.
Jarret Hardie

Tôi đã thực hiện một số thử nghiệm ở đây và đặt nó thành -1 (mặc định của hệ điều hành, thường là 8k, nhưng thường khó nói), có vẻ như nó sẽ nhanh như vậy. Điều đó nói rằng, một phần của điều đó có thể là tôi đang thử nghiệm trên một máy chủ ảo.
Oscar Smith

12

Tôi có thể làm hỏng bởi ram dồi dào, nhưng 15 M không phải là lớn. Đọc vào bộ nhớ với readlines() là những gì tôi thường làm với các tệp có kích thước này. Việc truy cập một dòng sau đó là điều không bình thường.


Tại sao tôi hơi do dự khi đọc toàn bộ tệp - tôi có thể có một vài trong số những quy trình đó đang chạy và nếu một tá trong số đó đọc 12 tệp 15MB mỗi tệp thì điều đó có thể không tốt. Nhưng tôi cần phải kiểm tra nó để tìm hiểu xem nó có hoạt động không. Cảm ơn bạn.
user63503 Ngày

4
Hrm, và nếu đó là tệp 1GB thì sao?
Noah

@photographer: ngay cả "một số" quy trình đọc trong các tệp 15MB cũng không thành vấn đề trên một máy hiện đại điển hình (tất nhiên, tùy thuộc vào chính xác những gì bạn đang làm với chúng).
Jacob Gabrielson

Jacob, vâng, tôi chỉ nên thử. (Các) quá trình đang / chạy trên một máy ảo trong nhiều tuần nếu vm không bị lỗi. Thật không may lần trước nó đã bị rơi sau 6 ngày. Tôi cần phải tiếp tục từ nơi nó đột ngột dừng lại. Vẫn cần phải tìm cách tìm nơi nó đã được bỏ lại.
user63503 Ngày

@Noah: nhưng không phải vậy! Tại sao bạn không đi xa hơn? Nếu tệp 128TB thì sao? Hơn nhiều hệ điều hành sẽ không thể hỗ trợ nó. Tại sao không giải quyết vấn đề khi chúng đến?
SilentGhost

7

Tôi ngạc nhiên là không ai đề cập đến islice

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

hoặc nếu bạn muốn toàn bộ phần còn lại của tệp

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

hoặc nếu bạn muốn mọi dòng khác từ tệp

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

5

Vì không có cách nào để xác định độ dài của tất cả các dòng mà không cần đọc chúng, bạn không có lựa chọn nào khác ngoài việc lặp lại tất cả các dòng trước dòng bắt đầu của mình. Tất cả những gì bạn có thể làm là làm cho nó trông đẹp mắt. Nếu tệp thực sự lớn thì bạn có thể muốn sử dụng cách tiếp cận dựa trên trình tạo:

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)

Lưu ý: chỉ số bằng 0 dựa trên cách tiếp cận này.


4

Nếu bạn không muốn đọc toàn bộ tệp trong bộ nhớ .. bạn có thể cần phải đưa ra một số định dạng khác với văn bản thuần túy.

tất nhiên tất cả phụ thuộc vào những gì bạn đang cố gắng làm và tần suất bạn sẽ chuyển qua tệp.

Ví dụ: nếu bạn sẽ chuyển đến các dòng nhiều lần trong cùng một tệp và bạn biết rằng tệp không thay đổi khi làm việc với nó, bạn có thể làm điều này:
Đầu tiên, chuyển qua toàn bộ tệp và ghi lại " tìm kiếm vị trí "của một số khóa-dòng (chẳng hạn như 1000 dòng),
Sau đó, nếu bạn muốn dòng 12005, hãy chuyển đến vị trí 12000 (mà bạn đã ghi lại) rồi đọc 5 dòng và bạn sẽ biết bạn 'đang ở dòng 12005, v.v.


3

Nếu bạn biết trước vị trí trong tệp (thay vì số dòng), bạn có thể sử dụng file.seek () để đi đến vị trí đó.

Chỉnh sửa : bạn có thể sử dụng hàm linecache.getline (tên tệp, lineno) , hàm này sẽ trả về nội dung của dòng lineno, nhưng chỉ sau khi đọc toàn bộ tệp vào bộ nhớ. Tốt nếu bạn đang truy cập ngẫu nhiên các dòng từ bên trong tệp (vì bản thân python có thể muốn thực hiện để in dấu vết) nhưng không tốt cho tệp 15MB.


Tôi chắc chắn sẽ không sử dụng linecache cho mục đích này, vì nó đọc toàn bộ tệp trong bộ nhớ trước khi trả về dòng được yêu cầu.
MiniQuark

Vâng, nó nghe có vẻ quá tốt là đúng. Tôi vẫn ước có một mô-đun để làm điều này một cách hiệu quả, nhưng có xu hướng sử dụng phương thức file.seek () để thay thế.
Noah

3

Điều gì tạo ra tệp bạn muốn xử lý? Nếu đó là thứ nằm trong tầm kiểm soát của bạn, bạn có thể tạo chỉ mục (dòng nào ở vị trí nào.) Tại thời điểm tệp được thêm vào. Tệp chỉ mục có thể có kích thước dòng cố định (số đệm khoảng trắng hoặc 0) và chắc chắn sẽ nhỏ hơn. Và do đó có thể được đọc và xử lý qucikly.

  • Bạn muốn dòng nào ?.
  • Tính toán độ lệch byte của số dòng tương ứng trong tệp chỉ mục (có thể vì kích thước dòng của tệp chỉ mục là không đổi).
  • Sử dụng seek hoặc bất cứ thứ gì để nhảy trực tiếp để lấy dòng từ tệp chỉ mục.
  • Phân tích cú pháp để có được phần bù byte cho dòng tương ứng của tệp thực tế.

3

Tôi đã gặp vấn đề tương tự (cần truy xuất từ ​​dòng tệp lớn cụ thể).

Chắc chắn, tôi luôn có thể chạy qua tất cả các bản ghi trong tệp và dừng nó khi bộ đếm sẽ bằng dòng đích, nhưng nó không hoạt động hiệu quả trong trường hợp bạn muốn lấy số nhiều hàng cụ thể. Điều đó khiến vấn đề chính được giải quyết - cách xử lý trực tiếp đến nơi cần thiết của tệp.

Tôi tìm ra quyết định tiếp theo: Đầu tiên tôi hoàn thành từ điển với vị trí bắt đầu của mỗi dòng (khóa là số dòng và giá trị - độ dài tích lũy của các dòng trước đó).

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

cuối cùng, chức năng nhắm mục tiêu:

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

t.seek (line_number) - lệnh thực hiện việc cắt bớt tệp lên đến đầu dòng. Vì vậy, nếu bạn cam kết dòng đọc tiếp theo - bạn có được dòng mục tiêu của mình.

Sử dụng cách tiếp cận như vậy tôi đã tiết kiệm đáng kể thời gian.


3

Bạn có thể sử dụng mmap để tìm độ lệch của các dòng. MMap dường như là cách nhanh nhất để xử lý tệp

thí dụ:

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

sau đó sử dụng f.seek (hiệu số) để di chuyển đến dòng bạn cần


2

Bản thân các dòng có chứa bất kỳ thông tin chỉ mục nào không? Nếu nội dung của mỗi dòng giống như " <line index>:Data", thì seek()phương pháp này có thể được sử dụng để thực hiện tìm kiếm nhị phân thông qua tệp, ngay cả khi số lượng Datalà thay đổi. Bạn sẽ tìm đến điểm giữa của tệp, đọc một dòng, kiểm tra xem chỉ mục của nó cao hơn hay thấp hơn chỉ số bạn muốn, v.v.

Nếu không, điều tốt nhất bạn có thể làm là readlines(). Nếu bạn không muốn đọc tất cả 15MB, bạn có thể sử dụng sizehintđối số để thay thế ít nhất nhiều readline()s bằng một số lượng lệnh gọi đến ít hơn readlines().


2

Nếu bạn đang xử lý một tệp văn bản và dựa trên hệ thống linux , bạn có thể sử dụng các lệnh linux.
Đối với tôi, điều này hoạt động tốt!

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)

tất nhiên nó không tương thích với windows hoặc một số loại shell linux không hỗ trợ head / tail.
Wizmann

Điều này có nhanh hơn thực hiện bằng Python không?
Shamoon

Điều này có thể nhận được nhiều dòng?
Shamoon

1

Đây là một ví dụ sử dụng 'readlines (sizehint)' để đọc một đoạn dòng cùng một lúc. DNS đã chỉ ra giải pháp đó. Tôi đã viết ví dụ này bởi vì các ví dụ khác ở đây là hướng dòng đơn.

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

Không có câu trả lời nào đặc biệt thỏa đáng, vì vậy đây là một đoạn mã nhỏ để trợ giúp.

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()

Ví dụ sử dụng:

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!

Điều này liên quan đến việc thực hiện nhiều lần tìm kiếm tệp, nhưng hữu ích cho những trường hợp bạn không thể chứa toàn bộ tệp trong bộ nhớ. Nó thực hiện một lần đọc ban đầu để lấy các vị trí dòng (vì vậy nó đọc toàn bộ tệp, nhưng không giữ tất cả trong bộ nhớ), và sau đó mỗi lần truy cập thực hiện một tệp tìm kiếm thực tế.

Tôi cung cấp đoạn mã trên theo giấy phép MIT hoặc Apache theo quyết định của người dùng.


-1

Có thể sử dụng hàm này để trả về dòng n:

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

Logic này không làm việc nếu có những dòng sản phẩm nào liên tục, fi.next () sẽ bỏ qua tất cả các dòng sản phẩm nào cùng một lúc, nếu không nó tốt :)
Anvesh Yalamarthy

OP không đề cập đến việc các dòng có các dòng có ngắt dòng không chuẩn. Trong trường hợp đó, bạn phải phân tích cú pháp mỗi dòng với ít nhất một câu lệnh if cho các ngắt dòng từng phần.
đóng cửa
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.