วิธีทั่วไปในการอ่านไฟล์ในทับทิมคืออะไร?


280

วิธีทั่วไปในการอ่านไฟล์ในทับทิมคืออะไร?

ตัวอย่างเช่นต่อไปนี้เป็นวิธีการหนึ่ง:

fileObj = File.new($fileName, "r")
while (line = fileObj.gets)
  puts(line)
end
fileObj.close

ฉันรู้ว่าทับทิมมีความยืดหยุ่นสูงมาก ประโยชน์ / ข้อเสียของแต่ละวิธีมีอะไรบ้าง


6
ฉันไม่คิดว่าคำตอบที่ชนะในปัจจุบันนั้นถูกต้อง
Inger

คำตอบ:


259
File.open("my/file/path", "r") do |f|
  f.each_line do |line|
    puts line
  end
end
# File is closed automatically at end of block

นอกจากนี้ยังเป็นไปได้ที่จะปิดไฟล์อย่างชัดเจนหลังจากข้างต้น (ส่งบล็อกเพื่อopenปิดให้คุณ):

f = File.open("my/file/path", "r")
f.each_line do |line|
  puts line
end
f.close

14
นี่เป็นทับทิมที่ไม่ค่อยมีสำนวน ใช้foreachแทนopenและแจกจ่ายกับeach_lineบล็อก
Tin Man

7
f.each { |line| ... }และf.each_line { |line| ... }ดูเหมือนว่าจะมีพฤติกรรมเดียวกัน (อย่างน้อยใน Ruby 2.0.0)
chbrown

327

วิธีที่ง่ายที่สุดหากไฟล์ไม่ยาวเกินไป:

puts File.read(file_name)

แน่นอนIO.readหรือFile.readปิดไฟล์โดยอัตโนมัติดังนั้นจึงไม่จำเป็นต้องใช้File.openกับบล็อก


16
IO.readหรือFile.readปิดไฟล์โดยอัตโนมัติแม้ว่าข้อความของคุณจะทำให้ดูเหมือนไม่เป็นเช่นนั้น
Phrogz

15
เขาพูดแล้ว "ถ้าไฟล์ไม่ยาวเกินไป" เหมาะกับกรณีของฉันอย่างสมบูรณ์แบบ
jayP

227

ระวังไฟล์ "slurping" นั่นคือเมื่อคุณอ่านไฟล์ทั้งหมดในหน่วยความจำพร้อมกัน

ปัญหาคือมันไม่ได้ปรับขนาดได้ดี คุณสามารถพัฒนารหัสด้วยไฟล์ที่มีขนาดพอเหมาะจากนั้นนำไปผลิตและพบว่าคุณกำลังพยายามอ่านไฟล์ที่มีหน่วยเป็นกิกะไบต์และโฮสต์ของคุณค้างในขณะที่พยายามอ่านและจัดสรรหน่วยความจำ

I / O แบบบรรทัดต่อบรรทัดนั้นรวดเร็วมากและเกือบจะมีประสิทธิภาพเท่ากับ slurping มันเร็วอย่างน่าประหลาดใจจริงๆ

ฉันชอบที่จะใช้:

IO.foreach("testfile") {|x| print "GOT ", x }

หรือ

File.foreach('testfile') {|x| print "GOT", x }

ไฟล์สืบทอดมาจาก IO และforeachอยู่ใน IO ดังนั้นคุณสามารถใช้อย่างใดอย่างหนึ่ง

ฉันมีเกณฑ์มาตรฐานบางอย่างที่แสดงผลกระทบของการพยายามอ่านไฟล์ขนาดใหญ่ผ่านทางreadเทียบกับ I-O แบบทีละบรรทัดที่ " ทำไม" slurping "ไฟล์จึงไม่ใช่วิธีที่ดี? "


6
นี่คือสิ่งที่ฉันกำลังมองหา ฉันมีไฟล์ห้าล้านบรรทัดและไม่ต้องการโหลดลงในหน่วยความจำ
Scotty C.

68

คุณสามารถอ่านไฟล์ทั้งหมดในครั้งเดียว:

