จะข้ามไปยังบรรทัดใดบรรทัดหนึ่งในไฟล์ข้อความขนาดใหญ่ได้อย่างไร?


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
ผมคิดว่าชื่อจะทำให้เข้าใจผิด - 15MB TBH ไม่ได้จริงๆ "แฟ้มข้อความขนาดใหญ่" ที่จะพูดน้อย ...
PMS

คำตอบ:


30

linecache :

linecacheโมดูลช่วยให้หนึ่งที่จะได้รับสายใด ๆ จากแฟ้มแหล่งที่มาหลามในขณะที่พยายามที่จะเพิ่มประสิทธิภาพภายในโดยใช้แคชกรณีทั่วไปที่หลายสายถูกอ่านจากไฟล์เดียว tracebackโมดูลนี้ใช้เพื่อดึงรายการต้นทางสำหรับการรวมในการย้อนกลับที่จัดรูปแบบ ...


165
ฉันเพิ่งตรวจสอบซอร์สโค้ดของโมดูลนี้: ไฟล์ทั้งหมดถูกอ่านในหน่วยความจำ! ดังนั้นฉันจะตัดคำตอบนี้ออกเพื่อจุดประสงค์ในการเข้าถึงบรรทัดที่กำหนดในไฟล์อย่างรวดเร็ว
MiniQuark

MiniQuark ฉันลองแล้วมันใช้งานได้จริงและเร็วมาก ฉันจะต้องดูว่าจะเกิดอะไรขึ้นถ้าฉันทำงานกับไฟล์หลายสิบไฟล์ในเวลาเดียวกันด้วยวิธีนี้ค้นหาว่าระบบของฉันตายที่จุดใด
user63503

5
ตัวจัดการหน่วยความจำเสมือนของระบบปฏิบัติการของคุณช่วยได้ไม่น้อยดังนั้นการอ่านไฟล์ขนาดใหญ่ลงในหน่วยความจำอาจไม่ช้าหากคุณไม่ได้สร้างความผิดพลาดของเพจจำนวนมาก :) ในทางกลับกันการทำแบบนี้เป็น "วิธีที่โง่" และจัดสรรจำนวนมาก ๆ ของหน่วยความจำได้อย่างรวดเร็วอย่างเห็นได้ชัด ฉันสนุกกับบทความของ Poul-Henning Kamp ผู้พัฒนา FreeBSD ชาวเดนมาร์กในเรื่องนี้: que.acm.org/detail.cfm?id=1814327
Morten Jensen

13
ลองไฟล์ 100G มันห่วย ฉันต้องใช้ f.tell () f.seek () f.readline ()
WHI

115

คุณไม่สามารถข้ามไปข้างหน้าได้หากไม่อ่านไฟล์อย่างน้อยหนึ่งครั้งเนื่องจากคุณไม่รู้ว่าตัวแบ่งบรรทัดอยู่ตรงไหน คุณสามารถทำสิ่งต่างๆเช่น:

# 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: นอกจากนี้หากไฟล์ไม่เปลี่ยนแปลงดัชนีหมายเลขบรรทัดสามารถดองและใช้ซ้ำได้โดยตัดค่าใช้จ่ายเริ่มต้นในการสแกนไฟล์เพิ่มเติม
ล็อต

ตกลงหลังจากที่ฉันกระโดดไปที่นั่นฉันจะประมวลผลทีละบรรทัดโดยเริ่มจากตำแหน่งนี้ได้อย่างไร
user63503

8
สิ่งหนึ่งที่ควรทราบ (โดยเฉพาะใน windows): ระวังการเปิดไฟล์ในโหมดไบนารีหรือใช้ offset = file.tell () ในโหมดข้อความบน windows บรรทัดจะสั้นกว่าความยาวดิบบนดิสก์ (\ r \ n แทนที่ด้วย \ n)
Brian

2
@photographer: ใช้ read () หรือ readline () โดยเริ่มจากตำแหน่งปัจจุบันตามที่กำหนดโดยการค้นหา
ล็อต

22

คุณไม่มีตัวเลือกมากนักหากบรรทัดมีความยาวต่างกัน ... คุณต้องประมวลผลอักขระลงท้ายบรรทัดเพื่อให้ทราบเมื่อคุณดำเนินการไปยังบรรทัดถัดไป

อย่างไรก็ตามคุณสามารถเร่งความเร็วได้อย่างมากและลดการใช้หน่วยความจำโดยเปลี่ยนพารามิเตอร์สุดท้ายเป็น "เปิด" เป็นค่าที่ไม่ใช่ 0

