ภาษาจาวาจัดเตรียม Java API เพื่อให้เราสามารถเขียนโปรแกรมเพื่อ

อ่านและเขียนข้อมูลจากแหล่งข้อมูลชนิดต่างๆ เช่น ไฟล์ (file) เครือข่าย (socket) คีย์บอร์ด จอภาพ เป็นต้น ลักษณะของข้อมูลที่จะอ่านหรือเขียนกับแหล่งข้อมูลสามารถแบ่งได้ 2 แบบคือข้อมูลแบบอักขระ (character) และข้อมูลแบบไบนารี่ (binary) หรือเรียกว่าเป็นแบบไบต์เพราะหน่วยเล็กที่สุดของการอ่านหรือเขียนข้อมูลต้องทำเป็นไบต์ ข้อมูลที่เป็นแบบอักขระคือข้อมูลที่มีการจัดรูปแบบด้วยชุดรหัสอักขระ (character set) เช่น ไฟล์ข้อความต่างๆ ซึ่งเมื่อเราเปิดด้วยโปรแกรมแก้ไขข้อความแล้วสามารถอ่านได้รู้เรื่อง ส่วนข้อมูลแบบไบต์คือข้อมูลในรูปแบบไบนารี่ซึ่งเมื่อเราเปิดด้วยโปรแกรมแก้ไขข้อความแล้วอ่านไม่รู้เรื่องหรือไม่เป็นภาษาคน 

     ข้อมูลที่จะอ่านหรือเขียนจะเรียงต่อกันไปเรื่อยๆตั้งแต่ต้นจนจบเรียกว่าสตรีม (stream) ถ้าข้อมูลเป็นแบบอักขระจะเรียกสตรีมของอักขระ (character stream) ถ้าเป็นข้อมูลแบบไบนารี่จะเรียกว่าสตรีมของไบต์ (byte stream) ดังนั้น Java API ที่ใช้ในการอ่านและเขียนข้อมูลจึงเรียกได้ว่าเป็นคลาสที่ใช้สร้างออบเจกต์สำหรับจัดการสตรีมของข้อมูล ประกอบด้วยแพคเกจ java.io และแพคเกจ java.nio โดยแพคเกจ java.io เป็นแพคเกจที่มีมากับภาษาจาวาตั้งแต่เวอร์ชั่นแรก ส่วนแพคเกจ java.nio ถูกพัฒนาขึ้นในภายหลัง

    Stream ใน Java API เป็น abstract ในการติดต่อกับเพื่ออ่านหรือเขียนข้อมูลกับแหล่งข้อมูลภายนอก เช่น ดิสก์ ไฟล์ เครือข่าย เป็นต้น คำว่า abstract หมายถึงเรารู้ว่าต้องใช้งานอย่างไร แต่มันทำงานอย่างไรเราไม่จำเป็นต้องรู้ เราแบ่ง stream ออกเป็น 2 แบบคือ input stream เพื่ออ่านข้อมูลและ output stream เพื่อเขียนข้อมูล ซึ่งทั้ง 2 แบบเรียกรวมๆว่า IO Stream ตัวอย่างของ IO Stream เบื้องต้นคือ System.in และ System.out ที่เราใช้อ่านและเขียนข้อมูลกับคอนโซลในแอพพลิเคชั่น IDE ถ้ามองในมุมของข้อมูลเราแบ่ง stream ออกเป็น byte streams ซึ่งอ่านและเขียนข้อมูลเป็นไบต์และ char streams ซึ่งอ่านและเขียนข้อมูลเป็นตัวอักษรในรูปแบบ utf-16 เราสามารถใช้ buffer ในการอ่านหรือเขียนข้อมูลเพื่อลดการเชื่อมต่อกับแหล่งข้อมูล โดยข้อมูลจะถูกอ่านจากแหล่งข้อมูลมายัง  buffer ก่อนหลังจากนั้นโปรแกรมจึงจะอ่านจาก  buffer หรือในกรณีการเขียนข้อมูล ข้อมูลจะถูกเขียนลงใน buffer ก่อนจากนั้นข้อมูลใน buffer จึงจะถูกเขียนไปยังแห่ลงข้อมูล เราอาจจะมองว่า buffer เป็นเหมือนจุดรวบรวมข้อมูลนั่นเอง

     ในการใช้งานเราจะสร้างออบเจกต์ของสตรีมขึ้นมาโดยระบุแหล่งข้อมูลที่เชื่อมต่อกับสตรีม จากนั้นเราจึงอ่านและเขียนข้อมูลกับออบเจกต์ของสตรีม

ซึ่งจะไปอ่านและเขียนข้อมูลกับแหล่งข้อมูลจริงๆอีกทีหนึ่งผ่าน JVM  

     ตัวอย่างด้านล่างเป็นการใช้ออบเจกต์ของสตรีมจากคลาส FileWriter และออบเจกต์ของสตรีมจากคลาส FileReader เพื่อเขียนและอ่านข้อมูลกับไฟล์  data.txt และเมื่อเราใช้งานออบเจกต์ของสตรีมเรียบร้อยแล้ว เราต้องสั่งปิดออบเจกต์ของสตรีมด้วยเมธอด close() เพราะหากเราไม่ได้สั่งปิดออบเจกต์ของสตรีม อาจจะทำให้แหล่งข้อมูลถูกถือครองไว้ (hold) และไม่มีใครมาใช้งานได้ หรืออาจจะเกิดข้อผิดพลาดอื่นๆตามมา สังเกตุในบรรทัดที่ 11 จะเป็นการสั่งปิดออบเจกต์ของสตรีมของการเขียน และในบรรทัดที่ 19 จะเป็นการสั่งปิดออบเจกต์ของสตรีมของการอ่าน

การใช้ Try-with-resources ปิดออบเจกต์ของสตรีม

     อย่างไรก็ตามเป็นไปได้ว่าเราอาจจะไม่ได้สั่งปิดออบเจกต์ของสตรีมอันเนื่องมาจากลืมหรือเกิดข้อผิดพลาดในโปรแกรมก่อนที่จะถึงบรรทัดที่เราสั่งปิดออบเจกต์ของสตรีม ดังนั้นเพื่อหลีกเลี่ยงการไม่ได้ปิดออบเจกต์ของสตรีม เราสามารถใช้รูปแบบ Try-with-resources เพื่อช่วยจัดการเรื่องนี้ได้ การใช้ Try-with-resources จะช่วยจัดการเรื่องปิดการใช้งานออบเจกต์ของสตรีมให้เราเองถึงแม้ว่าจะเกิดข้อผิดพลาดก็ตาม รูปแบบของการใช้ Try-with-resources คือ 

Try (คำสั่งสร้างออบเจกต์ที่เป็นสตรีม;

       คำสั่งสร้างออบเจกต์ที่เป็นสตรีม) {

      คำสั่งอื่นๆ

 }

