2009-03-06 64 views
93

Có bất kỳ lựa chọn thay thế cho mã bên dưới:Làm cách nào để chuyển đến một dòng cụ thể trong một tệp văn bản lớn?

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 xử lý một tập tin văn bản rất lớn (~15MB) với dòng thời gian vô danh nhưng khác nhau, và cần phải chuyển đến một dòng cụ thể mà số tôi biết trước ? Tôi cảm thấy xấu khi xử lý chúng từng người một khi tôi biết tôi có thể bỏ qua ít nhất nửa đầu của tập tin. Tìm kiếm giải pháp thanh lịch hơn nếu có.

+0

Làm cách nào để bạn biết 1/2 tệp đầu tiên không phải là một loạt các "\ n" trong khi nửa thứ hai là một dòng đơn? Tại sao bạn cảm thấy xấu về điều này? –

+4

Tôi nghĩ rằng tiêu đề là gây hiểu nhầm - tbh 15MB không thực sự là "tệp văn bản lớn", để nói rằng ít nhất ... – pms

Trả lời

26

linecache:

Module linecache 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. Này được sử dụng bởi module traceback để lấy dòng nguồn để đưa vào traceback định dạng ...

+127

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 cho mục đích truy cập nhanh vào một dòng nhất định trong một tệp. – MiniQuark

+0

MiniQuark, tôi đã thử nó, nó thực sự hoạt động, và thực sự nhanh chóng. Tôi sẽ cần phải xem những gì sẽ xảy ra nếu tôi làm việc trên một tá các tập tin cùng một lúc theo cách này, tìm hiểu tại thời điểm hệ thống của tôi chết. – user63503

+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 chậm nếu bạn không tạo ra nhiều lỗi trang :) Ngược lại, thực hiện "cách ngu ngốc" và phân bổ nhiều và rất nhiều bộ nhớ có thể được nhanh chóng blazingly. Tôi rất thích bài viết của nhà phát triển danish FreeBSD Poul-Henning Kamp về nó: https://queue.acm.org/detail.cfm?id=1814327 –

2

Nếu bạn biết trước các vị trí trong tập tin (chứ không phải số dòng), bạn có thể sử dụng file.seek() đến vị trí đó.

Sửa: bạn có thể sử dụng chức năng linecache.getline(filename, lineno), mà sẽ trở lại với nội dung của dòng lineno, nhưng chỉ sau khi đọc toàn bộ tập tin 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 (như chính python có thể muốn thực hiện để in một dấu vết truy nguyên) nhưng không tốt cho tệp 15MB.

+0

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ả lại dòng yêu cầu. – MiniQuark

+0

Vâng, nó có vẻ quá tốt là đúng. Tôi vẫn muốn 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

12

Tôi có thể bị hư hỏng do ram phong phú, nhưng 15 triệu không 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. Truy cập một dòng sau đó là tầm thường.

+0

Tại sao tôi hơi lưỡng lự khi đọc toàn bộ tập tin - tôi có thể có một vài trong số các tiến trình đang chạy, và nếu hàng chục người đọc 12 tập tin 15MB thì nó 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ó hiệu quả không. Cảm ơn bạn. – user63503

+3

Hrm và nếu đó là tệp 1 GB? – Noah

+0

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

90

Bạn không thể nhảy lên trước mà không đọc trong 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]) 
+1

+1 Tôi thích điều này! Tôi có thể cố gắng bọc nó trong một người trợ giúp tốt. – MiniQuark

+2

+1, nhưng hãy cẩn thận rằng điều này chỉ hữu ích nếu anh ta sẽ nhảy đến một số dòng ngẫu nhiên! nhưng nếu anh ta chỉ nhảy vào một dòng, thì điều này là lãng phí – hasen

+2

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

19

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

Bạn có thể, tuy nhiên, tốc độ đột ngột này lên và giảm sử dụng bộ nhớ bằng cách thay đổi các tham số cuối cùng để "mở" một cái gì đó không 0.

