คำแนะนำเกี่ยวกับ BufferedReader

1. ภาพรวม

BufferedReaderเป็นคลาสที่ช่วยลดความยุ่งยากในการอ่านข้อความจากสตรีมอินพุตอักขระ บัฟเฟอร์อักขระเพื่อให้สามารถอ่านข้อมูลข้อความได้อย่างมีประสิทธิภาพ

ในการกวดวิชานี้เรากำลังจะไปดูที่วิธีการใช้BufferedReader ระดับ

2. เมื่อใดควรใช้BufferedReader

โดยทั่วไปแล้วBufferedReaderมีประโยชน์หากเราต้องการอ่านข้อความจากแหล่งอินพุตใด ๆ ไม่ว่าจะเป็นไฟล์ซ็อกเก็ตหรืออย่างอื่น

พูดง่ายๆก็คือช่วยให้เราสามารถลดจำนวนการดำเนินการ I / O ได้โดยการอ่านตัวอักษรและเก็บไว้ในบัฟเฟอร์ภายใน ในขณะที่บัฟเฟอร์มีข้อมูลผู้อ่านจะอ่านข้อมูลแทนที่จะอ่านโดยตรงจากสตรีมที่อยู่เบื้องหลัง

2.1. การบัฟเฟอร์ผู้อ่านอื่น

เช่นเดียวกับคลาส Java I / O ส่วนใหญ่BufferedReaderใช้รูปแบบ Decorator ซึ่งหมายความว่าจะมีReaderในตัวสร้าง ด้วยวิธีนี้จะช่วยให้เราสามารถขยายอินสแตนซ์ของการใช้งานReader ได้อย่างยืดหยุ่นด้วยฟังก์ชันการบัฟเฟอร์:

BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt"));

แต่ถ้าการบัฟเฟอร์ไม่สำคัญสำหรับเราเราสามารถใช้FileReaderได้โดยตรง:

FileReader reader = new FileReader("src/main/resources/input.txt");

นอกจากนี้ในการบัฟเฟอร์BufferedReaderนอกจากนี้ยังมีฟังก์ชั่นบางอย่างผู้ช่วยที่ดีสำหรับการอ่านไฟล์บรรทัดโดยบรรทัด ดังนั้นแม้ว่าการใช้FileReaderโดยตรงอาจดูง่ายกว่าแต่BufferedReaderสามารถช่วยได้มาก

2.2. การบัฟเฟอร์สตรีม

โดยทั่วไปเราสามารถกำหนดค่าBufferedReaderให้รับกระแสข้อมูลประเภทใดก็ได้เป็นแหล่งอ้างอิง เราสามารถทำได้โดยใช้InputStreamReaderและห่อไว้ในตัวสร้าง:

BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));

ในตัวอย่างข้างต้นเรากำลังอ่านจากSystem.inซึ่งโดยทั่วไปจะตรงกับอินพุตจากแป้นพิมพ์ ในทำนองเดียวกันเราสามารถส่งกระแสข้อมูลเข้าสำหรับการอ่านจากซ็อกเก็ตไฟล์หรืออินพุตข้อความประเภทใดก็ได้ที่สามารถจินตนาการได้ ข้อกำหนดเบื้องต้นเพียงอย่างเดียวคือมีการใช้งานInputStreamที่เหมาะสมสำหรับมัน

2.3. BufferedReader กับ Scanner

อีกทางเลือกหนึ่งคือเราสามารถใช้คลาสScannerเพื่อให้สามารถใช้งานได้เช่นเดียวกับBufferedReader

อย่างไรก็ตามมีความแตกต่างอย่างมีนัยสำคัญระหว่างสองคลาสนี้ซึ่งสามารถทำให้สะดวกสำหรับเรามากหรือน้อยขึ้นอยู่กับกรณีการใช้งานของเรา:

  • BufferedReaderถูกซิงโครไนซ์ (thread-safe) ในขณะที่ Scanner ไม่ใช่
  • สแกนเนอร์สามารถแยกวิเคราะห์ประเภทและสตริงดั้งเดิมโดยใช้นิพจน์ทั่วไป
  • BufferedReaderอนุญาตให้เปลี่ยนขนาดของบัฟเฟอร์ในขณะที่ Scanner มีขนาดบัฟเฟอร์คงที่
  • BufferedReaderมีขนาดบัฟเฟอร์เริ่มต้นที่ใหญ่กว่า
  • สแกนเนอร์ซ่อนIOExceptionในขณะที่BufferedReaderบังคับให้เราจัดการ
  • โดยปกติแล้วBufferedReaderจะเร็วกว่าScannerเพราะอ่านข้อมูลโดยไม่แยกวิเคราะห์เท่านั้น