แพคเกจ java.io

      แพคเกจ java.io เป็น Java API เพื่อใช้จัดการเรื่องการอ่านและเขียนข้อมูลกับ ไฟล์ เครือข่าย บัฟเฟอร์ อาเรย์ ไปป์ System.in System.out System.err แต่ไม่รวมถึงการอ่านและเขียนข้อมูลกับ GUI หรือเว็บเพจ และไม่รวมถึงการเปิดใช้งานซ็อกเก็ต (socket) เพื่อเชื่อมต่อกับเครือข่าย แพคเกจ java.io มีมากับภาษาจาวาตั้งแต่เวอร์ชั่นแรกและมีการพัฒนาเพิ่มเติมในเวอร์ชั่นต่อๆมา

     แนวคิดหลักของการใช้งาน java.io คือการทำงานกับสตรีมของข้อมูล
โดยสตรีมจะเชื่อมต่อกับแหล่งข้อมูล เช่น ไฟล์ หรือเครือข่าย อีกทีหนึ่ง เราใช้ออบเจกต์ในกลุ่ม InputStream / Reader ในการอ่านข้อมูลเข้ามาจากแหล่งข้อมูลต้นทาง (source) และใช้ออบเจกต์ในกลุ่ม OutputStream / Writer ในการเขียนข้อมูลออกไปยังแหล่งข้อมูลปลายทาง (destination) 

      โครงสร้างของคลาสในแพคเกจ java.io สามารถแยกได้เป็นกลุ่มดังนี้

     จากแผนภาพจะเห็นว่าการใช้งานจะถูกแบ่งออกเป็นกลุ่มใหญ่ๆ 4 กลุ่ม คือกลุ่มของคลาส Reader และคลาส Writer ซึ่งเป็นกลุ่มที่ดำเนินการกับสตรีมของ อักขระ และกลุ่มของคลาส InputStream และคลาส OutputStream ซึ่งเป็น

กลุ่มที่ดำเนินการกับสตรีมของไบต์ โดยคลาสลูกของแต่ละกลุ่มถูกสร้างมาเพื่อรองรับความต้องการที่แตกต่างกันไป ส่วนที่เหลือเป็นคลาสที่ทำหน้าที่เฉพาะทางต่างๆ เช่น คลาส File คลาส RandomAccessFIle เป็นต้น เราสามารถนำคลาสในแพคเกจ java.io มาร้อยเรียงเข้าด้วยกันเพื่อเพิ่มความสามารถของการใช้งานสตรีมของข้อมูลดังจะได้เห็นตัวอย่างในหัวข้อต่อๆไป

     ภาพด้านล่างเป็นการแยกแยะแต่ละคลาสตามการใช้งานและชนิดข้อมูล