0 หมายถึงการดำเนินการอ่านไฟล์ไม่มีบัฟเฟอร์ซึ่งช้ามากและใช้ดิสก์มาก 1 หมายถึงไฟล์ถูกบัฟเฟอร์บรรทัดซึ่งจะเป็นการปรับปรุง สิ่งที่สูงกว่า 1 (พูดว่า 8k .. เช่น 8096 หรือสูงกว่า) อ่านส่วนของไฟล์ในหน่วยความจำ คุณยังคงเข้าถึงได้for line in open(etc):แต่ python จะไปทีละเล็กน้อยเท่านั้นโดยทิ้งแต่ละส่วนที่บัฟเฟอร์หลังจากประมวลผลแล้ว


6
8K คือ 8192 อาจจะดีกว่าที่จะเขียน 8 << 10 เพื่อให้อยู่ในด้านที่ปลอดภัย :)
ผ่อนคลาย

คุณบังเอิญรู้หรือไม่ว่ามีการระบุขนาดบัฟเฟอร์เป็นไบต์? รูปแบบที่เหมาะสมคืออะไร? ฉันเขียน '8k' ได้ไหม หรือควรเป็น '8096'?
user63503

1
ฮ่าฮ่าฮ่า ... ต้องเป็นวันศุกร์ ... ฉันไม่สามารถทำคณิตศาสตร์ได้อย่างชัดเจน ขนาดบัฟเฟอร์เป็นจำนวนเต็มที่แสดงไบต์ดังนั้นเขียน 8192 (ไม่ใช่ 8096 :-)) แทนที่จะเป็น 8
Jarret Hardie

ความสุขของฉัน - หวังว่ามันจะได้ผล ในระบบสมัยใหม่คุณอาจเพิ่มขนาดบัฟเฟอร์ได้ไม่น้อย 8k เป็นเพียงสิ่งที่อยู่ในความทรงจำของฉันด้วยเหตุผลบางอย่างที่ฉันไม่สามารถระบุได้
Jarret Hardie

ฉันได้ทำการทดสอบที่นี่แล้วและตั้งค่าเป็น -1 (ค่าเริ่มต้นของระบบปฏิบัติการมักเป็น 8k แต่มักจะบอกได้ยาก) ดูเหมือนว่าจะเร็วที่สุดเท่าที่จะทำได้ ที่กล่าวว่าส่วนหนึ่งอาจเป็นเพราะฉันกำลังทดสอบบนเซิร์ฟเวอร์เสมือน
Oscar Smith

12

ฉันอาจจะเสียเพราะแรมมากมาย แต่ 15 M ก็ไม่ได้ใหญ่โตอะไร การอ่านลงในหน่วยความจำreadlines() คือสิ่งที่ฉันมักจะทำกับไฟล์ขนาดนี้ การเข้าถึงบรรทัดหลังจากนั้นเป็นเรื่องเล็กน้อย


เหตุใดฉันจึงลังเลเล็กน้อยที่จะอ่านทั้งไฟล์ - ฉันอาจมีกระบวนการเหล่านี้หลายกระบวนการทำงานอยู่และหากมีการอ่าน 12 ไฟล์ 15MB แต่ละไฟล์อาจไม่ดี แต่ฉันต้องทดสอบเพื่อดูว่ามันใช้ได้ไหม ขอบคุณ.
user63503

4
หือแล้วถ้าเป็นไฟล์ 1GB ล่ะ?
โนอาห์

@ ช่างภาพ: แม้แต่กระบวนการ "หลาย" ที่อ่านในไฟล์ 15MB ก็ไม่ควรมีความสำคัญกับเครื่องสมัยใหม่ทั่วไป (แน่นอนว่าคุณกำลังทำอะไรกับพวกเขา)
Jacob Gabrielson

เจคอบใช่ฉันควรจะลอง กระบวนการกำลังทำงานบนเครื่องเสมือนเป็นเวลาหลายสัปดาห์หาก vm ไม่ล้มเหลว น่าเสียดายที่ครั้งที่แล้วมันพังหลังจาก 6 วัน ฉันต้องไปต่อจากที่ที่มันหยุดกะทันหัน ยังคงต้องค้นหาวิธีการหาว่ามันถูกทิ้งอย่างไร
user63503

@ โนอาห์: แต่ไม่ใช่! ทำไมคุณไม่ไปต่อ? จะเกิดอะไรขึ้นถ้าไฟล์ 128TB? ระบบปฏิบัติการจำนวนมากจะไม่สามารถรองรับได้ ทำไมไม่แก้ปัญหาตามที่มา
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)

หมายเหตุ: ดัชนีเป็นศูนย์ตามแนวทางนี้


4

หากคุณไม่ต้องการอ่านทั้งไฟล์ในหน่วยความจำ .. คุณอาจต้องใช้รูปแบบอื่นที่ไม่ใช่ข้อความธรรมดา

แน่นอนว่าทั้งหมดขึ้นอยู่กับสิ่งที่คุณพยายามทำและความถี่ในการข้ามไฟล์