ด้วยเหตุนี้หากเราแยกวิเคราะห์โทเค็นแต่ละรายการในไฟล์เครื่องสแกนเนอร์จะรู้สึกเป็นธรรมชาติกว่าBufferedReader เล็กน้อย แต่เพียงแค่อ่านทีละบรรทัดก็เป็นจุดที่BufferedReaderส่องแสง

หากจำเป็นเรามีคำแนะนำเกี่ยวกับเครื่องสแกนด้วยเช่นกัน

3. การอ่านข้อความด้วยBufferedReader

มาดูกระบวนการทั้งหมดในการสร้างโดยใช้และทำลายBufferReaderอย่างถูกต้องเพื่ออ่านจากไฟล์ข้อความ

3.1. การเริ่มต้นBufferedReader

ประการแรกเรามาสร้างBufferedReaderใช้ของBufferedReader (Reader)คอนสตรัค :

BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt"));

การห่อFileReaderเช่นนี้เป็นวิธีที่ดีในการเพิ่มการบัฟเฟอร์เป็นแง่มุมให้กับผู้อ่านคนอื่น ๆ

โดยค่าเริ่มต้นจะใช้บัฟเฟอร์ 8 KB อย่างไรก็ตามหากเราต้องการบัฟเฟอร์บล็อกที่เล็กลงหรือใหญ่ขึ้นเราสามารถใช้ตัวสร้างBufferedReader (Reader, int) :

BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt")), 16384);

ซึ่งจะกำหนดขนาดบัฟเฟอร์เป็น 16384 ไบต์ (16 KB)

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

ควรใช้พาวเวอร์ 2 เป็นขนาดบัฟเฟอร์เนื่องจากอุปกรณ์ฮาร์ดแวร์ส่วนใหญ่มีกำลัง 2 เท่ากับขนาดบล็อก

สุดท้ายมีอีกวิธีหนึ่งที่สะดวกในการสร้างBufferedReaderโดยใช้คลาสFiles helperจากjava.nio API:

BufferedReader reader = Files.newBufferedReader(Paths.get("src/main/resources/input.txt"))

กำลังสร้างมันเช่นนี้เป็นวิธีที่ดีในการบัฟเฟอร์หากเราต้องการอ่านไฟล์เพราะเราไม่จำเป็นต้องสร้างFileReaderด้วยตนเองก่อนแล้วจึงตัดมัน

3.2. การอ่านทีละบรรทัด

ต่อไปมาอ่านเนื้อหาของไฟล์โดยใช้เมธอดreadLine :

public String readAllLines(BufferedReader reader) throws IOException { StringBuilder content = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { content.append(line); content.append(System.lineSeparator()); } return content.toString(); }

เราสามารถทำสิ่งเดียวกันกับด้านบนโดยใช้วิธีการเส้นที่แนะนำใน Java 8 ได้ง่ายขึ้นเล็กน้อย:

public String readAllLinesWithStream(BufferedReader reader) { return reader.lines() .collect(Collectors.joining(System.lineSeparator())); }

3.3. การปิดสตรีม

หลังจากใช้BufferedReaderเราต้องเรียกใช้เมธอดclose ()เพื่อปล่อยทรัพยากรระบบใด ๆ ที่เกี่ยวข้องกับมัน สิ่งนี้จะทำโดยอัตโนมัติหากเราใช้บล็อกtry-with-resources :

try (BufferedReader reader = new BufferedReader(new FileReader("src/main/resources/input.txt"))) { return readAllLines(reader); }

4. วิธีการอื่น ๆ ที่เป็นประโยชน์