การอ่านและเขียนสตรีมของอักขระกับไฟล์

     ภาษาจาวาได้จัดเตรียมคลาส java.io.Reader และคลาส java.io.Writer ไว้สำหรับการอ่านและเขียนข้อมูลกับสตรีมของอักขระ โดยใช้เมธอด read() ในการอ่านข้อมูลและใช้เมธอด write() ในการเขียนข้อมูล สำหรับการอ่านและเขียนข้อมูลกับไฟล์เราสร้างออบเจกต์จากคลาสลูกคือคลาส java.io.FileReader และ java.io.FileWriter ซึ่งจะเป็นการสร้างออบเจกต์ของสตรีมสำหรับเชื่อมต่อกับไฟล์ ข้อมูลจะถูกอ่านและเขียนทีละอักขระไปจนจบข้อมูล หากเราต้องการอ่านหรือเขียนแบบเจาะจงตำแหน่งของข้อมูลในไฟล์จะต้องใช้คลาส RandomAccessFile 

     ในการอ่านข้อมูลด้วยเมธอด read() จะได้ค่าส่งกลับมาเป็นข้อมูลชนิด int และเราต้องวนรอบอ่านค่าจนกว่าจะได้ค่าเป็น -1 หมายถึงสิ้นสุดข้อมูลแล้ว

     ตัวอย่างด้านล่างเป็นการเขียนข้อมูลแบบสตรีมของอักขระลงในไฟล์ด้วยคลาส FileWriter และอ่านมาพิมพ์ด้วยคลาส FileReader

     เริ่มจากในบรรทัดที่ 7 เนื่องจากการดำเนินการกับไฟล์เป็น Exception ชนิด checked exception ดังนั้นเราต้องจัดการกับข้อผิดพลาดที่เกิดขึ้น ด้วยการใช้รูปแบบ try-catch-finally หรือใช้คีย์เวิร์ด throws ซึ่งในที่นี่เราใช้คีย์เวิร์ด throws ตามด้วยคลาสข้อผิดพลาดที่ต้องการจัดการ หากเราไม่กำหนดการจัดการให้กับ checked exception โปรแกรม IntelliJ IDEA จะแจ้งข้อผิดพลาดและไม่ยอมคอมไพล์โปรแกรมให้เรา

     ในบรรทัดที่ 9 เป็นการสร้างออบเจกต์จากคลาส FileWriter โดยกำหนดชื่อไฟล์ที่ต้องการเชื่อมต่อ ซึ่งในการใช้งานหากยังไม่มีไฟล์ตามที่กำหนดไว้ ไฟล์จะถูกสร้างขึ้นมาให้ใหม่ และกำหนดค่าเป็น true ซึ่งหมายถึงเขียนข้อมูลต่อท้ายข้อมูลที่มีอยู่เสมอ หากไม่กำหนดหรือกำหนดเป็น false ข้อมูลในไฟล์จะถูกเขียนทับด้วยข้อมูลใหม่ทุกครั้งที่สั่งเขียนไฟล์ ออบเจกต์ FileWriter ที่สร้างขึ้นมาคือสตรีมที่เชื่อมต่อกับไฟล์ data.txt และเรากำหนดออบเจกต์ให้กับตัวแปรที่เป็นชนิดข้อมูลแบบ Writer

     ในบรรทัดที่ 10-12 เป็นการเขียนข้อมูลลงในไฟล์โดยใช้เมธอด write() ซึ่งจะเป็นการเขียนข้อมูลลงในออบเจกต์ FileWriter ซึ่งเป็นสตรีมของอักขระซึ่งจะเขียนข้อมูลลงไฟล์จริงๆอีกทีหนึ่ง

     ในบรรทัดที่ 15 เป็นการสร้างออบเจกต์จากคลาส FileReader โดยระบุชื่อไฟล์ที่ต้องการอ่านข้อมูล ออบเจกต์ FileReader คือสตรีมของอักขระที่ได้จากการอ่านข้อมูลจากไฟล์ การอ่านข้อมูลจากไฟล์จะใช้เมธอด read() ซึ่งจะอ่านข้อมูลทีละอักขระโดยข้อมูลที่อ่านได้จะเป็นเลขจำนวนเต็ม (int) ของแต่ละตัวอักขระ  ในบรรทัด 16 เราอ่านข้อมูลมาเป็นค่าตั้งต้นสำหรับการวนรอบอ่านข้อมูลทั้งหมดจากนั้นในบรรทัดที่ 17-19 เราต้องวนรอบอ่านข้อมูลที่ละอักขระจนกว่าจะพบค่า -1 ซึ่งหมายถึงสิ้นสุดข้อมูลแล้ว และในบรรทัดที่ 18 เราสั่งพิมพ์ข้อมูลโดยการคาสติ้งด้วยชนิดข้อมูลแบบ char เพื่อพิมพ์เป็นตัวอักษรออกมา เพราะค่าที่ได้จากออบเจกต์ FileReader เป็นชนิดข้อมูล int นอกจากนี้เรายังสามารถข้ามข้อมูลที่ไม่ต้องการอ่านได้ด้วยเมธอด skip() โดยระบุจำนวนอักขระที่ต้องการข้าม และเมธอดจะคืนค่ามาเป็นจำนวนอักขระที่ถูกข้ามไปซึ่งควรจะเท่ากับค่าที่เรากำหนด เว้นแต่เรากำหนดค่ามากกว่าอักขระที่มี

     เราสามารถใช้อาเรย์มาช่วยเพิ่มประสิทธิภาพในการอ่านและเขียนข้อมูลโดยใช้เมธอด read(char[ ]) และเมธอด write(char[ ]) ซึ่งการอ่านและเขียนข้อมูลกับไฟล์จะทำเป็นบล็อกตามขนาดของอาเรย์ และเราจะทำงานกับอาเรย์แทนเพื่อช่วยลดเวลาที่ใช้ในการเขียนและอ่านกับไฟล์โดยตรง จากตัวอย่างด้านล่าง บรรทัดที่ 10 เราสร้างอาเรย์ของอักขระขึ้นมาและในบรรทัดที่ 11 เรา เขียนข้อมูลด้วยเมธอด write() โดยระบุชื่ออาเรย์ที่มีข้อมูลที่จะเขียนเป็นพารามิเตอร์ ในบรรทัดที่ 15 เราสร้างอาเรย์มาเพื่อรองรับข้อมูลที่จะอ่านและในบรรทัดที่ 16 เราอ่านข้อมูลด้วยเมธอด read() โดยระบุชื่ออาเรย์ที่จะรับข้อมูลเป็นพารามิเตอร์ จากนั้นจึงวนรอบเพื่ออ่านข้อมูลจากอาเรย์

    ในกรณีที่เราไม่สามารถสร้างอาเรย์มาใช้ได้หรือจำเป็นต้องกำหนดชนิดของตัวแปรเป็นคลาสลูกทำให้ไม่สามารถใช้เมธอด read(char[ ]) หรือ write(char[ ]) ของคลาส Reader หรือ Writer จาวาได้เตรียมคลาส java.io.BufferedReader และ java.io.BufferedWriter เพื่อทำหน้าที่เป็นบัฟเฟอร์ให้กับสตรีมของข้อมูลเพื่อเพิ่มประสิทธิภาพในการอ่านและเขียนข้อมูล ซึ่งบัฟเฟอร์คืออาเรย์ภายในของออบเจกต์ BufferedReader และ BufferedWriter โดยในการทำงานจะเป็นการเขียนข้อมูลลงในออบเจกต์ BufferedReader และ BufferedWriter เมื่อบัฟเฟอร์เต็มจึงจะอ่านหรือเขียนกับสตรีมซึ่งจะอ่านหรือเขียนกับไฟล์อีกทีหนึ่ง เราสามารถใช้ออบเจกต์จากคลาสนี้ไปห่อหุ้มสตรีมของอักขระแบบใดก็ได้เพื่อเพิ่มความสามารถในการบัฟเฟอร์ให้กับสตรีมของอักขระ ในกรณีที่สามารถกำหนดชนิดของตัวแปรเป็นคลาสแม่ได้เราสามารถใช้  read(char[ ]) หรือ write(char[ ]) กับบัฟเฟอร์ได้เช่นเดียวกับที่ใช้งานกับสตรีม

     การใช้เมธอด read() ในการอ่านข้อมูลจะเป็นการอ่านข้อมูลทีละตัวอักษร ซึ่งจะไม่สะดวกในกรณีที่ข้อมูลมีจำนวนมากหลายๆบรรทัด เราสามารถใช้ออบเจกต์ Scanner มาช่วย ซึ่งที่ผ่านมาเราใช้ออบเจกต์ Scanner โดยมี System.in เป็นแหล่งข้อมูล แต่ในครั้งนี้เราจะใช้ออบเจกต์ BufferedReader เป็นแหล่งข้อมูล ดังตัวอย่างด้านล่าง ในบรรทัดที่ 15 เราสร้างออบเจกต์ Scanner ขึ้นมาโดยใช้ออบเจกต์ BufferedReader เป็นแหล่งข้อมูล ซึ่งจะช่วยให้เราจัดการข้อมูลได้ง่ายขึ้น เพราะออบเจต์ Scanner ทำงานโดยใช้ regular expression เราจึงสามารถจัดการกับข้อความได้ง่ายขึ้น 

     คลาส BufferedReader ก็มีเมธอดที่สามารถอ่านข้อความเป็นบรรทัดคือ readLine() ซึ่งจะคืนค่าเป็นข้อความ 1 บรรทัด และยังมีเมธอด skip() ให้เราข้ามอักขระที่ไม่ต้องการ ส่วนเมธอด read() เป็นการอ่านครั้งละอักขระ ดังนั้นหากไม่ต้องการใช้ความสามารถในการจัดการอักขระด้วย regular expression ของออบเจกต์ Scanner ก็สามารถใช้ออบเจกต์ BufferedReader โดยตรงได้ ดังตัวอย่างด้านล่างเป็นการอ่านไฟล์โดยใช้เมธอด readLine()

     เราสามารถแปลงอาเรย์ของอักขระ (char array) เป็นสตรีมของอักขระได้โดยใช้คลาส CharArrayReader และแปลงจากสตรีมของอักขระเป็นอาเรย์ของอักขระโดยใช้คลาส CharArrayWriter ตัวอย่างด้านล่าง บรรทัดที่ 9 เป็นการสร้างออบเจกต์ CharArrayWriter บรรทัดที่ 10 เขียนข้อมูลลงสตรีม บรรทัดที่ 11 แปลงข้อมูลจากสตรีมออกมาเป็นอาเรย์ด้วยเมธอด toCharArray() บรรทัดที่ 13 เป็นการสร้างออบเจกต์ CharArrayReader และกำหนดให้อ่านข้อมูลจากอาเรย์เข้ามาในสตรีม ในบรรทัดที่  15-17 เป็นการวนรอบอ่านข้อมูลจากสตรีม 

     ถ้าเราต้องการแปลงข้อมูลจากสตรีมของไบต์มาเป็นรูปแบบสตรีมของอักขระเราใช้คลาส InputStreamReader ส่วนการแปลงจากสตรีมของอักขระไปเป็นสตรีมของไบต์ เราใช้คลาส OutputStreamWriter จากตัวอย่างด้านล่าง ในบรรทัดที่ 9 เราสร้างออบเจกต์ที่เป็นสตรีมของไบต์ขึ้นมาซึ่งเชื่อมกับไฟล์ data.dat จากนั้นในบรรทัดที่ 10 เราห่อหุ้มออบเจกต์ที่เป็นสตรีมของไบต์ด้วยออบเจกต์ OutputStreamWriter ในบรรทัดที่ 11 อักขระที่เราเขียนด้วยเมธอด write() จะถูกแปลงด้วยออบเจกต์ OutputStreamWriter เป็นไบต์เพื่อเขียนในสตรีมของไบต์ ในการอ่านข้อมูลก็เช่นเดียวกัน ในบรรทัดที่ 14 เราสร้างออบเจกต์ที่เป็นสตรีมของไบต์ขึ้นมาซึ่งเชื่อมกับไฟล์ data.dat ในบรรทัดที่ 15 เราห่อหุ้มออบเจกต์ที่เป็นสตรีมของไบต์ด้วยออบเจกต์ InputStreamReader ในบรรทัดที่ 16-19 ข้อมูลที่อ่านมาจากสตรีมของไบต์จะถูกแปลงโดยออบเจกต์ InputStreamReader มาเป็นสตรีมของอักขระ

     การอ่านข้อมูลด้วยเมธอด read() จะทำให้ตัวชี้ตำแหน่งเลื่อนไปยังตำแหน่ง ถัดไป แต่บางครั้งเราเพียงแค่ต้องการอ่านข้อมูลล่วงหน้ามาเพื่อตัดสินใจว่าจะทำอย่างไรกับข้อมูล และต้องการอ่านข้อมูลนั้นอีกครั้งในการดำเนินการครั้งต่อไป เราสามารถทำได้โดยใช้คลาส PushBackReader โดยสร้างออบเจกต์ PushBackReader ซึ่งจะทำหน้าที่่เป็นบัฟเฟอร์ที่คอยจดจำการอ่านข้อมูล โดยอ่านข้อมูลด้วยเมธอด read() และส่งข้อมูลกลับไปที่สตรีมด้วยเมธอด unread() 

    จากตัวอย่างด้านล่าง บรรทัดที่ 9 เป็นการสร้างออบเจกต์ PushBackReader ไปห่อหุ้มสตรีม FileReader ไว้และกำหนดขนาดของบัฟเฟอร์เป็น 8 ตัวอักขระ เราใช้เมธอด read() ในการอ่านข้อมูลและใช้เมธอด unread() ในการเลื่อนตัวชี้ตำแหน่งย้อนกลับไป

     เราสามารถใช้คลาส LineNumberReader ในการอ่านสตรีมอักขระและเลขที่บรรทัด โดยเลขที่บรรทัดจะเริ่มจาก 0 และเพิ่มขึ้นเมื่อพบอักขระสิ้นสุดบรรทัด (return character) เลขที่บรรทัดอ่านโดยใช้เมธอด getLineNumber() และเราสามารถกำหนดเลขที่บรรทัดด้วยเมธอด setLineNumber() 

     เราสามารถอ่านสตรีมของอักขระเป็นคำๆได้โดยใช้คลาส StreamTokenizer โดยข้อมูลที่ได้สำหรับแต่ละคำประกอบด้วย ttype คือชนิดของคำว่าเป็นข้อความหรือตัวเลขหรืออักขระสิ้นสุดบรรทัด sval คือข้อมูลที่เป็นข้อความ และ nval คือข้อมูลที่เป็นตัวเลข จากตัวอย่างด้านล่าง บรรทัดที่ 9 เป็นการสร้างออบเจกต์ StreamTokenizer โดยระบุไฟล์ที่ต้องการอ่าน บรรทัดที่ 10 เป็นการอ่านแต่ละคำด้วยเมธอด nextToken() เราอ้างอิงว่าคำที่อ่านมาเป็นข้อความหรือตัวเลขหรืออักขระสิ้นสุดบรรทัดโดยใช้ฟิลด์ ttype เพื่อเปรียบเทียบกับฟิลด์ TT_EOF  TT_WORD หรือ TT_NUMBER และอ่านค่าที่ต้องการจากฟิลด์ sval และ nval

     เราสามารถจัดรูปแบบสิ่งที่จะเขียนได้โดยใช้คลาส PrintWriter โดยใช้เมธอด format() หรือ printf() ในการจัดรูปแบบตามตัวอย่างด้านล่างในบรรทัดที่ 15

     เราสามารถแปลงข้อความไปเป็นสตริงของอักขระโดยใช้คลาส StringReader ดังตัวอย่างด้านล่าง และสามารถแปลงสตริงของอักขระไปเป็นข้อความโดยใช้คลาส StringWriter

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