content = File.readlines 'file.txt'
content.each_with_index{|line, i| puts "#{i+1}: #{line}"}

เมื่อไฟล์มีขนาดใหญ่หรืออาจมีขนาดใหญ่ก็มักจะดีกว่าในการประมวลผลทีละบรรทัด:

File.foreach( 'file.txt' ) do |line|
  puts line
end

บางครั้งคุณต้องการเข้าถึงตัวจัดการไฟล์หรือควบคุมการอ่านด้วยตนเอง:

File.open( 'file.txt' ) do |f|
  loop do
    break if not line = f.gets
    puts "#{f.lineno}: #{line}"
  end
end

ในกรณีของไฟล์ไบนารีคุณอาจระบุตัวคั่น nil และขนาดบล็อกเช่น:

File.open('file.bin', 'rb') do |f|
  loop do
    break if not buf = f.gets(nil, 80)
    puts buf.unpack('H*')
  end
end

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

begin
  f = File.open 'file.txt'
  while line = f.gets
    puts line
  end
ensure
  f.close
end

อ้างอิง: ไฟล์ APIและIO API


2
ไม่มีfor_eachอยู่ในไฟล์หรือ IO ใช้foreachแทน
มนุษย์ดีบุก

1
ฉันมักจะใช้เครื่องมือแก้ไขข้อความ Sublime กับปลั๊กอิน RubyMarkers เมื่อเอกสารรหัสที่จะใช้ในคำตอบที่นี่ มันทำให้ง่ายต่อการแสดงผลกลางคล้ายกับการใช้ IRB ปลั๊กอิน Seeing Is Believing สำหรับ Sublime Text 2 นั้นทรงพลังเช่นกัน
ชายดีบุก

1
คำตอบที่ดี สำหรับตัวอย่างสุดท้ายฉันอาจแนะนำให้ใช้ whileแทนloopและใช้ensureเพื่อให้แน่ใจว่าไฟล์ถูกปิดแม้ว่าจะมีข้อยกเว้นเกิดขึ้น เช่นนี้ begin; f = File.open('testfile'); while line = f.gets; puts line; end; ensure; f.close; end(แทนที่กึ่งทวิภาคกับการขึ้นบรรทัดใหม่):
antinome

1
ใช่ที่ดีกว่า @antinome ปรับปรุงคำตอบ ขอบคุณ!
Victor Klos

26

วิธีการง่าย ๆ อย่างหนึ่งคือการใช้readlines:

my_array = IO.readlines('filename.txt')

แต่ละบรรทัดในไฟล์อินพุตจะเป็นรายการในอาร์เรย์ วิธีการจัดการการเปิดและปิดไฟล์สำหรับคุณ


5
เช่นเดียวกับreadหรือตัวแปรอื่น ๆ สิ่งนี้จะดึงไฟล์ทั้งหมดลงในหน่วยความจำซึ่งอาจทำให้เกิดปัญหาใหญ่หากไฟล์มีขนาดใหญ่กว่าหน่วยความจำที่มีอยู่ นอกจากนี้เนื่องจากเป็นอาร์เรย์ Ruby จึงต้องสร้างอาร์เรย์ทำให้กระบวนการช้าลง
Tin Man


9

ฉันมักจะทำสิ่งนี้:

open(path_in_string, &:read)

นี่จะให้ข้อความทั้งหมดเป็นวัตถุสตริง ใช้งานได้กับ Ruby 1.9 เท่านั้น


อันนี้ดีและสั้น! มันปิดไฟล์ด้วยหรือไม่
mrgreenfur

5
มันปิด แต่ไม่สามารถปรับขนาดได้ดังนั้นควรระวัง
คนดีบุก

3

ส่งคืนnบรรทัดสุดท้ายจาก your_file.log หรือ. txt

path = File.join(Rails.root, 'your_folder','your_file.log')

last_100_lines = `tail -n 100 #{path}`

1