ตอนนี้เรามาดูวิธีการที่มีประโยชน์ต่างๆที่มีอยู่ในBufferedReader

4.1. การอ่านอักขระเดี่ยว

เราสามารถใช้วิธีread ()เพื่ออ่านอักขระเดี่ยว มาอ่านเนื้อหาทั้งหมดทีละตัวละครจนจบสตรีม:

public String readAllCharsOneByOne(BufferedReader reader) throws IOException { StringBuilder content = new StringBuilder(); int value; while ((value = reader.read()) != -1) { content.append((char) value); } return content.toString(); }

สิ่งนี้จะอ่านอักขระ (ส่งคืนเป็นค่า ASCII) ร่ายเป็นอักขระและต่อท้ายผลลัพธ์ เราทำอย่างนี้ซ้ำจนกว่าจะสิ้นสุดของกระแสที่ระบุโดยค่าการตอบสนอง -1 จากที่อ่าน ()วิธีการ

4.2. การอ่านอักขระหลายตัว

หากเราต้องการอ่านอักขระหลายตัวพร้อมกันเราสามารถใช้วิธีการอ่าน (ถ่าน [] cbuf, int off, int len) :

public String readMultipleChars(BufferedReader reader) throws IOException { int length; char[] chars = new char[length]; int charsRead = reader.read(chars, 0, length); String result; if (charsRead != -1) { result = new String(chars, 0, charsRead); } else { result = ""; } return result; }

ในตัวอย่างโค้ดด้านบนเราจะอ่านอักขระได้สูงสุด 5 ตัวในอาร์เรย์ char และสร้างสตริงจากมัน ในกรณีที่ไม่มีการอ่านอักขระใด ๆ ในความพยายามอ่านของเรา (เช่นเรามาถึงจุดสิ้นสุดของสตรีมแล้ว) เราจะส่งคืนสตริงว่างเปล่า

4.3. การข้ามอักขระ

นอกจากนี้เรายังสามารถข้ามจำนวนอักขระที่กำหนดโดยเรียกเมธอดskip (long n) :

@Test public void givenBufferedReader_whensSkipChars_thenOk() throws IOException { StringBuilder result = new StringBuilder(); try (BufferedReader reader = new BufferedReader(new StringReader("1__2__3__4__5"))) { int value; while ((value = reader.read()) != -1) { result.append((char) value); reader.skip(2L); } } assertEquals("12345", result); }

In the above example, we read from an input string which contains numbers separated by two underscores. In order to construct a string containing only the numbers, we are skipping the underscores by calling the skip method.

4.4. mark and reset

We can use the mark(int readAheadLimit) and reset() methods to mark some position in the stream and return to it later. As a somewhat contrived example, let's use mark() and reset() to ignore all whitespaces at the beginning of a stream:

@Test public void givenBufferedReader_whenSkipsWhitespacesAtBeginning_thenOk() throws IOException { String result; try (BufferedReader reader = new BufferedReader(new StringReader(" Lorem ipsum dolor sit amet."))) { do { reader.mark(1); } while(Character.isWhitespace(reader.read())) reader.reset(); result = reader.readLine(); } assertEquals("Lorem ipsum dolor sit amet.", result); }

In the above example, we use the mark() method to mark the position we just read. Giving it a value of 1 means only the code will remember the mark for one character forward. It's handy here because, once we see our first non-whitespace character, we can go back and re-read that character without needing to reprocess the whole stream. Without having a mark, we'd lose the L in our final string.

Note that because mark() can throw an UnsupportedOperationException, it's pretty common to associate markSupported() with code that invokes mark(). Though, we don't actually need it here. That's because markSupported() always returns true for BufferedReader.

Of course, we might be able to do the above a bit more elegantly in other ways, and indeed mark and reset aren't very typical methods. They certainly come in handy, though, when there is a need to look ahead.

5. Conclusion

ในการกวดวิชาอย่างนี้เราได้เรียนรู้วิธีการอ่านลำธารป้อนตัวอักษรในตัวอย่างในทางปฏิบัติโดยใช้BufferedReader

ในที่สุดซอร์สโค้ดสำหรับตัวอย่างก็มีให้ใช้งานบน Github