การอ่านและเขียนสตรีมของไบต์กับไฟล์

     ภาษาจาวาได้จัดเตรียมคลาส java.io.InputStream และคลาส java.io.OutputStream ไว้สำหรับการอ่านและเขียนข้อมูลในแบบสตรีมของไบต์ โดยใช้เมธอด read() ในการอ่านข้อมูลและใช้เมธอด write() ในการเขียนข้อมูล สำหรับการอ่านและเขียนข้อมูลกับไฟล์เราสร้างออบเจกต์จากคลาสลูกคือคลาส java.io.FileInputStream และ java.io.FileOutputStream ซึ่งจะเป็นการสร้างออบเจกต์ของสตรีมสำหรับเชื่อมต่อกับไฟล์  โดยข้อมูลจะถูกอ่านและเขียนทีละไบต์ไปจนจบข้อมูล หากเราต้องการอ่านหรือเขียนแบบเจาะจงตำแหน่งของข้อมูลในไฟล์จะต้องใช้คลาส RandomAccessFile 

     ในการอ่านข้อมูลด้วยเมธอด read() จะได้ค่าส่งกลับมาเป็นข้อมูลชนิด int และเราต้องวนรอบอ่านค่าจนกว่าจะได้ค่าเป็น -1 หมายถึงสิ้นสุดข้อมูลแล้ว 

     จากตัวอย่างด้านล่างบรรทัดที่ 9 เป็นการสร้างออบเจกต์ FileOutputStream และกำหนดให้กับตัวแปรที่เป็นชนิดข้อมูล OutputStream โดยกำหนดชื่อไฟล์ที่ต้องการเขียนข้อมูลและกำหนดทางเลือกเป็น true หมายถึงเขียนข้อมูลต่อจากข้อมูลที่มีอยู่ ซึ่งหากไม่กำหนดหรือกำหนดเป็น false จะเป็นการเขียนข้อมูลทับข้อมูลเดิม และในบรรทัดที่ 10-12 เป็นการเขียนข้อมูลด้วยเมธอด write()

     ในบรรทัดที่ 15 เป็นการสร้างออบเจกต์ FileInputStream และกำหนดให้กับตัวแปรที่เป็นชนิดข้อมูล  InputStream โดยกำหนดชื่อไฟล์ที่ต้องการอ่านข้อมูล