0 có nghĩa là hoạt động tập đọc là không có bộ đệm, rất chậm và đĩa chuyên sâu. 1 có nghĩa là các tập tin là dòng đệm, đó sẽ là một sự cải tiến. Bất cứ điều gì trên 1 (nói 8k .. tức là: 8096, hoặc cao hơn) đọc khối của tập tin 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 đệm sau khi xử lý của nó.

+5

8K là 8192, có lẽ tốt hơn để viết 8 << 10 để được an toàn. :) – unwind

+0

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

+1

HAHAHA ... phải là thứ sáu ... Tôi rõ ràng không thể làm toán. Kích thước bộ đệm thực sự là một số nguyên thể hiện byte, vì vậy hãy viết 8192 (không phải 8096 :-)), thay vì 8 –

2

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 tìm 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ẽ nhảy qua tệp.

Ví dụ, nếu bạn đang gonna được nhảy đến dòng nhiều lần trong cùng một tập tin, và bạn biết rằng tập tin không thay đổi trong khi làm việc với nó, bạn có thể làm điều này:
Thứ nhất, đi qua toàn bộ tệp và ghi lại "vị trí tìm kiếm" của một số số dòng chính (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 (bạn đã ghi lại) sau đó đọc 5 dòng và bạn sẽ biết bạn đang ở dòng 12005 và cứ thế

4

Vì không có cách nào để xác định chiều dài của tất cả các dòng mà không đọc chúng, bạn không có lựa chọn nào khác ngoài việc lặp lại r tất cả các dòng trước vạch xuất phát của bạn. 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 phương pháp dựa trên máy phát điện:

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ố không dựa trên phương pháp này.

1

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ì có thể sử dụng phương pháp seek() để thực hiện tìm kiếm nhị phân thông qua tệp, ngay cả khi số lượng Data là biến. 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ó có cao hơn hay thấp hơn chỉ số bạn muốn, v.v.

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

0

Dưới đây là ví dụ sử dụng 'readlines (sizehint)' để đọc một đoạn dòng tại một thời điểm. 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à định hướng một dòng.

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

Điều gì tạo tệp bạn muốn xử lý? Nếu nó là một cái gì đó dưới sự kiểm soát của bạn, bạn có thể tạo ra một chỉ mục (dòng nào ở vị trí nào.) Tại thời điểm tệp được nối vào. Tệp chỉ mục có thể có kích thước đường cố định (số vùng đệm hoặc 0 đệm) 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ể do kích thước đường của tệp chỉ mục là không đổi).
  • Sử dụng tìm kiếm hoặc bất kỳ điều gì để trực tiếp chuyển đến lấy dòng từ tệp chỉ mục.
  • Phân tích cú pháp để lấy byte bù cho dòng tương ứng của tệp thực tế.
2

Tôi đã gặp sự cố tương tự (cần truy xuất từ ​​dòng tệp cụ thể lớn). Chắc chắn, mỗi lần tôi chạy qua tất cả các bản ghi trong hồ sơ 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ố lượng nhiều hàng cụ thể. Điều đó gây ra vấn đề chính cần được giải quyết - cách xử lý trực tiếp đến vị trí cần thiết của tệp.

Tôi phát hiện ra quyết định tiếp theo: Trước hết 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ị - thời gian 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, nhằm mục đích chức năng:

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 cắt tỉa của tập tin lên đến dòng khởi động. Vì vậy, nếu bạn cam kết tiếp theo readline - bạn có được dòng mục tiêu của bạn.

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

0

Bạn có thể sử dụng mmap để tìm bù đắp của các dòng. Mmap có vẻ là cách nhanh nhất để xử lý một tập tin

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 (offsets) để di chuyển đến dòng bạn cần

-2

có thể sử dụng chức năng này để trả lại dòng n:

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

Nếu bạn đang làm việc với một tập tin văn bản & dựa trên li nux hệ thống, 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) 
+0

tất nhiên nó không tương thích với các cửa sổ hoặc một số loại vỏ linux mà không hỗ trợ đầu/đuôi. – Wizmann

3

Tôi ngạc nhiên không ai đề cập 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 tin

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 tất cả các dòng khác từ tập tin

rest_of_file = itertools.islice(Fhandle,index_of_interest,None,2) 
for odd_line in rest_of_file: 
    print odd_line 
Các vấn đề liên quan