ตัวอย่างเช่นหากคุณจะกระโดดไปที่บรรทัดหลาย ๆ ครั้งในไฟล์เดียวกันและคุณรู้ว่าไฟล์ไม่เปลี่ยนแปลงในขณะที่ทำงานกับมันคุณสามารถทำได้:
ขั้นแรกส่งผ่านไฟล์ทั้งหมดและบันทึก " ค้นหาตำแหน่ง "ของคีย์ - ไลน์ - ตัวเลข (เช่นเคย 1,000 บรรทัด)
จากนั้นหากคุณต้องการบรรทัด 12005 ให้ข้ามไปที่ตำแหน่ง 12000 (ที่คุณบันทึกไว้) จากนั้นอ่าน 5 บรรทัดแล้วคุณจะรู้ว่าคุณ 'อยู่ในบรรทัด 12005 และอื่น ๆ


3

หากคุณทราบตำแหน่งล่วงหน้าในไฟล์ (แทนที่จะเป็นหมายเลขบรรทัด) คุณสามารถใช้file.seek ()เพื่อไปที่ตำแหน่งนั้นได้

แก้ไข : คุณสามารถใช้ฟังก์ชัน linecache.getline (ชื่อไฟล์, ผ้าลินิน)ซึ่งจะส่งคืนเนื้อหาของเส้นลินิน แต่หลังจากอ่านไฟล์ทั้งหมดลงในหน่วยความจำเท่านั้น ดีถ้าคุณสุ่มเข้าถึงบรรทัดจากในไฟล์ (เนื่องจาก python เองอาจต้องการพิมพ์การย้อนกลับ) แต่ไม่ดีสำหรับไฟล์ 15MB


ฉันจะไม่ใช้ linecache เพื่อจุดประสงค์นี้อย่างแน่นอนเพราะมันอ่านไฟล์ทั้งหมดในหน่วยความจำก่อนที่จะส่งคืนบรรทัดที่ร้องขอ
MiniQuark

ใช่มันฟังดูดีเกินไปที่จะเป็นจริง ฉันยังหวังว่าจะมีโมดูลที่ทำสิ่งนี้ได้อย่างมีประสิทธิภาพ แต่มักจะใช้เมธอด file.seek () แทน
โนอาห์

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) - คำสั่งที่ดำเนินการตัดไฟล์จนถึงการเริ่มต้นบรรทัด ดังนั้นหากคุณกระทำการอ่านบรรทัดถัดไปคุณจะได้รับบรรทัดเป้าหมายของคุณ

การใช้แนวทางดังกล่าวทำให้ฉันประหยัดเวลาได้มาก


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 (ออฟเซ็ต) เพื่อย้ายไปยังบรรทัดที่คุณต้องการ


2

บรรทัดมีข้อมูลดัชนีหรือไม่? หากเนื้อหาของแต่ละบรรทัดเป็นแบบ " <line index>:Data" ก็seek()สามารถใช้วิธีการค้นหาไบนารีผ่านไฟล์ได้แม้ว่าจำนวนDataจะเป็นตัวแปรก็ตาม คุณต้องการหาจุดกึ่งกลางของไฟล์อ่านบรรทัดตรวจสอบว่าดัชนีสูงหรือต่ำกว่าที่คุณต้องการเป็นต้น

readlines()มิฉะนั้นสิ่งที่ดีที่สุดที่คุณสามารถทำได้เป็นเพียง หากคุณไม่ต้องการอ่านทั้งหมด 15MB คุณสามารถใช้sizehintอาร์กิวเมนต์เพื่อแทนที่จำนวนมากreadline()ด้วยจำนวนการโทรที่readlines()น้อยลง


2

หากคุณกำลังจัดการกับไฟล์ข้อความและใช้ระบบ linuxคุณสามารถใช้คำสั่ง 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)

แน่นอนว่ามันเข้ากันไม่ได้กับ windows หรือเปลือกลินุกซ์บางชนิดที่ไม่รองรับหัว / ท้าย
Wizmann

เร็วกว่าการทำใน Python หรือไม่?
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)

1

ไม่มีคำตอบใดที่น่าพอใจเป็นพิเศษดังนั้นนี่คือตัวอย่างเล็ก ๆ ที่จะช่วยได้

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
นี่เป็นทางออกที่ดีที่สุดไม่เพียง แต่สำหรับคำถามเท่านั้น แต่ยังรวมถึงปัญหาที่เกี่ยวข้องกับหน่วยความจำอื่น ๆ อีกมากมายเมื่ออ่านไฟล์ขนาดใหญ่ ขอบคุณสำหรับสิ่งนั้น!
Lucas Azevedo

-2

สามารถใช้ฟังก์ชันนี้เพื่อส่งกลับบรรทัด 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-statement อย่างน้อยหนึ่งรายการสำหรับการแบ่งบรรทัดบางส่วน
ksed
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.