ในบรรทัดที่ 16-19 เป็นการวนรอบอ่านข้อมูลทีละไบต์จนหมดข้อมูล

     เราสามารถใช้อาเรย์มาช่วยเพิ่มประสิทธิภาพในการอ่านและเขียนข้อมูลโดยใช้เมธอด read(byte[ ]) หรือเมธอด readAllBytes() และเมธอด 

write(byte[ ]) ซึ่งจะเป็นการอ่านและเขียนข้อมูลกับไฟล์จะทำเป็นบล็อกตามขนาดของอาเรย์ และเราจะทำงานกับอาเรย์แทนเพื่อช่วยลดเวลาที่ใช้ในการเขียนและอ่านกับแหล่งข้อมูล 

     จากตัวอย่างด้านล่าง บรรทัดที่ 10 เราสร้างอาเรย์ของข้อมูลที่ต้องการเขียนขึ้นมาและในบรรทัดที่ 11 เป็นการสั่งเขียนข้อมูลจากอาเรย์เป็นบล็อกลงในแหล่งข้อมูล ในบรรทัดที่ 15 เราสร้างอาเรย์มารอรับข้อมูล ในบรรทัดที่ 16 อ่านข้อมูลจากแหล่งข้อมูลทั้้งหมดมาไว้ในอาเรย์ด้วยเมธอด readAllBytes() จากนั้นก็จะเป็นการอ่านข้อมูลจากอาเรย์เท่านั้น ในทำนองเดียวกันในบรรทัดที่ 24-29 ก็เป็นการอ่านข้อมูลมาไว้ในอาเรย์ที่มีขนาดจำกัด จึงต้องวนรอบเอาข้อมูลไปใช้ก่อนจะอ่านข้อมูลเข้ามาใหม่จนกว่าจะหมดข้อมูล 

     ในกรณีที่เราไม่สามารถสร้างอาเรย์มาใช้ได้หรือจำเป็นต้องกำหนดชนิดข้อมูลเป็นคลาสลูกทำให้ไม่สามารถใช้เมธอด read(byte[ ]) หรือ write(byte[ ]) ของคลาส InputStream หรือ OutputStream จาวาได้เตรียมคลาส java.io.BufferedInputStream และ java.io.BufferedOutputStream เพื่อทำหน้าที่เป็นบัฟเฟอร์ให้กับสตรีมของข้อมูลเพื่อเพิ่มประสิทธิภาพในการอ่านและเขียนข้อมูล ซึ่งบัฟเฟอร์คืออาเรย์ภายในของออบเจกต์ BufferedInputStream และ BufferedOutputStream โดยในการทำงานจะเป็นการเขียนข้อมูลลงในออบเจกต์ BufferedInputStream และ BufferedOutputStream ทีละไบต์และเมื่อบัฟเฟอร์เต็มจึงจะอ่านหรือเขียนกับสตรีมซึ่งจะอ่านหรือเขียนกับแหล่งข้อมูลอีกทีหนึ่ง 

     จากตัวอย่างด้านล่าง ในบรรทัดที่ 9 และบรรทัดที่ 15 เป็นการห่อหุ้มออบเจกต์สตรีมคือ FileInputStream และ FileOutputStream ด้วยออบเจกต์ที่ทำหน้าที่บัฟเฟอร์คือ BufferedInputStream และ BufferedOutputStream 

     จากตัวอย่างที่ผ่านมาเราสร้างออบเจกต์ด้วยคลาสลูกอย่าง FileInputStream หรือ FileOutputStream แต่กำหนดให้กับตัวแปรที่มีชนิดข้อมูลเป็นคลาสแม่คือ InputStream หรือ OutputStream  ซึ่งการกำหนดชนิดข้อมูลด้วยคลาสแม่จะทำให้โปรแกรมของเรายืดหยุ่นในการใช้งานกับแหล่งข้อมูลได้หลายแบบ แต่ก็จะทำให้ไม่สามารถใช้เมธอดเฉพาะทางของคลาสลูกได้ จึงขึ้นอยู่กับเราว่าจะออกแบบโปรแกรมอย่างไร หรือขึ้นอยู่กับโปรแกรมที่เราต้องไปเชื่อมต่อว่ารับและส่งข้อมูลแบบใด

     หากเราต้องการใช้เมธอดของคลาสลูกเราจะต้องกำหนดชนิดข้อมูลของตัวแปรเป็นคลาสลูกด้วย ตัวอย่างเช่น ในกรณีที่เราต้องการแปลงข้อมูลที่เป็น primary data type ไปเป็นสตรีมของไบต์เราจะใช้คลาส DataOutputStream และใช้คลาส DataInputStream ในการแปลงข้อมูลในรูปแบบสตรีมของไบต์กลับมาเป็นข้อมูลที่เป็น primary data type แต่การอ่านข้อมูลด้วย DataInputStream เราจะแยกไม่ได้ว่าค่า -1 ที่อ่านมาคือค่า -1 หรือหมายถึงสิ้นสุดข้อมูล ดังนั้นเราต้องรู้โครงสร้างของข้อมูลที่จะอ่าน โดยมากเราใช้ออบเจกต์ DataInputStream เพื่ออ่านข้อมูลที่เขียนโดย DataOutputStream

     จากตัวอย่างด้านล่างเราจะกำหนดรูปแบบของไฟล์ไบนารี่ของเราคือเป็นข้อมูลแบบ Integer ตามด้วยข้อมูลแบบ String สำหรับการเขียนข้อมูลโดยใช้คลาส DataOutputStream เราจะใช้เมธอด writeInt() เพื่อเขียนข้อมูลที่เป็น Integer และตามด้วยเมธอด writeUFT() เพื่อเขียนข้อมูลที่เป็น String และในการอ่านข้อมูลโดยใช้คลาส DataInputStream เราใช้เมธอด readInt() ตามด้วย readUTF() เพื่ออ่านข้อมูลกลับมาเป็น primary data type ส่วนออบเจกต์ที่เราสร้างขึ้นจะใช้ DataInputStream / DataOutputStream ที่ทำหน้าที่ในการแปลงข้อมูลระหว่าง primitive data type กับสตรีมของไบต์ เอาไปห่อหุ้ม BufferedInputStream / BufferedOutputStream ที่ทำหน้าที่บัฟเฟอร์ ซึ่งห่อหุ้ม FileInputStream / FileOutoutStream ที่เป็นสตรีมอีกทีหนึ่ง ทำให้เราสามารถแปลงข้อมูลที่เป็น primary data type ไปเป็นสตรีมของไบต์และอ่าน/เขียนกับบัฟเฟอร์ก่อนที่จะเขียนลงไฟล์

     สำหรับเมธอดอื่นๆที่ใช้อ่านและเขียนข้อมูลแบบ primitive data type สามารถดูได้จากคู่มือ Java API ของคลาส DataInputStream และคลาส DataOutputStream

     เราสามารถใช้คลาส ByteArrayInputStream เพื่อแปลงอาเรย์ของไบต์มาเป็นสตรีมของไบต์ โดยกำหนดให้อาเรย์ของไบต์เป็นแหล่งข้อมูลของออบเจกต์ ByteArrayInputStream จากนั้นเราจึงอ่านข้อมูลจากออบเจกต์ ByteArrayInputStream ซึ่งเป็นสตรีมของไบต์ด้วยเมธอด read() ในทางกลับกันเราสามารถใช้คลาส ByteArrayOutputStream เพื่อแปลงสตรีมของไบต์มาเป็นอาเรย์ของไบต์ โดยสร้างออบเจกต์ ByteArrayOutputStream ขึ้นมารับข้อมูลที่เราจะเขียนลงในสตรีมของไบต์ด้วยเมธอด write() และใช้เมธอด toByteArray() เพื่อแปลงจากสตรีมของไบต์ไปเป็นอาเรย์ของไบต์ เราสามารถตรวจสอบได้ว่ามีข้อมูลเหลืออยู่อีกกี่ไบต์ในออบเจกต์ ByteArrayInputStream หลังจากอ่านมาแล้วบ้างด้วยเมธอด available()

     จากตัวอย่างด้านล่าง บรรทัดที่ 9 เราสร้างอาเรย์ของไบต์ขึ้นมาและกำหนดให้กับออบเจกต์ ByteArrayInputStream ในบรรทัดที่ 10 ส่วนในบรรทัดที่ 11-14 เป็นการอ่านข้อมูลจากสตรีมของไบต์

     สำหรับการเขียนข้อมูล เราสร้างออบเจกต์ ByteArrayOutputStream เพื่อมารับข้อมูลในบรรทัดที่ 18 และเขียนข้อมูลลงสตรีมของไบต์ในบรรทัดที่ 19-21 จากนั้นจึงแปลงสตรีมของไบต์เป็นอาเรย์ของไบต์ในบรรทัดที่ 22

     การอ่านข้อมูลด้วยเมธอด read() จะทำให้ตัวชี้ตำแหน่งเลื่อนไปยังตำแหน่ง ถัดไป แต่บางครั้งเราเพียงแค่ต้องการอ่านข้อมูลล่วงหน้ามาเพื่อตัดสินใจว่าจะทำอย่างไรกับข้อมูล และต้องการอ่านข้อมูลนั้นอีกครั้งในการดำเนินการครั้งต่อไป เราสามารถทำได้โดยใช้คลาส PushBackInputStream โดยสร้างออบเจกต์ PushBackInputStream ซึ่งจะทำหน้าที่่เป็นบัฟเฟอร์ที่คอยจดจำการอ่านข้อมูล โดยอ่านข้อมูลด้วยเมธอด read() และส่งข้อมูลกลับไปที่สตรีมด้วยเมธอด unread() 

     จากตัวอย่างด้านล่าง บรรทัดที่ 9-11 เป็นการเขียนข้อมูลจำนวน 5 ไบต์ลงในไฟล์ data.dat และในบรรทัดที่ 14 เราห่อหุ้มออบเจกต์ FileInputStream ด้วยออบเจกต์ PushBackInputStream และกำหนดขนาดของบัฟเฟอร์ที่สามารถเก็บข้อมูลได้จำนวน 5 ไบต์ บรรทัดที่ 16 เป็นการอ่านข้อมูลใส่อาเรย์และในบรรทัดที่ 18 เป็นการใส่ข้อมูลกลับไปที่สตรีม

     เราสามารถนำข้อมูลจาก 2 สตรีมของไบต์มารวมกันได้โดยใช้คลาส SequenceInputStream ทำหน้าที่จัดลำดับในการอ่านสตรีม โดยอ่านข้อมูลจากจากสตรีมหนึ่งจนหมดก่อนแล้วจึงอ่านข้อมูลจากอีกสตรีมนึง จากตัวอย่างด้านล่าง บรรทัดที่ 9-10 เป็นการสร้างสตรีมของไบต์ 2 สตรีมจากแต่ละไฟล์ บรรทัดที่ 11 เป็นการสร้างออบเจกต์ SequenceInputStream โดยกำหนดให้อ่านสตรีม input1 ก่อนตามด้วยสตรีม input2 จากนั้นจึงอ่านข้อมูลด้วยเมธอด read() ซึ่งจะส่งค่า -1 เมื่ออ่านข้อมูลสตรีมที่ 2 จนหมด

     หากเรามีสตรีมของไบต์ที่ต้องการอ่านข้อมูลมารวมกันมากกว่า 2 สตรีมเราสามารถใช้ออบเจกต์ SequenceInputStream ซ้อนกันได้ เช่น

     หรือใช้ชนิดข้อมูลแบบ Vector ช่วย เช่น

     เราสามารถจัดรูปแบบของสิ่งที่ต้องการเขียนในไฟล์ด้วยคลาส PrintStream โดยใช้เมธอด printf() ตัวอย่างเช่น

     เราสามารถระบุตำแหน่งของข้อมูลในไฟล์ที่ต้องการอ่านหรือเขียนได้โดยใช้คลาส RandomAccessFile โดยการอ่านและเขียนข้อมูลแบบระบุตำแหน่งข้อมูลจะมีเฉพาะการอ่านและเขียนข้อมูลกับไฟล์เท่านั้น ส่วนแหล่งข้อมูลแบบอื่นจะรองรับการทำงานแบบสตรีมของข้อมูลเท่านั้น การใช้งานจะเป็นการสร้างออบเจกต์ RandomAccessFile โดยกำหนดชื่อไฟล์ที่ต้องการและทางเลือกในการดำเนินการกับไฟล์คือ r (อ่านอย่างเดียว) rw (อ่านและเขียน) rwd (อ่านและเขียนข้อมูลได้พร้อมกัน) rws (อ่านและเขียนข้อมูลและ meta-data ของไฟล์ได้พร้อมกัน)

     คลาส RandomAccessFile จะมีตัวชี้ตำแหน่งของข้อมูลซึ่งการระบุตำแหน่งของข้อมูลเป็นการบอกตำแหน่งของไบต์ที่เราต้องการอ่านหรือเขียนข้อมูล โดยตำแหน่งแรกสุดที่ต้นไฟล์คือตำแหน่งที่ 0 เราสามารถอ่านค่าตำแหน่งปัจจุบันของตัวชี้ตำแหน่งได้ด้วยเมธอด getFilePointer() ถ้าเราต้องการไปยังตำแหน่งที่ต้องการจะใช้เมธอด seek(number) โดยระบุตำแหน่งของไบต์ที่ต้องการ การอ่านข้อมูลทีละไบต์จะใช้เมธอด read() ซึ่งเมื่ออ่านข้อมูลแล้วตัวชี้ตำแหน่งจะเลื่อนไปยังตำแหน่งถัดไป หากต้องการอ่านข้อมูลเป็นบล็อกเพื่อเก็บในอาเรย์เราใช้เมธอด read(byte[ ], offset, length) โดย byte[ ] คืออาเรย์ที่กำหนดไว้ offset คือตำแหน่งของอาเรย์ต้องการเขียนข้อมูลลงไป และ length คือจำนวนไบต์ที่จะเขียนลงในอาเรย์ 

    สำหรับการเขียนข้อมูลทีละไบต์ลงในตำแหน่งปัจจุบันของตัวชี้ตำแหน่งใช้เมธอด write() โดยข้อมูลใหม่จะเขียนทับข้อมูลเดิมและตัวชี้ตำแหน่งจะเลื่อนไปยังตำแหน่งถัดไป เราสามารถเขียนข้อมูลจากอาเรย์ลงในตำแหน่งปัจจุบันของตัวชี้ตำแหน่งด้วยเมธอด write(byte[ ]) หรือ write(byte[ ], offset, length) ซึ่งระบุตำแหน่งและข้อมูลในอาเรย์ที่ต้องการเขียนข้อมูล

     ตามที่ภาษาจาวามีสตรีมมาตรฐานให้เราใช้งานคือ System.in System.out และ System.err ซึ่งทั้ง 3 สตรีมจะถูกเปิดใช้งานเมื่อ JVM ทำงาน ทั้ง 3 สตรีมเป็นสตรีมของไบต์โดย System.in ก็คือ InputStream ส่วน System.out และ System.err ก็คือ PrintStream เราสามารถเปลี่ยนแหล่งข้อมูลของทั้ง 3 สตรีมได้ด้วยการสร้างออบเจกต์ InputStream หรือออบเจกต์ PrintStream ขึ้นมาและกำหนดให้กับแต่ละสตรีมด้วยเมธอด System.setIn() System.setOut() และ System.setErr() ตัวอย่างเช่น 