วิธีที่มีประสิทธิภาพมากขึ้นคือการสตรีมโดยขอให้เคอร์เนลของระบบปฏิบัติการเปิดไฟล์จากนั้นอ่านไบต์จากมันทีละนิด เมื่ออ่านไฟล์ต่อบรรทัดใน Ruby ข้อมูลจะถูกนำมาจากไฟล์ 512 ไบต์ในแต่ละครั้งและแยกเป็น“ เส้น” หลังจากนั้น

โดยการบัฟเฟอร์เนื้อหาของไฟล์จำนวนการโทร I / O จะลดลงในขณะที่แบ่งไฟล์ในกลุ่มตรรกะ

ตัวอย่าง:

เพิ่มคลาสนี้ลงในแอปของคุณเป็นวัตถุบริการ:

class MyIO
  def initialize(filename)
    fd = IO.sysopen(filename)
    @io = IO.new(fd)
    @buffer = ""
  end

  def each(&block)
    @buffer << @io.sysread(512) until @buffer.include?($/)

    line, @buffer = @buffer.split($/, 2)

    block.call(line)
    each(&block)
  rescue EOFError
    @io.close
 end
end

เรียกมันและส่งผ่าน:eachวิธีการบล็อก:

filename = './somewhere/large-file-4gb.txt'
MyIO.new(filename).each{|x| puts x }

อ่านเกี่ยวกับที่นี่ในโพสต์รายละเอียดนี้:

Ruby Magic Slurping & สตรีมไฟล์โดย AppSignal


ระวัง: รหัสนั้นจะไม่สนใจบรรทัดสุดท้ายหากมันไม่ได้จบด้วย linefeed (อย่างน้อยใน Linux)
Jorgen

ฉันคิดว่าการแทรก "block.call (@buffer)" ก่อน "@ io.close" จะรับสายที่ไม่สมบูรณ์หายไป อย่างไรก็ตามฉันได้เล่นกับ Ruby เพียงวันเดียวดังนั้นฉันอาจผิดได้ มันใช้งานได้ในแอปพลิเคชันของฉัน :)
Jorgen

หลังจากอ่านโพสต์ AppSignal ดูเหมือนว่ามีความเข้าใจผิดเล็กน้อยที่นี่ รหัสที่คุณคัดลอกออกจากโพสต์นั้นซึ่งเป็นบัฟเฟอร์ IO คือตัวอย่างการนำไปปฏิบัติของสิ่งที่ Ruby ทำกับ File.foreach หรือ IO.foreach (ซึ่งเป็นวิธีการเดียวกัน) พวกเขาควรจะใช้และคุณไม่จำเป็นต้องปรับใช้พวกเขาเช่นนี้
Peter H. Boling

@ PeterH.Boling ฉันยังใช้ความคิดและการไม่ได้นำมาใช้ใหม่ส่วนใหญ่ แต่ทับทิมช่วยให้เราสามารถเปิดสิ่งต่าง ๆ และโผล่ที่อวัยวะภายในของพวกเขาโดยไม่ละอายใจมันเป็นหนึ่งในประโยชน์ ไม่มีจริง 'ควร' หรือ 'ไม่ควร' โดยเฉพาะอย่างยิ่งในทับทิม / ราง ตราบใดที่คุณรู้ว่าคุณกำลังทำอะไรและคุณเขียนข้อสอบไว้
Khalil Gharbaoui

0
content = `cat file`

ฉันคิดว่าวิธีนี้เป็นวิธีที่ "ผิดปกติ" ที่สุด อาจจะเป็นเรื่องยุ่งยาก แต่ก็ใช้งานได้หากcatติดตั้งแล้ว


1
เคล็ดลับที่มีประโยชน์ แต่การโทรหาเชลล์มีข้อผิดพลาดมากมายรวมถึง 1) คำสั่งอาจแตกต่างกันไปสำหรับระบบปฏิบัติการที่แตกต่างกัน 2) คุณอาจต้องหลบหนีช่องว่างในชื่อไฟล์ คุณจะดีกว่ามากเมื่อใช้ฟังก์ชันในตัวของ Ruby เช่นcontent = File.read(filename)
Jeff Ward
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.