การอ่านและเขียนออบเจกต์ลงในไฟล์

     จากที่ผ่านมาเราเขียนข้อมูลที่เป็น primitive data type โดยใช้เมธอด writeInt() และ writeUTF() จากคลาส java.io.DataOutputrStream ซึ่งมองได้ว่าเป็นการเขียนข้อมูลลงในไฟล์ทีละฟิลด์ ดังนั้นในกรณีที่เรามี 20 ฟิลด์เราต้องสั่งเขียนข้อมูล 20 ครั้ง แต่ถ้าเราเอาทั้ง 20 ฟิลด์ไปใส่ไว้ในออบเจกต์แล้วสั่งเขียนออบเจกต์ลงไฟล์แทน ก็จะเป็นการสั่งเขียนออบเจกต์เพียงครั้งเดียว แต่การเขียนออบเจกต์ลงในไฟล์เราต้องแปลงรูปแบบของออบเจกต์เป็นสตรีมของไบต์ก่อนจึงจะเขียนลงในไฟล์ได้ และเมื่ออ่านข้อมูลกลับมาค่อยนำมาแปลงกลับมาเป็นออบเจกต์เพื่อใช้งาน ขั้นตอนในการแปลงโครงสร้างข้อมูลหรือออบเจกต์นี้เรียกว่า Serialization 

     ภาษาจาวามีคลาสที่ให้เราใช้อ่านและเขียนออบเจกต์ลงในไฟล์คือคลาส java.io.ObjectInputStream และ java.io.ObjectOutputStream โดยออบเจกต์ที่เราจะเขียนลงไปจะต้อง implements คลาส java.io.Serializable ด้วยจึงจะสามารถเขียนลงในไฟล์ได้ และหากคลาสของเรามีฟิลด์ที่เป็นออบเจกต์ ออบเจกต์นั้นๆก็ต้อง implements คลาส Serializable ด้วยเช่นกัน การ implements คลาส Serializable คือการบอก java virtual machine ว่าเราจะแปลงออบเจกต์เป็นสตรีมของไบต์

     คลาส java.io.Serializable เป็นคลาสแบบอินเตอร์เฟสชนิดมาร์คเกอร์ (marker interface) หมายถึงเป็นคลาสแบบอินเตอร์เฟสที่ไม่มีเมธอด

     ตัวอย่างด้านล่างเป็นการสร้างออบเจกต์จากคลาส Car และเขียนออบเจกต์ Car ลงในไฟล์

     จากตัวอย่างในบรรทัดที่ 33 เป็นการประกาศคลาส Car โดยจะเห็นว่ามีการ implements คลาส Serializable ด้วย และฟิลด์ในคลาส Car คือออบเจกต์ String ก็  implements คลาส Serializable อยู่แล้ว 

     และส่วนสำคัญอีกส่วนคือบรรทัดที่ 37 เป็นการกำหนดเวอร์ชั่นของสตรีม ของออบเจกต์เรียกว่า serial version id โดยต้องกำหนดในรูปแบบ 

private static long SerialVersionUID = version_numberL;

     โดย version_number คือเลขที่เวอร์ชั่นของโปรแกรม หากเราไม่กำหนดฟิลด์นี้ คอมไพเลอร์จะกำหนดค่านี้ให้ซึ่งอาจจะแตกต่างกันไปในแต่ละครั้งที่คอมไพล์โปรแกรมและอาจจะเกิดปัญหา InvalidClassException เมื่อมีการอ่านออบเจกต์จากไฟล์มาใช้งาน ดังนั้นเราควรต้องกำหนดค่านี้เองทุกครั้งและเมื่อมีการปรับปรุงโปรแกรมเราก็ควรที่จะเปลี่ยนเลขที่เวอร์ชั่นด้วย เลขที่เวอร์ชั่นนี้ใช้เปรียบเทียบว่าเวอร์ชั่นของออบเจกต์ที่จะ deserialization ตรงกันกับที่ใช้งานอยู่ เช่น เราเขียนออบเจกต์เก็บไว้ในไฟล์ แล้วเรามีการปรับปรุงโปรแกรมและเปลี่ยนเลขที่เวอร์ชั่นของโปรแกรม หากเราไปอ่านออบเจกต์จากไฟล์เก่ามาใช้งานซึ่งเป็นคนละเวอร์ชั่นจะใช้งานไม่ได้ช่วยป้องกันไม่ให้เกิดปัญหาตามมา

     ในเมธอด main บรรทัดที่ 8 – 10 เราสร้างออบเจกต์ Car ขึ้นมา บรรทัดที่ 11 เป็นการสร้างออบเจกต์ ObjectOutputStream ขั้นมาเพื่อทำหน้าที่แปลงออบเจกต์เป็นสตรีมของไบต์โดยห่อหุ้มออบเจกต์ BufferedOutputStream ซึ่งทำหน้าที่บัฟเฟอร์ซึ่งห่อหุ้มออบเจกต์ FileOutputStream ซึ่งเป็นสตรีมที่เชื่อมกับไฟล์ car.dat อีกทีหนึ่ง บรรทัดที่ 12-14 ป็นการเขียนออบเจกต์ลงในไฟล์ด้วยเมธอด writeObject()

     ในบรรทัดที่ 16 ป็นการสร้างออบเจกต์ ObjectInputStream ขึ้นมาเพื่อทำหน้าที่แปลงสตรีมของไบต์เป็นออบเจกต์โดยห่อหุ้มออบเจกต์ BufferedInputStream ซึ่งทำหน้าที่บัฟเฟอร์ซึ่งห่อหุ้มออบเจกต์ FileInputStream ซึ่งเป็นสตรีมที่เชื่อมกับไฟล์ car.dat อีกทีหนึ่ง บรรทัดที่ 18-28 เป็นการวนรอบเพื่ออ่านออบเจกต์โดยใช้เมธอด readObject() 

     เนื่องจากการอ่านข้อมูลแต่ละครั้งจะได้ 1 ออบเจกต์ หากเราต้องการวนรอบอ่านข้อมูลออบเจกต์ทั้งหมดในไฟล์เราจะกำหนดการวนรอบและประยุกต์ใช้รูปแบบ Try-catch-finally เพื่อตรวจสอบข้อผิดพลาด EOFException ในการหยุด การทำงานของการวนรอบ ดังตัวอย่างในบรรทัดที่ 23 ซึ่งเมื่อการอ่านข้อมูลวนรอบอ่านข้อมูลจนหมดแล้วการอ่านรอบต่อไปจะเกิดข้อผิดพลาด EOFException และโปรแกรมจะทำงานตามที่กำหนดในบล็อก catch ซึ่งเป็นการกำหนดให้ตัวแปร eof เป็น true เพื่อหยุดการทำงานของการวนรอบ

     สังเกตุว่าไฟล์ car.dat จะอ่านไม่รู้เรื่องเนื่องจากเป็นไบนารี่ไฟล์ที่ได้จากการเขียนออบเจกต์ Car ไปเก็บไว้

การจัดการไฟล์และไดเร็คทอรี่

     คลาส java.io.File เป็นคลาสที่ใช้สร้างออบเจกต์ File ซึ่งเป็นภาพจำลอง (abstract) ของไฟล์หรือไดเร็คทอรี่จริงๆที่เราจะใช้งาน (เมื่อโปรแกรมเราทำงานกับภาพจำลองนี้ ออบเจกต์ File จะไปดำเนินการกับไฟล์จริงๆผ่าน JVM)  ไฟล์หรือไดเร็คทอรี่ที่เรากำหนดให้กับออบเจกต์อาจจะไม่มีอยู่จริงก็ได้โดยที่จะไม่เกิดข้อผิดพลาดในขั้นตอนการสร้างออบเจกต์ทั้งนี้เพื่อรองรับการตรวจสอบว่ามีไฟล์หรือไดเร็คทอรี่หรือไม่ ออบเจกต์ File ช่วยให้เราจัดการกับไฟล์หรืออ่านข้อมูลเกี่ยวกับไฟล์หรือไดเร็คทอรี่ (meta data) เท่านั้น แต่ไม่รวมถึงการอ่านหรือเขียนข้อมูลในไฟล์

     เราสร้างออบเจกต์ File ด้วยเมธอดคอนสตรัคเตอร์ดังตัวอย่างด้านล่าง โดยตัวแปรกำหนดจะเป็นไฟล์หรือไดเร็คทอรี่ขึ้นอยู่กับว่าเราต้องการจัดการไฟล์หรือไดเร็คทอรี่ ตัวอย่างเช่นในบรรทัดที่ 9 กำหนดเป็นไฟล์ บรรทัดที่ 10 และ 11 กำหนดเป็นไดเร็คทอรี่

    ในการจัดการไฟล์ เรากำหนดชื่อไฟล์ในขั้นตอนการสร้างออบเจกต์ File และใช้เมธอด createNewFile() เพื่อสร้างไฟล์ ตรวจสอบการมีอยู่ของไฟล์ด้วยเมธอด exists() ตรวจสอบขนาดไฟล์ด้วยเมธอด length() แก้ไขชื่อไฟล์และย้ายไฟล์ไปยังไดเร็คทอรี่อื่นด้วยเมธอด renameTo() ตรวจสอบว่าเป็นไฟล์หรือไม่ด้วยเมธอด isFile() ซึ่งจะได้ผลลัพธ์เป็น true หรือ false และลบไฟล์ด้วยเมธอด delete() 

     ในการจัดการไดเร็คทอรี่ เรากำหนดชื่อไดเร็คทอรี่ ในขั้นตอนการสร้างออบเจกต์ File และใช้เมธอด mkdir() เพื่อสร้างไดเร็คทอรี่หรือใช้เมธอด mkdirs() ในกรณีที่มีการสร้างไดเร็คทอรี่เป็นโครงสร้างซ้อนกันหลายชั้นในคราวเดียวกัน ตรวจสอบการมีอยู่ของไดเร็คทอรี่ด้วยเมธอด exists() โดยจะได้ผลลัพธ์เป็น true หรือ false แก้ไขชื่อไดเร็คทอรี่และย้ายไดเร็คทอรี่ด้วยเมธอด renameTo() ตรวจสอบว่าเป็นไดเร็คทอรี่หรือไม่ด้วยเมธอด isDirectory() ซึ่งจะได้ผลลัพธ์เป็น true หรือ false ลบไดเร็คทอรี่จะใช้เมธอด delete() แต่การสั่งลบไดเร็คทอรี่นั้นภายใต้ไดเร็คทอรี่จะต้องไม่มีไฟล์อยู่ เราสามารถเขียนโปรแกรมแบบรีเคอร์ซีฟเพื่อวนลบไฟล์และไดเร็คทอรี่ได้ดังตัวอย่างด้านล่าง

     เราสามารถอ่านรายชื่อไฟล์หรือไดเร็คทอรี่ด้วยเมธอด list() ซึ่งจะได้ผลลัพธ์เป็นอาเรย์ของสตริงที่เป็นชื่อไฟล์หรือไดเร็คทอรี่ เช่น กำหนดไดเร็คทอรี่เป็น “.” ในการสร้างออบเจกต์ File เพื่อระบุว่าเป็นไดเร็คทอรี่ปัจจุบันที่โปรแกรมทำงานอยู่ เป็นต้น