You are on page 1of 30

intro. to Java (FEU.

faa)
ในบทที่แปดนี้เราจะพูดถึงการอาน และเขียนขอมูลผานทาง stream หรือชองทางการสงขอมูล
เราจะดูถึงวิธีการ กระบวนการตาง ๆ ที่เกี่ยวของกับขอมูลนําเขา และการนําขอมูลออก

หลังจากจบบทนี้แลว ผูอานจะไดทราบถึง

o ความหมายของ stream
o Class ตาง ๆ ที่ Java มีใหในการประมวลผลดวย stream
o การสราง directory
o การสราง file การเปดและปด file การอานและเขียน file
o ขอแตกตางระหวาง text file และ binary file

ในการทํางานที่เกี่ยวของกับไฟลใน Java นั้นจะตองทําผานทาง stream ซึ่งเปนตัวแทนของ


อุปกรณที่นําเขา (input) หรือสงออก (output) ขอมูล เราสามารถเขียน และอานขอมูลผานทาง
stream ดวยวิธก ี ารตาง ๆ ในรูปแบบตาง ๆ กัน เชน อานขอมูลจาก keyboard ทีละหนึ่งตัวอักษร
หรือทีละ 10 byte

โดยทั่วไป ขอมูลนําเขาจะมาจาก keyboard หนวยความจําสํารอง (disk) หรือ อุปกรณอื่น ๆ


เชน scanner light-pen memory-card แตโดยสวนใหญแลวมักจะมาจาก keyboard และ disk
ในบทนี้เราจะใชอุปกรณทั้งสองที่ไดกลาวมา เปนชองทางหลักในการนําเขาขอมูล และจะใช
ชองทางสงออกเพียงสองทางคือ จอ (monitor) และ เครื่องพิมพ (printer)

8.1 Streams

รูปแบบพื้นฐานของขอมูลที่ Java รูจักมีอยูสองลักษณะคือ binary streams และ character


streams ซึ่งมีความแตกตางกันในรูปแบบของการจัดเก็บ ถาเราเขียนขอมูลเขาสู stream โดย
เขียนทีละ byte หรือ ทีละหลาย ๆ byte เราเรียกขอมูลเหลานี้วา binary data การเขียนจะไมมี
กระบวนการอื่นใดมายุงเกี่ยว (เชนการเปลี่ยนขอมูลใหอยูในรูปแบบอื่นกอนการจัดเก็บ) ขอมูลจะ
ถูกสงเขาสู stream ตามที่ปรากฏอยูใน memory เชน ถา memory สวนนี้เก็บขอมูลที่เปน
ตัวเลขอยู ตัวเลขเหลานี้จะถูกสงเขาสู streams ตามที่ปรากฏใน memory

ในการเก็บขอมูลที่เปน character streams นั้นโดยทั่วไปจะทํากับขอมูลที่เปนตัวอักษร (text)


เราจะใช character streams ในการอานไฟลที่เปน text ตัวเลขที่อยูในไฟลจะถูกเปลี่ยนใหอยู
ในรูปแบบที่ Java ไดกําหนดขึ้น การอานตัวเลขดวยการใช character streams เปนกระบวนการ
ที่ยุงยากมาก เพราะเราตองรูวาตัวอักษรที่เปนตัวแทนของตัวเลขนั้นใชกี่ตัวอักษรในการเก็บ เชน
ถาเราสงตัวเลข 17 ผานทาง character stream ตัวเลขนี้จะถูกเปลี่ยนใหเปนคา ASCII ของ '1'
และ '7' หรือ 0x310x37 (เลขฐาน 16) และสมมติวาเราเขียนเลข 727 อีกตัวใน stream ของเรา
ก็จะมีขอมูลเปน 0x310x370x370x320x37 การอานตัวเลขทั้งสองตัวออกมาจะทําไดยากมาก
เพราะเราไมรูวาจะตองอานตัวอักษรทีละกี่ตัวถึงจะไดขอมูลที่ถูกตองเหมือกับที่ไดเขียนไว
เราไดทราบแลววา Java เก็บขอมูลในรูปแบบของ Unicode ดังนั้นในการเขียนขอมูลเขาสู
stream ในรูปแบบของ character ขอมูลจะถูกเปลี่ยน (โดยอัตโนมัติ) ใหอยูในรูปแบบของการ
เก็บ (local code) ตามที่เครื่องนั้นใชอยู เชนถาเครื่องของเราใชภาษาไทยเปนภาษาหลัก
รูปแบบของการเก็บก็จะเปน code ของภาษาไทย (Java เรียกระบบนี้วา localization) ในการ
อานขอมูลนั้น Java จะเปลี่ยนขอมูลที่ถูกจัดเก็บในรูปแบบของ local code ใหเปน Unicode
กอน โดยสรุปแลวในการเขียน และอานนั้น Java จะเปลี่ยนขอมูลใหเปน Unicode สวนการ
จัดเก็บนั้นจะจัดเก็บในรูปแบบของภาษาที่ใชอยูในเครื่อง
เริ่มตนการเขียนโปรแกรมดวย Java

เราจะมาดูตัวอยางที่เกี่ยวของกับไฟล ในหลาย ๆ รูปแบบเพื่อทําใหเกิดความเขาใจถึงวิธีการ


และกระบวนการตาง ๆ ที่เกี่ยวของกับ input และ output และกอนที่เราจะสรางไฟลในรูปแบบ

intro. to Java (FEU.faa)


ตาง ๆ เราจะมาดูกันถึงการใช class ที่เอื้อถึงการดูขอมูลที่เกี่ยวของกับไฟล หรือ directory

8.2 การเรียกใช class File

Class File เปน class ที่มีไวเพื่อใหเราสามารถดึงขอมูลที่เกี่ยวของกับไฟล หรือ directory ออก


จากหนวยความจําสํารอง (disk) Object ที่สรางมาจาก class File ไมสามารถเปดไฟลหรือ
ประมวลผลขอมูลที่อยูในไฟลได จะใชรวมกับ class ที่เกี่ยวของกับไฟลอื่น ๆ ที่ Java มีให

Class File มี constructor อยูสามตัวที่เราสามารถเรียกใชได คือ

1. File(File parent, String child) จะเรียกใชไฟล object directory (parent) ที่มี


อยูแลวในการคนหาไฟล หรือ directory ที่กําหนดใหจากตัวแปร child
2. File(String pathname) จะเรียกใช pathname ในการคนหาไฟล หรือ directory
ซึ่ง pathname ที่วาจะเปน absolute path1 หรือ relative path2 ก็ได
3. File(String parent, String child) จะเรียกใช parent ในการคนหาไฟล หรือ
directory ที่กําหนดใหจากตัวแปร child
4. File(URI uri) เปนการคนหาไฟลจาก URI3 เชน file:/D:/temp/chapter7.txt

โปรแกรมตัวอยางที่เห็นนี้เปนการแสดงขอมูลที่เกี่ยวกับไฟลที่กําหนดให

1: /**
2: Get inofrmation about file
3: */
4:
5: import java.io.*;
6: import static java.lang.System.out;
7:
8: class FileInfo {
9: public static void main(String[] args) {
10: //get path from command-line argument
11: File path = new File(args[0]);
12:
13: //display some info. about this file
14: if(path.exists()) {
15: out.println(path + " does exist.");
16: out.println("Readable : " + statusOk(path.canRead()));
17: out.println("Writable : " + statusOk(path.canWrite()));
18: out.println("Directory? : " + statusOk(path.isDirectory()));
19: out.println("File? : " + statusOk(path.isFile()));
20: out.println("Hidden? : " + statusOk(path.isHidden()));
21: }
22: else
23: out.println(path + " does not exist.");
24: }
25:
26: //return "Yes" or "No"
27: public static String statusOk(boolean yes) {
28: return yes ? "Yes" : "No";
29: }
30: }

โปรแกรมเริ่มตนดวยการสราง object path จาก class File ดวยขอมูลที่มาจาก command-line


argument ซึ่งอาจอยูในรูปแบบ

e:\bc221Book\source
e:\bc221Book\source
\source
\bc221Book\source\FileInfo.java

1
Path ที่เริ่มตนจาก root directory ไปจนถึงไฟล เชน C:\javaBook\revision2\chapter2.doc
2
Path ที่เริ่มภายใน directory ที่มีโปรแกรมอยู เชนถาเราทํางานใน D:\temp ไฟลที่เราคนหาจะอยูใน directory นี้
3
URI หมายถึง Uniform Resource Identifier ซึ่งเปนอีกรูปแบบหนึ่งของ URL (Uniform Resource Relocator) ที่ใช
ในการคนหา web site

246
บทที่ 8: Streams I/O

ตัวอยางสองตัวแรกอยูในรูปแบบที่เรียกวา absolute path สวนสองตัวสุดทายอยูในรูปแบที่

intro. to Java (FEU.faa)


เรียกวา relative path

absolute path เปนการกําหนดที่มาที่ไปดวยตัวอักษรของ drive สวน relative path จะไมมีการ


กําหนดตัวอักษรของ drive แตการทํางานทั้งหมดจะอยูภายใน directory นั้น ๆ ดังตัวอยาง
ผลลัพธของการ run นี้

>java FileInfo \bc221Book


\bc221Book does exist.
Readable : Yes
Writable : Yes
Directory? : Yes
File? : No
Hidden? : No

>java FileInfo \bc221Book\source\FileInfo.java


\bc221Book\source\FileInfo.java does exist.
Readable : Yes
Writable : Yes
Directory? : No
File? : Yes
Hidden? : No

>java FileInfo \bc221Book\FileInfo.java


\bc221Book\FileInfo.java does not exist.

ผลลัพธของการ run ทั้งสามครั้งใช relative path เปนตัวกําหนดการคนหา directory และ file


สวนผลลัพธดานลางนี้เปนการาคนหาจาก absolute path

E:\bc221Book\source>java FileInfo e:\bc221Book\source\FileInfo.java


e:\bc221Book\source\FileInfo.java does exist.
Readable : Yes
Writable : Yes
Directory? : No
File? : Yes
Hidden? : No

เราเรียกใช method ที่มีอยูใน class File ในการตรวจสอบขอมูลของ file วาเปน file ลักษณะ
ไหน ขอมูลเฉพาะคือ อะไร คําวา file ใน Java นั้นเปนไดสองอยาง คือ file ที่เก็บขอมูลที่เราใช
(หรือ ระบบใช) และ file ที่เก็บ file หรือที่รียกวา directory (หรือ folder)

Java ยังมี method ที่ใชในการตรวจสอบขอมูลของ file อีกหลายตัว เราจะลองใช method


เหลานี้ในโปรแกรมตัวอยางตอไปนี้

1: /**
2: Displaying file information
3: */
4:
5: import java.io.*;
6: import java.util.Date;
7: import static java.lang.System.out;
8:
9: class FileInfo1 {
10: public static void main(String[] args) {
11: //explain how to use if no argument is given
12: if(args.length < 1) {
13: out.println("Usage: FileInfo1 file-name");
14: System.exit(1);
15: }
16: //display information about this file
17: echoFileInfo(new File(args[0]));
18: }
19:
20: private static void echoFileInfo(File file) {
21: //display if file is a file or a directory
22: if(file.isFile())
23: out.println("\n" + file + " is a file.");

247
เริ่มตนการเขียนโปรแกรมดวย Java

24: if(file.isDirectory()) {
25: String []list = file.list();
26: out.println("\n" + file + " is a directory.");

intro. to Java (FEU.faa)


27: out.println("There are " + list.length +
" items in this directory.");
28: }
29:
30: //lastModifeid() returns amount of milliseconds the file
31: //was last modified, e.g. 1045375241775 since
32: //January 1, 1970, 00:00:00 GMT. We have to create
33: //a date object from this data.
34: long lastModified = file.lastModified();
35: Date dateModified = new Date(lastModified);
36: //display information about this file
37: out.println(
38: "Absolute path: " + file.getAbsolutePath() +
39: "\n Name: " + file.getName() +
40: "\n Parent: " + file.getParent() +
41: "\n Path: " + file.getPath() +
42: "\n Length: " + file.length() +
43: "\n Last modified: " + dateModified);
44: }
45: }

โปรแกรม FileInfo1.java เรียกใช method 5 ตัวในการแสดงขอมูลของไฟลที่กําหนดให (ผาน


ทาง command-line argument) ซึ่งเปนการเรียกใชโดยตรง และไมมีความยุงยากในการ
เรียกใช มี method เพียงตัวเดียวเทานั้นที่เราตองปรับปรุงผลลัพธที่ method ตัวนี้สงมาให
method ที่วานี้คือ lastModified()

lastModified() จะสงคาที่มีชนิดเปน long ที่เปนคาของ วันและเวลาที่ไฟลไดรับการ


เปลี่ยนแปลงครั้งสุดทายในรูปแบบของ millisecond เชน 1045375241775 ซึ่งเราไมสามารถ
บอกไดวาเปนวันที่เทาไร เวลาอะไร เราตองใชคานี้ในการสราง วันขึ้นใหมผานทาง class Date
ดังนี้

long lastModified = file.lastModified();


Date dateModified = new Date(lastModified);

หลังจากนั้นเราก็แสดงผลออกทางหนาจอ ดังที่แสดงใหดูเปนตัวอยางดานลางนี้

D:\Intro.to.Java\Word.Format\edition3\source>java FileInfo1 FileInfo1.java

FileInfo1.java is a file.
Absolute path: D:\Intro.to.Java\Word.Format\edition3\source\FileInfo1.java
Name: FileInfo1.java
Parent: null
Path: FileInfo1.java
Length: 1364
Last modified: Mon May 16 08:09:53 ICT 2005

ตัวอยางตอไปเปนโปรแกรมที่แสดงรายละเอียดของ directory วาประกอบไปดวยไฟล หรือ


sub-directory อะไรบาง ซึ่งเปนคําสั่งที่คลายกับคําสั่ง dir ของ DOS ใน command window ที่
เรียกดูไฟลที่อยูใน directory นั้น ๆ

1: /**
2: Show files in directory
3: */
4:
5: import java.io.*;
6: import java.util.Arrays;
7:
8: //extract file name from path
9: //must implements FilenameFilter and override method accept()
10: //to get files with given information into a list
11: class DirFilter implements FilenameFilter {
12: String fileName;
13:
14: //constructor
15: DirFilter(String fileName) {

248
บทที่ 8: Streams I/O

16: this.fileName = fileName;


17: }
18:

intro. to Java (FEU.faa)


19: //this method is called by method list() from class File
20: //to include a file if it contains a given info
21: public boolean accept(File path, String name) {
22: //get only file name
23: String file = new File(name).getName();
24: //if in fact the file exists we will get the first index
25: //of this file, which causes accept() to return true,
26: //otherwise we will get -1 and returns false
27: return file.indexOf(fileName) != -1;
28: }
29: }
30:
31: class DirListing {
32: public static void main(String[] args) {
33: //create file object with a default directory
34: File path = new File(".");
35: String[] fileList; //list of files
36:
37: //no argument provided, get all files on this directory
38: if(args.length == 0)
39: fileList = path.list();
40: //information provided, get rid of path
41: //with given info.
42: else
43: fileList = path.list(new DirFilter(args[0]));
44:
45: Arrays.sort(fileList); //sort the list
46: showDir(fileList); //display the list
47: }
48:
49: //display files to screen
50: public static void showDir(String[] list) {
51: for(int i = 0; i < list.length; i++)
52: System.out.println(list[i]);
53: }
54: }

กอนที่จะอธิบายถึงการทํางานของโปรแกรม เรามาดูผลลัพธ (ตัดทอนบางสวนออก) กันกอน

>java DirListing java


Access.java
Account.java
Add1To100.java


AddNumbers2.java
ThrowsWithTry.java
ThrowsWithTry1.java
Triangle.java
TryAndCatchExample1.java
TryAndCatchExample2.java
Two.java
UpperLower.java
UpperLower2.java
Variables.java
Vowels.java
ZeroDivideException.java
p.java

>java DirListing te
BankRate.class
BankRate.java
ByteShort.class
ByteShort.java
Calculate.class
Calculate.java

โปรแกรม DirListing.java จะแสดงรายการของขอมูลที่อยูใน directory นั้น ๆ ตามขอกําหนดที่


เราตั้งให เชนถาเราตองการแสดงไฟลทั้งหมดที่มีอยูเราก็ใชคําสั่ง DirListing โดยไมมีการ

249
เริ่มตนการเขียนโปรแกรมดวย Java

กําหนดใด ๆ แตถาตองการแสดงผลดวยขอกําหนด เชน ตองการแสดงไฟลทุกตัวที่มีคําวา "te"


อยูในไฟลเราก็ใชคําสั่ง DirListing te เปนตน

intro. to Java (FEU.faa)


โปรแกรมของเราตองเรียกใช interface FileNameFilter เพื่อทําการ override method
accept() ที่ทําหนาที่ในการกรองขอมูลสําหรับให method list() ที่อยูใน class File ใช โดย
กําหนดให method accept() ตรวจสอบไฟลจาก path เฉพาะไฟลที่ถูกตองตามเงื่อนไขที่
กําหนดไวแลวจึงบอก method list() วาไฟลตัวนี้ควรเก็บไวใน list ประโยคที่แสดงผลโดยไม
ตองกําหนดเงื่อนไข ตองตรวจสอบกับ args.length ถาคานี้เปน 0 เราจะเก็บไฟลทุกตัวไวใน
array list ดวยการเรียกใช
fileList = path.list();

แตถา มีการกําหนดเงื่อนไข เราจะสงเงื่อนไขนี้ไปให method accept() เพื่อทําการคัดเลือกไฟล


ที่ถูกตองตามเงือ
่ นไข ดวยประโยค

fileList = path.list(new DirFilter(args[0]));

สมมติวา args[0] ของเรามีคาเปน "te" (ดังตัวอยาง) "te" ก็จะถูกเก็บไวเปนตัวตรวจสอบวาไฟล


ที่อยูใน path มีคํานี้อยูหรือไม เชนไฟลชื่อ Calculate.java

Method list() กําหนดให parameter ที่สงเขาไปเปน object จาก class FileNameFilter ดังนั้น
เราสามารถที่จะเขียน method accept() ในลักษณะใดก็ได ในที่นี้เราดึงเอาเฉพาะชื่อไฟลที่ไมมี
path รวมอยูดวย ดวยการกําหนดดังนี้

String file = new File(name).getName();

หลังจากนั้นเราใช method indexOf() จาก class String ในการตรวจสอบวา "te" มีอยูในไฟลนี้


หรือไม ซึ่งถามีคาที่ไดจาก indexOf() จะเปนตําแหนงที่ "te" อยู แตถาไมมีไดคา -1 เรานําคานี้
มาเปรียบเทียบกับ -1 เพื่อบอกให list() รูวาควรเก็บไฟลตัวนี้หรือไม (true = เก็บ false = ไม
เก็บ)

return file.indexOf(fileName) != -1;

ไฟลทุกตัวที่เราไดจาก method list() จะถูกเก็บไวในตัวแปร fileList เพื่อเอาไวใชในการ


แสดงผลตอไป แตกอนที่เราจะแสดงผล เราทําการเรียงลําดับของไฟลใน fileList ดวยการ
เรียกใช method sort() ของ class Arrays

โปรแกรมตัวนี้อาจดูเขาใจยากสักหนอย แตกไ ็ มยากเกินไปนัก ผูอานตองทําความเขาใจในเรื่อง


ของการเรียกใช method list() ที่มาจาก class File รวมไปถึงการเขียน code ตามขอกําหนดที่
เราตองการใน method accept() สิ่งที่เราตองคํานึงถึง คือ

Method list() ใน class File มีอยูสองตัว คือ ตัวที่ไมมี parameter และตัวที่มี parameter
สําหรับ method ตัวที่มี parameter นี้ parameter ตองเปน object จาก class FileNameFilter
ดังนั้นเราตองสราง class ที่มาจาก class นี้พรอมทั้งทําการ override method accept() ให
ทํางานตามที่เราตองการ ผูอานควรทดลอง run โปรแกรมตัวนี้ดวยเงื่อนไขตาง ๆ เพื่อใหเกิด
ความเขาใจมากยิ่งขึ้น และควรทดลองเขียน code ให method accept() ใหมโดยกําหนดใหทํา
การตาง ๆ ที่ตางออกไปจากที่เขียนในโปรแกรมตัวอยางนี้

เราไดพูดถึงการแสดงรายการของไฟลที่มีอยูใ น directory การสรางเงื่อนไขใหกับ list() และ


accept() พอสมควรแลว ตอไปเราจะพูดถึงการอาน การเขียน ไฟล โดยเราจะเริ่มตนที่ Input
streams

8.3 Input and Output streams (ชองทางการนําขอมูลเขา และ การนําขอมูลออก)

Java กําหนดใหมีการนําขอมูลเขาสูโปรแกรมไดหลายทาง ซึ่งเราก็ไดสัมผัสถึงวิธีการมาบาง


พอสมควรในเรื่องของการนําขอมูลเขาผานทางชองทางการนําเขามาตรฐาน (standard I/O)
ตอไปนี้เราจะมาดูถึงการนําขอมูลที่มาจากไฟลเขาสูโปรแกรม

รูปแบบของขอมูลที่ถูกจัดเก็บในไฟลนั้นถูกแบงออกเปนสองลักษณะคือ

250
บทที่ 8: Streams I/O

1. ขอมูลที่อยูในรูปแบบของ character เชน code ของโปรแกรมที่สรางขึ้นจาก Text


editor พูดงาย ๆ ก็คือ เราสามารถที่จะอานขอมูลเหลานี้ได (human-readable form)

intro. to Java (FEU.faa)


เราเรียกไฟลแบบนี้วา text file

2. ขอมูลที่ถูกจัดเก็บในรูปแบบของ binary data เชนขอมูลที่ถูกเก็บในรูปแบบของ MS


Word เราไมสามรถที่จะอานขอมูลเหลานี้ไดตองอาศัยโปรแกรมในการชวยอาน เรา
เรียกไฟลในรูปแบบนี้วา binary file

8.3.1 Text File (ไฟลที่เก็บขอมูลเปน character)

เราจะเริ่มดวยการอานขอมูลจาก text file ที่เก็บ code ของโปรแกรมที่เราไดเขียนขึ้น

1: /**
2: Reading from a text file
3: */
4:
5: import java.io.BufferedReader;
6: import java.io.File;
7: import java.io.FileReader;
8: import static java.lang.System.out;
9: import static java.lang.System.err;
10:
11: class ReadingTextFile {
12: public static void main(String[] args) {
13: String filename = args[0];
14:
15: try {
16: File file = new File(filename);
17: FileReader fileReader = new FileReader(file);
18: BufferedReader reader = new BufferedReader(fileReader);
19: String line;
20: int i = 1;
21: while((line = reader.readLine()) != null) {
22: out.printf("%4d: %s%n", i++, line);
23: }
24: }
25: catch(Exception e) {
26: err.printf("Unable to open file name '%s': %s",
filename, e.getMessage());
27: }
28: }
29: }

เราเปดไฟลที่ตองการอานดวย FileReader() พรอมกับการเรียกใช BufferedReader() เพื่อให


การอานขอมูลทําไดรวดเร็วขึ้น เราอานขอมูลเขามาเก็บไวใน str ทีละหนึ่งแถวจนกวาขอมูลหมด
จาก buffer หลังจากนั้นเราก็สงขอมูลที่อานไดออกไปยังหนาจอพรอมทั้งเลขที่บรรทัด เมื่อสง
ขอมูลหมดแลวเราก็ปดไฟลดวยการใช close() ผลลัพธที่เราไดจากการสงไฟล
ReadingTextFile.java ไปใหโปรแกรมนี้ คือตัว code ของตัวโปรแกรมเองที่มีเลขกํากับบรรทัด
อยู ที่เราเห็นดานบนนี้นั่นเอง

เรามาดูตัวอยางที่อานขอมูลดวยการใช FileInputStream() และ read() ในการอานกันบาง

1: /**
2: Reading a text file
3: */
4:
5: import java.io.*;
6:
7: class ReadTextFile {
8: public static void main(String[] args) throws IOException {
9: FileInputStream in; //file input stream
10: int ch; //store each character read
11:
12: try {
13: in = new FileInputStream(args[0]);
14: while((ch = in.read()) != -1) {
15: System.out.print((char)ch);

251
เริ่มตนการเขียนโปรแกรมดวย Java

16: }
17: in.close();
18: }

intro. to Java (FEU.faa)


19: //file not found
20: catch(FileNotFoundException e) {
21: System.err.println("Cannot find: " + args[0]);
22: System.exit(1);
23: }
24: //no argument specified
25: catch(ArrayIndexOutOfBoundsException e) {
26: System.err.println("Usage: ReadTextFile file-name");
27: System.exit(1);
28: }
29: }
30: }

โปรแกรมตัวนี้ใช FileInputStream() เปนตัวเปดไฟล และใช read() ในการอานขอมูลทีละ


byte จาก FileInputStream ทําการจัดเก็บขอมูลที่อานไดไวใน ch พรอมทั้งสงขอมูลที่อานไดนี้
ออกไปทางหนาจอ จนกวาการอานจะสิ้นสุดลง โดยอานเจอ -1 หรือ EOF (End Of File)

ในการสงขอมูลที่อานไดออกไปยังหนาจอนั้น เราตองทําการ cast ใหกับ ch กอนมิเชนนั้นแลว


ขอมูลที่สงออกไปจะไมตรงกับที่อานเขามา โปรแกรมของเรายังไดทําการตรวจจับ error ที่อาจ
เกิดขึ้นสองตัว คือ โปรแกรมหาไฟลไมเจอ และ ผูใชไมกําหนดชื่อไฟลที่ใชเปนขอมูลสําหรับ
การเปดอาน

character streams โดยทั่วไปเหมาะสมกับการอาน และเขียนขอมูลที่เปน text ดังนั้นหากจะ


เอามาใชกับขอมูลอื่น ๆ ก็จะสรางความยุงยากในการอานและเขียนมากพอสมควร ดังนั้นในการ
อานและเขียนขอมูลที่ไมใช text เราจึงตองใชการอานและเขียนขอมูลที่อยูในรูปแบบของ
binary data แตกอนที่จะพูดถึงเรื่องของ Binary file เราจะมาดูกันถึงความยุงยากที่วา ในการ
ทํางานกับ text file

โปรแกรมตัวอยาง TextWithStreamTok.java ตอไปนี้เปนโปรแกรมตัวอยางงาย ๆ ที่อานขอมูล


ที่ถูกเก็บไวในรูปแบบของ text นํามาประมวลผล เสร็จแลวจึงสงขอมูลกลับไปยังหนาจอ

เพื่อใหผูอานเขาใจงายขึ้น เราจึงจัดเก็บขอมูลอยูในรูปแบบของ

String double int

เชน

Notebook 12.00 30
CoffeeMug 5.00 40
PaperCutter 12.50 25

และเราจะกําหนดใหมีขอมูลเพียง 3 ตัวเทานั้น ลองมาดูการใช StreamTokenizer ที่เราไดพูด


ถึงตอนที่เราอานขอมูลนําเขาจาก keyboard กอนหนานี้ วาจะนํามาใชกับขอมูลที่อยูในไฟลได
อยางไร ในโปรแกรมตัวอยางดานลางนี้

1: /**
2: Reading text data from a file
3: */
4:
5: import java.io.*;
6: import static java.lang.System.out;
7: import static java.lang.System.err;
8:
9: class TextWithStreamTok {
10: public static void main(String[] args) throws IOException {
11: BufferedReader in = null; //input buffer
12: FileReader file = null; //input file
13: StreamTokenizer stream = null; //tokens
14:
15: try {
16: file = new FileReader(args[0]); //get a file
17: in = new BufferedReader(file); //storage buffer

252
บทที่ 8: Streams I/O

18: //create tokens


19: stream = new StreamTokenizer(in);
20: stream.eolIsSignificant(true); //ignored EOL

intro. to Java (FEU.faa)


21: }
22: catch(FileNotFoundException e) {
23: err.println("Cannot find file: " + args[0]);
24: System.exit(1);
25: }
26:
27: //arrays to store data only 3 items as example e.g.
28: //description prices unit
29: //Coffee-Mug 12.50 30
30: String[] descs = new String[3];
31: double[] prices = new double[3];
32: int[] units = new int[3];
33:
34: int i = 0, j = 0, k = 0; //arrays' indexes
35: boolean first = true; //make sure correct data is read
36: try {
37: //reading tokens into corresponding arrays
38: //until EOF is reached
39: while(stream.nextToken() != StreamTokenizer.TT_EOF) {
40: //data is a string
41: if(stream.ttype == StreamTokenizer.TT_WORD) {
42: descs[i++] = stream.sval;
43: }
44: //data is a number (price or unit)
45: if(stream.ttype == StreamTokenizer.TT_NUMBER) {
46: //first data in line is price
47: if(first == true) {
48: prices[j++] = stream.nval;
49: first = false;
50: }
51: //next one is unit price
52: else {
53: units[k++] = (int)stream.nval;
54: first = true;
55: }
56: }
57: //ignore EOL
58: if(stream.ttype == StreamTokenizer.TT_EOL) {
59: /* do nothing */
60: }
61: }
62: in.close(); //close the stream
63: }
64: //trouble reading file
65: catch(IOException e) {
66: err.println("Error reading file.");
67: e.printStackTrace();
68: System.exit(1);
69: }
70: //too many items for arrays
71: catch(ArrayIndexOutOfBoundsException e) {
72: err.println("Array index out of bound.");
73: e.printStackTrace();
74: System.exit(1);
75: }
76:
77: display(descs, prices, units); //display data
78: }
79:
80: //display data to screen
81: private static void display(String[] d, double[] p, int[] u) {
82: double total = 0.00;
83: for(int i = 0; i < d.length; i++) {
84: total = p[i] * u[i];
85: out.printf("%-15s %6.2f %4d %6.2f%n", d[i],
p[i], u[i], total);
86: }
87: }
88: }

253
เริ่มตนการเขียนโปรแกรมดวย Java

การทํางานหลัก ๆ ของโปรแกรมก็คือ การอานขอมูลที่ถูกจัดเก็บใน format ที่ไดกลาวไว สิ่ง


สําคัญที่เราตองคํานึงถึงคือ ตําแหนงและชนิดของขอมูลที่เราตองอาน กอนอื่นเราตองกําหนด

intro. to Java (FEU.faa)


ชองทางนําเขาขอมูล ดังนี้

file = new FileReader(args[0]); //get a file


in = new BufferedReader(file); //storage buffer
//create tokens
stream = new StreamTokenizer(in);
stream.eolIsSignificant(true); //ignore EOL

หลังจากนั้นเราก็ตรวจสอบ token ที่เราอานจาก stream วาเปนตัวเลขหรือวาเปน string

while(stream.nextToken() != StreamTokenizer.TT_EOF) {
//data is a string
if(stream.ttype == StreamTokenizer.TT_WORD) {
descs[i++] = stream.sval;
}
//data is a number (price or unit)
if(stream.ttype == StreamTokenizer.TT_NUMBER) {
//first data in line is price
if(first == true) {
prices[j++] = stream.nval;
first = false;
}
//next one is unit price
else {
units[k++] = (int)stream.nval;
first = true;
}
}
//ignore EOL
if(stream.ttype == StreamTokenizer.TT_EOL) {
/* do nothing */
}
}

เราจะเก็บขอมูลที่เปน string ไวใน descs[] ขอมูลที่เปน double ไวใน prices[] และขอมูลที่


เปน int ไวใน units[] สิ่งที่เราตองคํานึงก็คอ
ื ลําดับของขอมูลที่เปนตัวเลข เรารูวาตัวเลขกลุม
แรกที่อานไดคือ prices และขอมูลถัดไปคือ units ดังนั้นเราตองใชตัวแปร first เปนตัวกําหนดที่
เก็บขอมูลวา ที่อานไดครั้งแรกควรไปอยูที่ prices[] และที่อานไดถัดมาควรไปอยูที่ units[] และ
เมื่อเราอานจนหมดแลวเราก็สง arrays ทั้งสามตัวไปให display() ทําการประมวลผล และ
แสดงผลตอไป

ผลลัพธที่เราได จากการ run คือ

Notebook 12.00 30 360.00


CoffeeMug 5.00 40 200.00
PaperCutter 12.50 24 300.00

ขอมูลที่เก็บไวในไฟล data.dat จะตองมีชองวางระหวางขอมูลแตละตัว เชน

Notebook 12.0 30
CoffeeMug 5.0 40
PaperCutter 12.5 25

หรือจะใหอยูในบรรทัดเดียวกันก็ได เชน

Notebook 12.0 30 CoffeeMug 5.0 40 PaperCutter 12.5 25

ทั้งนี้เพราะ nextToken() จะอานขอมูลที่อยูถด


ั จากชองวางไปจนกวาจะเจอชองวางอีกครั้งหนึ่ง

จะเห็นไดวาเราตองคอยระวังการอานขอมูล เพราะถาลําดับของการอานขอมูลผิด โปรแกรมของ


เราก็จะเกิดความผิดพลาด และที่สําคัญอีกอยางหนึ่งคือ ขั้นตอนที่ยุงยากของการกําหนดลําดับ
ของขอมูล การอานขอมูล (ลองนึกถึงการอาน ถาเรามีขอมูลที่เปนตัวเลขอยูมากกวาสองตัว และ

254
บทที่ 8: Streams I/O

มีขอมูลที่เปน string อยูระหวางขอมูลเหลานี้) ดังนั้นเราจึงไมนิยมใช text file ในการเก็บขอมูล


ที่เปน primitive type เราควรใช text file ในการทํางานกับขอมูลที่เปน text เทานั้น

intro. to Java (FEU.faa)


เราสามารถที่จะทําใหการทํางานกับขอมูลที่เปน text ไดงายขึ้นถาเรากําหนดใหมี ตัวแบงขอมูล
(delimiter) ระหวางขอมูลแตละตัวที่อยูในไฟล ซึ่งตัวแบงที่วานี้จะเปนตัวอักษรใด ๆ ก็ไดที่ไมมี
สวนเกี่ยวของกับขอมูลที่อยูในไฟล เชน เราอาจใชเครื่องหมาย "|" เปนตัวแบงขอมูลในไฟล
ตัวอยางที่ไดพด
ู ถึงกอนหนานี้ ลองมาดูตัวอยางการใชตัวแบงที่วานี้กัน

1: /**
2: Using '|' as delimiter in a text file
3: */
4:
5: import java.io.*;
6: import java.util.StringTokenizer;
7: import static java.lang.System.out;
8: import static java.lang.System.err;
9:
10: class TokenWithDelimiter {
11: public static void main(String[] args) throws IOException {
12: //data to write to file
13: String[] items = {"Notebook", "Coffee Mug", "Paper Cutter"};
14: double[] prices = {12.0D, 5.0D, 12.5D};
15: int[] units = {30, 40, 25};
16:
17: //check to see if a file is provided
18: if(args.length < 1) {
19: err.println("Usage: TokenWithDelimeter file-name.");
20: System.exit(1);
21: }
22: writeToFile(args[0], items, prices, units);
23: processData(args[0]);
24: }
25:
26: //writing data in arrays to a given file
27: private static void writeToFile(String fileName, String[] items,
28: double[] prices, int[] units)
29: throws IOException {
30: File dataFile = null;
31: FileWriter fWriter = null;
32: BufferedWriter buffer = null;
33: PrintWriter outFile = null;
34:
35: try {
36: dataFile = new File(fileName);
37: fWriter = new FileWriter(dataFile);
38: buffer = new BufferedWriter(fWriter);
39: outFile = new PrintWriter(buffer);
40:
41: //printing data to file with '|' in between
42: for(int i = 0; i < items.length; i++) {
43: outFile.print(items[i]);
44: outFile.print('|');
45: outFile.print(prices[i]);
46: outFile.print('|');
47: outFile.println(units[i]);
48: }
49: }
50: catch(IOException e) {
51: err.println("Error writing to file");
52: e.printStackTrace();
53: System.exit(1);
54: }
55: finally {
56: if(outFile != null)
57: outFile.close();
58: }
59: }
60:
61: //reading data from a given file & display to screen
62: private static void processData(String fileName)
63: throws IOException {

255
เริ่มตนการเขียนโปรแกรมดวย Java

64: String item; //item read


65: double price; //price read
66: int unit; //unit read

intro. to Java (FEU.faa)


67: File dataFile = null;
68: FileReader fReader = null;
69: BufferedReader buffer = null;
70: String input = null;
71:
72: try {
73: dataFile = new File(fileName);
74: fReader = new FileReader(dataFile);
75: buffer = new BufferedReader(fReader);
76: input = buffer.readLine(); //read first line
77:
78: //keep reading until there's no more lines to read
79: while(input != null) {
80: //get token from inputline - skip '|'
81: StringTokenizer token = new StringTokenizer(input, "|");
82: item = token.nextToken();
83: price = Double.parseDouble(token.nextToken());
84: unit = Integer.parseInt(token.nextToken());
85: //display to screen
86: out.printf("%-15s %6.2f %4d %6.2f%n",
item, price, unit, unit*price);
87: input = buffer.readLine();
88: }
89: }
90: catch(IOException e) {
91: err.println("Error reading file");
92: e.printStackTrace();
93: System.exit(1);
94: }
95: finally {
96: if(buffer != null)
97: buffer.close();
98: }
99: }
100: }

เราใชเครื่องหมาย '|' ในการแบงขอมูลที่อยูในไฟล พรอมทั้งใช class StringTokenizer เปนตัว


ดึงขอมูลที่อานมาจากไฟล หลังจากที่เราเขียนขอมูลที่อยูใน arrays ทั้งสามตัวไปเก็บไวในไฟล
ดวยการใช print() และ println() ผูอานควรสังเกตถึงการเขียนขอมูลในแตละครั้งวา เราเขียน '|'
ตามหลังเสมอ ยกเวนการเขียนขอมูลตัวสุดทาย

dataFile = new File(fileName);


fWriter = new FileWriter(dataFile);
buffer = new BufferedWriter(fWriter);
outFile = new PrintWriter(buffer);

//printing data to file with '|' in between


for(int i = 0; i < items.length; i++) {
outFile.print(items[i]);
outFile.print('|');
outFile.print(prices[i]);
outFile.print('|');
outFile.println(units[i]);
}

หลังจากนั้นเราก็อานขอมูลกลับออกมาดวย readLine() เราทําการดึงขอมูลแตละตัวทีถ ่ ูกแบง


ดวย '|' ดวยการใช nextToken() แตกอนที่เราจะทําการดึงขอมูลทุกครั้งในแตละบรรทัด เรา
จะตองกําหนดเงื่อนไขให token ของเราเสียกอน ดวยการใช

StringTokenizer token = new StringTokenizer(input, "|");

เพื่อที่จะบังคับใหการดึงขอมูลนั้นเกิดขึ้นระหวางเครื่องหมาย '|' ขอมูลตัวแรกที่เราดึงออกมาเปน


String เราจึงไมตองแปลงขอมูลตัวนี้ แตขอมูลอีกสองตัวที่เราดึงออกเปน double และ int ที่ถูก
เก็บใหเปน String ในการเขียน ดังนั้นเราจึงตองแปลงขอมูลทั้งสองตัวนี้ หลังจากที่เราไดขอมูล
ทั้งสามตัวแลวเราก็แสดงผลออกไปยังหนาจอ

256
บทที่ 8: Streams I/O

dataFile = new File(fileName);


fReader = new FileReader(dataFile);
buffer = new BufferedReader(fReader);

intro. to Java (FEU.faa)


input = buffer.readLine(); //read first line of data

//keep reading until there's no more lines to read


while(input != null) {
//get token from inputline - skip '|'
StringTokenizer token = new StringTokenizer(input, "|");
item = token.nextToken();
price = Double.parseDouble(token.nextToken());
unit = Integer.parseInt(token.nextToken());
//display to screen
out.printf("%-15s %6.2f %4d %6.2f%n",
item, price, unit, unit*price);
input = buffer.readLine();
}
ในการเขียนขอมูลเขาสูไฟลของเราครั้งนี้เราใช PrintWriter เปนตัวชวย ซึ่งภายใน class
PrintWriter นี้เราสามารถที่จะเรียกใช print() และ println() ได ทําใหการเขียนขอมูลสงไปยัง
ไฟลของเราเหมือนกับการเขียนที่เราไดทํามากอนหนานี้ตอนที่เราสงขอมูลไปยังหนาจอ แตเรา
จะตองใช PrintWriter รวมกับ FileWriter เพื่อใหการเขียนไปยังไฟลทําได และ
BufferedWriter เพื่อใหทําไดอยางมีประสิทธิภาพ (การใช buffer จะชวยใหการทํางานกับ
ขอมูลไวขึ้น)

ผลลัพธที่ไดจากการ run

>java TokenWithDelimiter tokens.dat


Notebook 12.00 30 360.00
Coffee Mug 5.00 40 200.00
Paper Cutter 12.50 25 312.50

ผูอานจะเห็นวาการใช delimiter เชนเครื่องหมาย '|' ทําใหเราสามารถใชชองวางใน string ได


(เชน Coffer Mug) ซึ่งเราไมสามารถทําไดถาเราใช ชองวางเปนตัวแบงขอมูล อยางไรก็ตาม
ผูอานสามารถที่จะกําหนดให delimiter เปนอะไรก็ไดตาบเทาที่การเขียนและอานขอมูลใช
delimiter ตัวเดียวกัน

8.3.2 การสราง Sequential Access File ในรูปแบบของ record (binary)

โปรแกรมตัวอยางที่เราไดแสดงใหดูกอนหนาเปนการทํางานกับ text file ทั้งหมดซึ่งการเขาหาที่


เราไดแสดงใหดูเปนการเขาหาที่เรียกวา sequential access ซึ่งเปนการเขาหาขอมูลที่อยูใน
ไฟลตามลําดับของขอมูลที่อยูดานหนาไฟลจนไปถึงดานหลังไฟล และเราไดกําหนดใหมี
ชองวาง หรือใช delimiter เปนตัวแบงขอมูล แตเราไมมีทางจะรูไดวาขอมูลที่ถูกเก็บอยูใน hard
disk เปนชนิดอะไร เชน ถาขอมูลที่เราเขียนเขาสู disk เปน 12 เราจะรูไดอยางไรวา 12 เปน int
หรือเปน String หรือเปน double แตเรารูกอนหนาแลววาเราไดเขียนอะไรกอน อะไรหลังทําให
การอานขอมูลออกจาก hard disk ทําไดถูกตอง บางครั้งเราก็ไมรูวาขอมูลถูกเก็บอยูในลักษณะ
ไหน ซึ่งทําใหเราตองอาน (หรือเขียน) ขอมูลในรูปแบบของ object ที่ประกอบไปดวยขอมูล
ดังกลาว ซึ่งขอมูลในรูปแบบนี้เราเรียกวา record

ในการเขียน record เขาสูไฟลนั้นเราตองทําให record อยูในรูปแบบของ object ที่ Java


เรียกวา serialized object ซึ่งหมายถึง object ที่ถูกเก็บอยูในรูปแบบของไบนารี หรือที่เรียกวา
sequence of bytes ซึ่งเปนการเก็บที่รวมเอาขอมูลของ object (data) และชนิดของ object
(object's type) รวมไปถึงชนิดของขอมูล (data type) ที่อยูใน object นั้นเขาดวยกัน เมื่อได
object แลวเราก็เขียน object นี้เขาสูไฟล กอนที่เราจะสรางไฟลที่เก็บขอมูลรูปแบบนี้เราตอง
สราง record ขึ้นมากอนซึ่งก็ทําไดดังนี้

1: /**
2: Simple record for sequential access file
3: */
4:
5: import java.io.Serializable;
6:
7: class OfficeSupply implements Serializable {
8: private int id;
9: private String name;

257
เริ่มตนการเขียนโปรแกรมดวย Java

10: private double price;


11: private int quantity;
12:

intro. to Java (FEU.faa)


13: OfficeSupply() {
14: //calling constructor below
15: this(0, null, 0.0, 0);
16: }
17:
18: OfficeSupply(int id, String name,
19: double price, int quantity) {
20: this.id = id;
21: this.name = name;
22: this.price = price;
23: this.quantity = quantity;
24: }
25:
26: public void setId(int id) {
27: this.id = id;
28: }
29:
30: public void setName(String name) {
31: this.name = name;
32: }
33:
34: public void setPrice(double price) {
35: this.price = price;
36: }
37:
38: public void setQuantity(int quantity) {
39: this.quantity = quantity;
40: }
41:
42: public int getId() {
43: return id;
44: }
45:
46: public String getName() {
47: return name;
48: }
49:
50: public double getPrice() {
51: return price;
52: }
53:
54: public int getQuantity() {
55: return quantity;
56: }
57: }

ผูอานจะเห็นวาเราตองกําหนดให class OfficeSupply ของเรา implement Serializable


package (บรรทัดที่ 7) เพื่อใหการ serialization (และ derializaion4) เปนไปไดซึ่งตองทําผาน
ทาง ObjectOutputStream และ ObjectInputStream ใน class OfficeSupply ของเราเรายัง
กําหนดใหมี method สําหรับการนําขอมูลเขา ดึงขอมูลออก (get… และ set…) ในการสราง
sequential access file นั้นเราก็เพียงแตเรียกใช ObjectOutputStream ผานทาง
FileOutputStream ดัง code ที่แสดงใหดูนี้

1: /**
2: Creating sequential access file
3: */
4:
5: import java.io.*;
6: import java.util.*;
7: import java.lang.*;
8: import static java.lang.System.out;
9: import static java.lang.System.err;
10:
11: class CreateSequentialFile {
12: private ObjectOutputStream ostream;
13: private FileOutputStream fstream;

4
นําเอา object กลับออกมาจากการ serialization

258
บทที่ 8: Streams I/O

14:
15: //create an output file
16: CreateSequentialFile(String fName) {

intro. to Java (FEU.faa)


17: try {
18: fstream = new FileOutputStream(fName);
19: ostream = new ObjectOutputStream(fstream);
20: }
21: catch(IOException ioe) {
22: err.print("Cannot create file.%n");
23: System.exit(1);
24: }
25: }
26:
27: //reading records from user
28: public void addRecords() {
29: OfficeSupply record;
30:
31: out.printf("Enter id, description, price, and quantity%n> ");
32: Scanner input = new Scanner(System.in);
33: while(input.hasNext()) {
34: try {
35: //set record fileds from user
36: record = new OfficeSupply(input.nextInt(),
37: input.next(),
38: input.nextDouble(),
39: input.nextInt());
40:
41: //write record to file
42: ostream.writeObject(record);
43: }
44: catch(IOException ioe) {
45: err.println("Error writing to file");
46: System.exit(1);
47: }
48: catch(NoSuchElementException ne) {
49: err.println("Invalid input");
50: System.exit(1);
51: }
52: out.printf("Enter description, price, and quantity%n> ");
53: }
54: }
55:
56: //close output file
57: public void closeFile() {
58: try {
59: if(ostream != null)
60: ostream.close();
61: }
62: catch(IOException ioe) {
63: err.println("Error closing file.");
64: System.exit(1);
65: }
66: }
67: }

โปรแกรม CreateSequentialFile.java เลือกที่จะสงชื่อไฟลที่ใชเก็บขอมูลผานทาง command-


line argument ซึ่งชื่อไฟลจะถูกสงไปให FileOutputStream และเมื่อเราได file output
stream แลวเราก็สงไปให ObjectOutputStream เพื่อใหเปนทางผานของขอมูล (ในการเขียน
เขาสูไฟล) ตอไป (บรรทัดที่ 18 และ 19)

Method addRecords() จะเปนตัวทําหนาที่ในการอานขอมูลจาก Scanner เขาสู record ซึ่งเมื่อ


เราได record แลวเราก็เขียน record นี้เขาสูไฟลดวยคําสั่ง

ostream.writeObject(record);

ถาหากวา user ใสขอมูลที่ไมถูกตอง (ตามชนิดนั้น ๆ ของขอมูล) เราก็จะฟองกลับไปยังผูใช


ดวยการ throw NoSuchElementException พรอมทั้งยุติการทํางานทันที เชนเดียวกัน เราจะยุติ
การเขียนไฟลพรอมทั้ง throw IOException หากมีปญหาในการเขียนไฟลดังกลาว

259
เริ่มตนการเขียนโปรแกรมดวย Java

เราไดเขียนโปรแกรมเพื่อปอนขอมูลเขาสูไฟลขึ้นดังนี้

intro. to Java (FEU.faa)


1: /**
2: Writing sequential text file
3: */
4:
5: import static java.lang.System.out;
6:
7: class WriteSequentialFile {
8: public static void main(String[] args) {
9: if(args.length < 1) {
10: out.println("Usage: WriteSequentialFile file-name");
11: System.exit(1);
12: }
13:
14: //creates sequential file from user's input
15: CreateSequentialFile cf = new
CreateSequentialFile(args[0]);
16: cf.addRecords();
17: cf.closeFile();
18: }
19: }

สมมติวาเราอยากที่จะดูขอมูลที่เราเขียนเขาสูไฟลจากโปรแกรม WriteSequentialFile.java ดวย


text editor จากขอมูลนําเขานี้

D:\ >java WriteSequentialFile supply.dat


Enter id, description, price, and quantity
> 100 Notebook 12.5 50
Enter description, price, and quantity
> 200 PaperClip 1.25 60
Enter description, price, and quantity
> 300 BallpointPen 2.50 12
Enter description, price, and quantity
> ^Z

เราจะเห็นขอมูลคลาย ๆ กันกับที่เห็นดานลาง (ขึ้นอยูกับการใชเครื่องมือในการดูขอมูล)

ฌํ ♣sr ♀OfficeSupply▼↕ sY

ในการดึงขอมูลกลับออกมาจากไฟลนั้นเราก็ทําคลายกับการนําขอมูลเขา ตางกันตรงที่วาเราตอง
เรียกใช ObjectInputStream และ FileInputStream แทน ทั้งนี้การอานขอมูลก็ตองเรียกใช
method readObject() แทนการใช writeObject() ที่เราไดทํามากอนหนานี้

1: /**
2: Accessing sequential access text file
3: */
4:
5: import java.io.*;
6: import java.util.*;
7: import java.lang.*;
8: import static java.lang.System.out;
9: import static java.lang.System.err;
10:
11: class AccessSequentialFile {
12: private ObjectInputStream input;
13:
14: //open an input file
15: AccessSequentialFile(String fName) {
16: try {
17: input = new ObjectInputStream(
18: new FileInputStream(fName));
19: }
20: catch(IOException ioe) {
21: err.printf("Cannot open file: %s%n", fName);
22: System.exit(1);
23: }
24: }
25:
26: //read records from file

260
บทที่ 8: Streams I/O

27: public void readRecords() {


28: OfficeSupply record;
29: out.printf("%4s %-10s %12s %9s %7s%n",

intro. to Java (FEU.faa)


30: "Id", "Description", "Price", "Quantity", "Total");
31: try {
32: while(true) {
33: //retrieve record from file
34: record = (OfficeSupply)input.readObject();
35:
36: //send it to screen
37: out.printf("%4d %-15s %8.2f %6d %10.2f%n",
38: record.getId(),
39: record.getName(),
40: record.getPrice(),
41: record.getQuantity(),
42: record.getPrice()*record.getQuantity());
43: }
44: }
45: catch(EOFException eofe) {
46: //EOF is reached, terminate program
47: return;
48: }
49: catch(ClassNotFoundException cnfe) {
50: err.println("Cannot create object.");
51: System.exit(1);
52: }
53: catch(IOException ioe) {
54: err.println("Error reading file.");
55: System.exit(1);
56: }
57: }
58:
59: //close a file
60: public void closeFile() {
61: try {
62: if(input != null)
63: input.close();
64: }
65: catch(IOException ioe) {
66: err.println("Error closing file.");
67: System.exit(1);
68: }
69: }
70: }

จะเห็นวาในบรรทัดที่ 34 เราดึง object เขาสู record ดวยการใช method readObject() แตเรา


ตองทําการ cast object ที่ถูกสงกลับออกมาใหมีชนิดเปน OfficeSupply กอนที่เราจะสงขอมูลที่
อยูใน record ออกไปยังหนาจอ ซึ่งเราก็ตองใช method get..() ที่อยูใน class OfficeSupply
ในการแยกแยะขอมูลดังกลาว

เราจะยุติการทํางานเมื่อเราอานขอมูลจนหมด นั่นก็คือเรามาถึงจุดสุดทายของไฟล (EOF) ซึ่ง


ผูอานจะเห็นวาเราดักจับ error ดวย EOFException แตเราก็ไมทําอะไร แคกลับออกจาก
method เทานั้นเอง สวนการดักจับที่เหลืออยูก็เปนสิ่งจําเปนเพราะ error ที่อาจเกิดขึ้นอาจมา
จากการหา class ไมเจอ (ClassNotFoundException) หรือมีการผิดพลาดในการอานขอมูลจาก
ไฟล (IOException) ก็เปนได

เราไดเขียนโปรแกรมสําหรับดึงขอมูลออกมาจากไฟลดังนี้

1: /**
2: Test writing/reading sequential text file
3: */
4:
5: import static java.lang.System.out;
6:
7: class ReadSequentialFile {
8: public static void main(String[] args) {
9: if(args.length < 1) {
10: out.println("Usage: TestSequentialFile file-name");
11: System.exit(1);
12: }

261
เริ่มตนการเขียนโปรแกรมดวย Java

13:
14: //reading sequential file and display records
15: AccessSequentialFile af = new AccessSequentialFile(args[0]);

intro. to Java (FEU.faa)


16: af.readRecords();
17: af.closeFile();
18: }
19: }

หลังจากที่เราไดเรียกโปรแกรม WriteSequentialFile.java เพื่อใสขอมูลเขาสูไฟลแลว เราก็


เรียกโปรแกรม ReadSequentialFile.java เพื่ออานขอมูลกลับออกมา ซึ่งไดผลลัพธดังนี้

D:\>java ReadSequentialFile data.dat

Id Description Price Quantity Total


100 Notebook 23.00 45 1035.00
200 PaperClip 12.00 7 84.00
300 laserPrinter 543.00 3 1629.00

อยางไรก็ตามในการใสขอมูลเขาสู record นั้นเราก็ยังไมสามารถที่จะมีชองวางอยูภายใน field ที่


เปน String ได เราตองใช delimiter ตัวอื่นมาชวยใหความตองการดังกลาวเปนไปได ดังเชนที่
เราไดแสดงใหดูกอนหนานี้ดวยการใช '|' เปนตัวแบง

8.3.3 การสราง Binary file ในรูปแบบตาง ๆ

ตัวอยางการเก็บขอมูลในรูปแบบของ binary ที่เราไดแสดงใหดูกอนหนานี้เปนเพียงวิธีเดียวใน


หลาย ๆ วิธีที่เราทําได แต Java ยังมีกระบวนการอื่น ๆ ที่เราสามารถเรียกใชในการประมวลผล
ไฟลที่อยูในรูปแบบของ binary อีกมากมาย ซึ่งเราสามารถที่จะเลือกใช method หลาย ๆ ตัว
เปนตัวชวยในการเขียน เชน

Method Throws ความหมาย


writeBoolean(boolean) IOException เขียนคาของ boolean จํานวน 1 byte
writeShort(int) IOException เขียนคาของ short จํานวน 2 byte
writeInt(int) IOException เขียนคาของ int จํานวน 4 byte
writeLong(long) IOException เขียนคาของ long จํานวน 8 byte
writeFloat(float) IOException เขียนคาของ float จํานวน 4 byte
writeDouble(double) IOException เขียนคาของ double จํานวน 8 byte
writeChar(int) IOException เขียนคาของ char จํานวน 2 byte
writeChars(String) IOException เขียน String จํานวน 2 byte ตอ 1
ตัวอักษร
writeUTF(String) IOException เขียน String จํานวน 1 byte ตอตัวอักษร
บวกอีก 2 byte สําหรับคาของความยาว
ของ String

สําหรับการอานนั้น เราจะใช method นี้

Method Throws ความหมาย


readBoolean() EOFException อาน 1 byte สงคา boolean กลับ
readShort() EOFException อาน 2 byte สงคา Short กลับ
readInt() EOFException อาน 4 byte สงคา int กลับ
readLong() EOFException อาน 8 byte สงคา long กลับ
readFloat(0 EOFException อาน 4 byte สงคา float กลับ
readDouble() EOFException อาน 8 byte สงคา double กลับ
readChar() EOFException อาน 2 byte สงคา char กลับ
readUTF() EOFException อาน String UTF
skipBytes(int) EOFException ไมอานขอมูลจํานวนเทากับ byte ที่กําหนด

ตัวอยางตอไปจะเปนตัวอยางการเขียนขอมูลเขาสูไฟลในรูปแบบของ binary หลังจากนั้นจะอาน


กลับออกมา เราจะเริ่มตนดวยการสราง record จํานวน 3 ตัว สรางไฟลสําหรับเก็บ record
เหลานี้ อาน record ออกมาทีละตัว ประมวล พรอมทั้งแสดงผลไปยังหนาจอ

262
บทที่ 8: Streams I/O

ดังที่เราไดกลาวมาแลววา Record คือกลุมขอมูลที่ประกอบไปดวยขอมูลตาง ๆ ซึ่งอาจเปน

intro. to Java (FEU.faa)


ขอมูลชนิดเดียวกันหรือขอมูลตางชนิดกันก็ได ใน Java เราสราง record ดวยการใช class เปน
ตัวสราง เราจะกําหนดให record ของเรามี ขอมูลอยู 4 field ที่คลาย ๆ กับตัวอยางกอนหนานี้
คือ

1. id
2. description
3. price
4. quantity

เรามาดูโปรแกรมตัวอยางกันดีกวา

1: /**
2: Binary file - sequential access
3: */
4:
5: import java.io.*;
6: import static java.lang.System.out;
7: import static java.lang.System.err;
8:
9: class DataFile {
10: public static void main(String[] args) throws IOException {
11: FileOutputStream fout; //file output stream
12: DataOutputStream dout; //data output stream
13: FileInputStream fin; //file input stream
14: DataInputStream din; //data input stream
15: //setting up data to be written to a file
16: int[] id = {100, 200, 300};
17: String[] desc = {"Coffee Mug", "Bookmark", "Note book"};
18: double[] prices = {5.00, 2.00, 10.00};
19: int[] unit = {12, 50, 30};
20:
21: //variables storing data from file
22: int no;
23: String description;
24: double price, total = 0.00;
25: int unitPrice;
26:
27: //open file for writing
28: try {
29: fout = new FileOutputStream(args[0]);
30: dout = new DataOutputStream(fout);
31: for(int i = 0; i < desc.length; i++) {
32: dout.writeInt(id[i]);
33: dout.writeUTF(desc[i]);
34: dout.writeDouble(prices[i]);
35: dout.writeInt(unit[i]);
36: }
37: dout.close();
38: }
39: catch(FileNotFoundException e) {
40: err.println("Cannot open " + args[0]);
41: return;
42: }
43: catch(ArrayIndexOutOfBoundsException e) {
44: err.println("Usage: DataFile file-name.");
45: return;
46: }
47:
48: //open file for input
49: try {
50: fin = new FileInputStream(args[0]);
51: din = new DataInputStream(fin);
52: out.printf("%4s %-10s %12s %9s %7s%n",
53: "Id", "Description", "Price", "Quantity", "Total");
54: while(true) {
55: no = din.readInt();
56: description = din.readUTF();
57: price = din.readDouble();

263
เริ่มตนการเขียนโปรแกรมดวย Java

58: unitPrice = din.readInt();


59: total = price * unitPrice;
60: display(no, description, price, unitPrice, total);

intro. to Java (FEU.faa)


61: }
62: }
63: catch(EOFException e) {
64: //use this exception to stop the while loop
65: //and exit the program
66: System.exit(1);
67: }
68: catch(IOException e) {
69: out.println("Error reading file: " + e.toString());
70: System.exit(1);
71: }
72: }
73:
74: //display a record to screen
75: private static void display(int n, String d,
76: double p, int u, double t) {
77: out.printf("%4d %-15s %8.2f %6d %10.2f%n",
78: n, d, p, u, t);
79: }
80: }

โปรแกรมของเราใช DataOutputStream และ DataInputStream เปนตัวสงขอมูลไปเก็บไวที่


ไฟล และเราใช for/loop เปนตัวกําหนดจํานวนครั้งของการเขียน

fout = new FileOutputStream(args[0]);


dout = new DataOutputStream(fout);
for(int i = 0; i < desc.length; i++) {
dout.writeInt(id[i]);
dout.writeUTF(desc[i]);
dout.writeDouble(prices[i]);
dout.writeInt(unit[i]);
}

หลังจากนั้นเราก็อานขอมูลออกมาจากไฟล ตามลําดับ ถาเราลองเปดดูไฟลที่เก็บขอมูลที่เรา


สรางขึ้น เราจะไมสามารถอานได เพราะขอมูลเหลานี้ถูกเก็บอยูในรูปแบบของ binary data

ในการอานขอมูลออกมาจากไฟลนั้นเราใช while/loop ในการอานดวยการกําหนดใหเปน


infinite loop โดยเราจะใช error ที่เกิดขึ้นเปนตัวยุติการทํางานของ loop เนื่องจากวาเรารูวาใน
การอานขอมูลออกจากไฟลนั้น ในที่สุดเราก็จะอานเจอเครื่องหมายที่บงบอกถึงจุดจบของไฟล
(EOF) ซึ่งอาจเปนการเขียน code ที่ไมคอยจะสวยเทาไร แตก็ทําใหการทํางานของโปรแกรม
เปนไปตามที่เราตองการ

fin = new FileInputStream(args[0]);


din = new DataInputStream(fin);
System.out.println("Description\tPrice\tUnit\tTotal");
while(true) {
no = din.readInt();
description = din.readUTF();
price = din.readDouble();
unitPrice = din.readInt();
total = price * unitPrice;
display(description, price, unitPrice, total);
}

ผลลัพธที่ไดจากการ run คือ

D:\>java DataFile supply.dat

Id Description Price Quantity Total


100 Coffee Mug 5.00 12 60.00
200 Bookmark 2.00 50 100.00
300 Note book 10.00 30 300.00

264
บทที่ 8: Streams I/O

ตัวอยางที่เห็นดานบนเปนอีกวิธีหนึ่งที่เราตองการแสดงใหผูอานเห็นวาเราสามารถที่จะใช
method อื่น ๆ ที่ Java มีใหในการเขียนหรืออานไฟลที่อยูในรูปแบบของ binary ทั้งนี้ก็คงจะ

intro. to Java (FEU.faa)


ขึ้นอยูกับลักษณะของงาน หรือความถนัดของผูเขียนเอง

ตัวอยางตอไปนี้จะเปนการแสดงถึงความแตกตางในการใช writeUTF() กับการใช writeChars()

1: /**
2: Differences between WriteChars() and WriteUTF()
3: */
4:
5: import java.io.*;
6:
7: class WriteCharsAndWriteUTF {
8: public static void main(String[] args) throws IOException {
9: File f = new File(args[0]);
10: DataOutputStream out = new DataOutputStream(
11: new BufferedOutputStream(
12: new FileOutputStream(f)));
13: out.writeUTF("If it does not work, blame the computer.");
14: int utfSize = out.size();
15:
16: out.writeChars("If it does not work, blame the computer.");
17: int charsSize = out.size() - utfSize;
18:
19: out.close();
20: System.out.printf("writeUTF() writes %d bytes%n", utfSize);
21: System.out.printf("writeChars writes %d bytes%n", charsSize);
22: }
23: }

ผลลัพธที่ไดจากการ run คือ

>java WriteCharsAndWriteUTF difs.dat


writeUTF() writes 42 bytes.
writeChars writes 80 bytes.

โปรแกรมของเราเขียน string จํานวน 40 ตัวอักษรไปยังไฟลดวยการใช writeUTF() และ


writeChars() จากผลของการ run จะเห็นวา writeUTF() ใชเพียงแค 1 byte ตอตัวอักษรบวก
กับอีก 2 byte สําหรับเก็บความยาวของ string สวน writeChars() ใช 2 byte ตอตัวอักษร

8.4 การทํางานกับ Random-access file

ตัวอยางกอนหนานี้เราเขาหาขอมูลในไฟลแบบ กอน-หลัง หรือที่เรียกวา sequential access ซึ่ง


เปนวิธีการที่คอนขางที่จะใชเวลามาก ถาขอมูลมีอยูเปนจํานวนมาก มีวิธีการอีกวิธีหนึ่งที่ทําให
การเขาหา หรือคนหาขอมูลทําไดรวดเร็ว นั่นก็คือการเขาหาแบบที่ไมตองเขาหาตามลําดับ กอน-
หลัง หรือที่เรียกวา Random-access

คําวา random-access ในที่นี้หมายความวาขอมูลที่ถูกดึงออก หรือ นําเขาไมมีสวนเกี่ยวของกับ


ขอมูลที่ถูกดึงออก หรือ นําเขากอนหนานี้ เชน ในการคนหาเบอรโทรศัพทของใครสักคนผาน
ทาง operator ผูคนหา ณ เวลาปจจุบันไมมีความเกี่ยวของหรือความสัมพันธใด ๆ กับการคนหา
ที่ผานมากอนหนานั้น

ในการจัดการกับขอมูลของ random-access file นั้นจะใชตัวชี้ตําแหนง (logical pointer) เปน


ตัวกําหนดตําแหนงของการนําขอมูล เขา-ออก ซึ่งการกําหนดตําแหนงจะคํานวณจากจุดเริ่มตน
ของไฟล (ไมใชตําแหนงที่เก็บจริงในหนวยความจําสํารอง) เราเรียกระยะทางจากจุดเริ่มตนนี้วา
offset Java กําหนดการเขาหาตําแหนงเหลานี้ดวยการใชคําสั่ง seek เพราะฉะนั้นในการเขาหา
ขอมูล ถาเรารูขนาดของขอมูลที่ตายตัว (fixed length record) เราก็สามารถเขาหาขอมูล ณ
ตําแหนง p ดวย p * length

เราจะเริ่มตนดวยการสราง record ตัวอยาง โดยกําหนดให record ของเราประกอบดวย

ชื่อ (first name)


นามสกุล (last name)

265
เริ่มตนการเขียนโปรแกรมดวย Java

ปที่เกิด (year of birth)


เงินเดือน (salary)

intro. to Java (FEU.faa)


เราจะกําหนดให record เกิดจาก class ที่มีสมาชิกดังที่กลาวขางตน พรอมกับ method ตาง ๆ
ที่เกี่ยวของกับการทํางานกับ record นั้น ๆ

1: /**
2: Writing/Reading Random access file
3: */
4:
5: import java.io.*;
6: import java.io.RandomAccessFile;
7: import static java.lang.System.out;
8: import static java.lang.System.err;
9:
10: class RandomAccessFileDemo {
11: public static void main(String[] args) throws IOException {
12: //data to be written to a file
13: String[] names = {"John", "Paul", "Stephen", "Hendrix"};
14: String[] lasts = {"Kawakami", "Collins", "Anderson", "McCoy"};
15: int[] years = {1965, 1978, 1985, 1972};
16: double[] pays = {2400.0, 500.0, 5000.0, 9500.0};
17:
18: //random positions for reading
19: int[] random = {2, 3, 0, 1};
20:
21: //writing to a file
22: RandomAccessFile fout = new RandomAccessFile("emp.dat", "rw");
23: try {
24: for(int i = 0; i < 4; i++) {
25: Employee emp = new Employee(names[i], lasts[i],
26: years[i], pays[i]);
27: emp.write(fout);
28: }
29: }
30: catch(IOException e) {
31: err.println("Error writing file");
32: e.printStackTrace();
33: System.exit(1);
34: }
35: finally {
36: if(fout != null) {
37: fout.close();
38: }
39: }
40:
41: //reading from a file by position given in random array
42: RandomAccessFile fin = null;
43: try {
44: fin = new RandomAccessFile("emp.dat", "r");
45: Employee em = new Employee();
46: out.printf("Records in file: %d (%d bytes)%n%n",
47: em.size(fin), fin.length());
48: for(int i = 0; i < 4; i++) {
49: em.read(fin, random[i]);
50: out.print("Record #" + (i+1) + ": ");
51: em.show();
52: }
53: }
54: catch(IOException e) {
55: err.println("Error reading file");
56: e.printStackTrace();
57: System.exit(1);
58: }
59: finally {
60: if(fin != null)
61: fin.close();
62: }
63: }
64: }

266
บทที่ 8: Streams I/O

โปรแกรมตัวอยางของเราใช array 4 ตัวเปนตัวเก็บ record ที่ตองการเขียนไปยังไฟล โดย


กําหนดใหการเขียนขอมูลเปนไปตามลําดับของขอมูลที่อยูใน array ทั้ง 4 ตัว ดู code ดานลาง

intro. to Java (FEU.faa)


ประกอบ

22: RandomAccessFile fout = new RandomAccessFile("emp.dat", "rw");


23: try {
24: for(int i = 0; i < 4; i++) {
25: Employee emp = new Employee(names[i], lasts[i],
26: years[i], pays[i]);
27: emp.write(fout);
28: }
29: }

เราเริ่มตนดวยการประกาศให fout เปนไฟลแบบ random access (บรรทัดที่ 22) โดยเรา


กําหนดชื่อไฟลใหเปน "emp.dat" พรอมทั้งกําหนดใหไฟลตัวนี้รองรับทั้งการอานและเขียนดวย
"rw" ในบรรทัดที่ 25 เราสราง object จาก class Employee ดวยขอมูลใน array ซึ่งเมื่อไดแลว
เราก็เขียนเขาสูไฟลดวยคําสั่ง write() ในบรรทัดที่ 27

สวนการอานออกมานั้นเราจะทําการอานตามตําแหนงที่กําหนดไวใน array random ดังนี้

42: RandomAccessFile fin = null;


43: try {
44: fin = new RandomAccessFile("emp.dat", "r");
45: Employee em = new Employee();
46: out.printf("Records in file: %d (%d bytes)%n%n",
47: em.size(fin), fin.length());
48: for(int i = 0; i < 4; i++) {
49: em.read(fin, random[i]);
50: out.print("Record #" + random[i] + ": ");
51: em.show();
52: }
53: }

เรากําหนดให fin เปน object จาก random access file ที่มีการกําหนดใหเปนเพียงแคการอาน


ดวย "r" และเราแสดงจํานวนของ record และขนาดของไฟลกอนที่เราจะแสดงขอมูลที่อยูใน
ไฟลทั้งหมด ในบรรทัดที่ 46 และ 48 – 52 ตามลําดับ

กระบวนการในการเขียน (หรืออาน) เราไดสรางไวใน class Employee ซึ่งเปน class ที่ทําหนาที่


หลักใหเรา มี code ดังนี้

1: /**
2: Simple record for random-access file
3: */
4:
5: import java.io.*;
6: import java.io.RandomAccessFile;
7: import java.lang.String;
8: import static java.lang.System.out;
9: import static java.lang.System.err;
10:
11: class Employee {
12: private String firstName; //15 characters (30 bytes)
13: private String lastName; //15 characters (30 bytes)
14: private int year; //4 bytes
15: private double salary; //8 bytes
16: private static final int RECORD_SIZE = 72;
17:
18: //default constructor
19: Employee() {
20: firstName = "";
21: lastName = "";
22: year = 0;
23: salary = 0.0D;
24: }
25:
26: //setting up fields
27: Employee(String firstName, String lastName,
28: int year, double salary) {

267
เริ่มตนการเขียนโปรแกรมดวย Java

29: this.firstName = firstName;


30: this.lastName = lastName;
31: this.year = year;

intro. to Java (FEU.faa)


32: this.salary = salary;
33: }
34:
35: //write record to file
36: public void write(RandomAccessFile file) throws IOException {
37: try {
38: //write equal-length strings
39: writeString(file, firstName, 15);
40: writeString(file, lastName, 15);
41: //write int and double
42: file.writeInt(year);
43: file.writeDouble(salary);
44: }
45: catch(IOException e) {
46: err.println("Error writing file");
47: e.printStackTrace();
48: System.exit(1);
49: }
50: }
51:
52: public void setFirstName(String name) {
53: firstName = name;
54: }
55:
56: public void setLastName(String name) {
57: lastName = name;
58: }
59:
60: public void setSalary(double pay) {
61: salary = pay;
62: }
63:
64: public int recSize() {
65: return RECORD_SIZE;
66: }
67:
68: public void setYear(int y) {
69: year = y;
70: }
71:
72: //reading record from file with a given position
73: public void read(RandomAccessFile file, int position)
74: throws IOException {
75: try {
76: //locate position to read
77: file.seek((long)(position * RECORD_SIZE));
78: //reading strings
79: firstName = readString(file, 15);
80: lastName = readString(file, 15);
81: //reading int and double
82: year = file.readInt();
83: salary = file.readDouble();
84: }
85: catch(IOException e) {
86: err.println("Error reading file");
87: e.printStackTrace();
88: System.exit(1);
89: }
90: }
91:
92: //helper method to write a string with specified length
93: public void writeString(DataOutput out, String s, int len)
94: throws IOException {
95: for(int i = 0; i < len; i++) {
96: if(i < s.length())
97: out.writeChar(s.charAt(i));
98: else
99: out.writeChar(0);
100: }
101: }
102:

268
บทที่ 8: Streams I/O

103: //helper method to read a string


104: public String readString(DataInput in, int len)
105: throws IOException {

intro. to Java (FEU.faa)


106: String s = "";
107: int i = 0;
108: while(i < len) {
109: char c = in.readChar();
110: if(c != 0)
111: s += c;
112: i++;
113: }
114: return s;
115: }
116:
117: //number of records in file
118: public int size(RandomAccessFile file) throws IOException{
119: int size = 0;
120: try {
121: size = (int)file.length() / RECORD_SIZE;
122: }
123: catch(IOException e) {
124: err.println("Error reading file.");
125: System.exit(1);
126: }
127: return size;
128: }
129:
130: //display a record to screen
131: public void show() {
132: out.printf("%-10s %-10s %5d %8.2f%n", firstName,
133: lastName, year, salary);
134: }
135: }

method หลัก ๆ ที่อยูใน class Employee คือ

write(RandomAccessFile)
read(RandomAccessFile, position)
writeString(DataOutptu, String, length)
readString(DataInput, length)
size(RandomAccessFile)

เนื่องจากวาเราตองรูขนาดของ record ของเราวามีความยาวเทาไร เราจึงจะสามารถที่จะเลื่อน


logical pointer ของเราไปยังตําแหนงตาง ๆ ที่เราตองการได ดังนั้นเราจึงตองกําหนดขนาดของ
record ภายในโปรแกรมของเราเพื่อใหการทํางานสะดวกขึ้น ทั้งนี้การกําหนดขนาดก็ตองขึน ้ อยู
กับการเลือกใชวิธีการเขียนของเราวาใช method ตัวไหนเปนตัวเขียน ถาเราใช writeUTF() เรา
ก็ตองกําหนดขนาดแบบหนึ่ง แตถา เราใช writeChars() เราก็ตองกําหนดขนาดอีกแบบหนึง่ (ดู
ความแตกตางของการใชจากตาราง) สําหรับโปรแกรมที่เราเขียนขึ้น เรากําหนดใหขนาดของ
record เปน 72 byte ก็เพราะวาเราใช writeChar() เปนตัวเขียน ดังนั้น ทั้งชื่อและนามสกุลใช
30 byte สวน year และ salary ใช 4 และ 8 byte ตามลําดับ

หนาที่ของ write() คือ การนําขอมูลจาก field ตาง ๆ ของ Employee object เขาสูไฟล
ตามลําดับของขอมูลใน array ดังที่เราไดกลาวไวแลว โดยไดรับการชวยเหลือจาก

public void writeString(DataOutput out, String s, int len)


throws IOException {
for(int i = 0; i < len; i++) {
if(i < s.length())
out.writeChar(s.charAt(i));
else
out.writeChar(0);
}
}

สวนการอานขอมูลออกจากไฟล เราหาตําแหนงดวยการใช array random และ method


seek() เปนตัวชวย ภาพที่ 8-1 แสดงถึงตําแหนงของ record ที่อยูในไฟล การเขาหา record
ตาง ๆ ทําไดจากการคูณขนาดของ record และตําแหนงที่เราตองการเขาหา

269
เริ่มตนการเขียนโปรแกรมดวย Java

file.seek((long)(position * RECORD_SIZE));

intro. to Java (FEU.faa)


0 72 144 216 288 position

Record 0 Record 1 Record 2 Record 3

ภาพที่ 8-1 ตําแหนงของ record ที่อยูในไฟล

ซึ่งเมื่อหาตําแหนงไดแลวเราก็อานขอมูลไปเก็บไวในตัวแปรที่เราไดกําหนดไว ดังที่ไดแสดงให
ดูในบรรทัดที่ 79 – 83 เชนเดียวกันกับการเขียนขอมูลเขาสูไฟล เราตองใช method
readString() ชวยในการอานขอมูลที่เปน String ใหเรา

public String readString(DataInput in, int len) throws IOException {


String s = "";
int i = 0;
while(i < len) {
char c = in.readChar();
if(c != 0)
s += c;
i++;
}
return s;
}

เราใช readChar() ในการอานขอมูลแตละตัว และเราจะตรวจสอบวาคาที่อานไดเปน 0 หรือไม


ถาไมเราก็เชื่อมคานี้เขาสู string s พรอมกับเลื่อนไปอานขอมูลตัวตอไป ทําการอานและเชื่อม
จนกวาจะหมดขอมูล สําหรับการหาจํานวนของ record ที่มีอยูในไฟลนั้นเราหาไดดว ยการใช
ขนาดของไฟลหารดวยขนาดของ record ที่เราไดกําหนดไว

public int size(RandomAccessFile file) throws IOException{


int size = 0;
try {
size = (int)file.length() / RECORD_SIZE;
}
catch(IOException e) {
System.err.println("Error reading file.");
System.exit(1);
}
return size;
}

เราใช readChar() และ writeChar() ในการเขียนและอานขอมูล ดังนั้นเราตองคํานึงถึงจํานวน


ของ byte ที่เราตองใชสําหรับ record แตละตัววาถูกตองตามที่ไดกําหนดหรือไม สิ่งที่ตอง
คํานึงถึงในเรื่องของการใช random access file ก็คือ field แตละ field ควรเปน field ที่มีขนาด
ตายตัว เพราะจะทําใหการคนหาตําแหนงทําไดอยางถูกตอง

ผลลัพธที่เราไดจากการ run คือ

D:\>java RandomAccessFileDemo
Enter file name: temp.dat
Records in file: 4 (288 bytes)

Record #3: Stephen Anderson 1985 5000.00


Record #4: Hendrix McCoy 1972 9500.00
Record #1: John Kawakami 1965 2400.00
Record #2: Paul Collins 1978 500.00

270
บทที่ 8: Streams I/O

ผูอานจะเห็นวาผลลัพธที่ไดจากการอานนั้นเปนไปตามขอกําหนดที่อยูใน array random คือ {2,


3, 0, 1} ซึ่งไดเราปรับใหเปนตัวเลขที่เหมาะสมในการอาน ในตอนตอไปเราจะมาดูกันถึงขั้นตอน

intro. to Java (FEU.faa)


และวิธีการในการปรับปรุงขอมูลที่อยูภายใน random-access file

8.4.1 การปรับปรุง (Update) ขอมูลในไฟล

การ update ขอมูลก็คลาย ๆ กับการเขียนขอมูลเขาสูไฟล เราตองหาตําแหนงของ record ที่


ตองการ update ใหเจอ โปรแกรมตัวอยางที่เราจะแสดงใหดูตอไปจะเปนการปรับปรุงเฉพาะสวน
ของขอมูลที่เปนเงินเดือน ของโปรแกรมขอมูลที่เราไดจากการ run โปรแกรม
RandomAccessFielDemo.java กอนหนานี้

โดยเราจะเริ่มตนดวยการอาน record ที่ตองการปรับปรุงออกมาจากไฟลกอน (หลังจากที่เราได


ทําการเปดไฟลที่เก็บขอมูล พรอมทั้งอาน record ที่ตองการปรับปรุงจาก user เรียบรอยแลว
บรรทัดที่ 23 - 25 ในโปรแกรม Update.java) ดังที่เห็นใน method getRecord() นี้

private static Employee getRecord(RandomAccessFile f, int num)


throws IOException {
Employee record = new Employee();
f.seek((long)(num - 1) * record.recSize());
record.read(f, num-1);
return record;
}

เมื่อเราหา record เจอจากการใช seek() เราก็อาน record ทั้งหมดเก็บไวในตัวแปรชื่อ record


ซึ่งเราจะสงกลับออกไปเพื่อการประมวลขั้นตอไป

เมื่อเราอานคาของเงินเดือน (salary) ใหมใหกับ record ที่ตองการปรับปรุงไดแลว (จาก


method getUpdateInfo() บรรทัดที่ 75 - 78)

private static double getUpdateInfo(Employee emp) {


Scanner input = new Scanner(System.in);
out.printf("Enter new salary for %s %s: ",
emp.getFirstName(),
emp.getLastName());
return input.nextDouble();
}

เมื่อเราไดขอมูลใหมที่ถูกตองแลว เราก็ทําการนําขอมูลนี้ใสกลับเขาไปในไฟลดว ยการใช


method write()

double newPay = getUpdateInfo(emp);


emp = new Employee(emp.getFirstName(), emp.getLastName(),
emp.getYear(), newPay);
//write the whole record
emp.write(f);

ผูอานจะเห็นวากระบวนการในการปรับปรุงขอมูลที่อยูใน random-access file นั้นไมยุงยากอะไร


มากมายนัก และการหาขอมูลในการปรับปรุงก็ไมยุงยากเหมือนกับการปรับปรุงไฟลที่เปน
sequential access ที่เราตองอานขอมูลไปทีละตัวจนกวาจะเจอขอมูลที่ตองการ

Code ทั้งหมดของโปรแกรม Update.java มีดังนี้

1: /**
2: Updating Random-access file
3: */
4:
5: import java.io.*;
6: import java.util.Scanner;
7: import java.io.RandomAccessFile;
8: import static java.lang.System.out;
9: import static java.lang.System.err;
10:
11: class Update {
12: public static void main(String[] args) throws IOException {

271
เริ่มตนการเขียนโปรแกรมดวย Java

13: RandomAccessFile f = null;


14: Employee emp = new Employee();
15: Scanner input = new Scanner(System.in);

intro. to Java (FEU.faa)


16:
17: if(args.length < 1) {
18: err.println("Usage: Update file-name");
19: System.exit(1);
20: }
21:
22: try {
23: f = new RandomAccessFile(args[0], "rw");
24: out.print("Enter record # to update: ");
25: int recNum = input.nextInt(); //get record number
26: updateData(f, recNum); //update record
27: showData(f, args[0]); //display records
28: }
29: catch(IOException e) {
30: err.println("Error processing file");
31: e.printStackTrace();
32: System.exit(1);
33: }
34: }
35:
36: //update record
37: private static void updateData(RandomAccessFile f,
38: int recNum)
39: throws IOException {
40: try {
41: Employee emp = getRecord(f, recNum);
42: //locate record
43: f.seek((long)(recNum - 1) * emp.recSize());
44: double newPay = getUpdateInfo(emp);
45: emp = new Employee(emp.getFirstName(),
46: emp.getLastName(),
47: emp.getYear(),
48: newPay);
49: //write the whole record
50: emp.write(f);
51: }
52: catch(IOException e) {
53: err.println("Error writing file");
54: e.printStackTrace();
55: System.exit(1);
56: }
57: finally {
58: if(f != null)
59: f.close();
60: }
61: }
62:
63: //get record to update
64: private static Employee getRecord(RandomAccessFile f, int num)
65: throws IOException {
66: Employee record = new Employee();
67: f.seek((long)(num - 1) * record.recSize());
68: record.read(f, num-1);
69: return record;
70: }
71:
72: //get new salary
73: private static double getUpdateInfo(Employee emp) {
74: Scanner input = new Scanner(System.in);
75: out.printf("Enter new salary for %s %s: ",
76: emp.getFirstName(),
77: emp.getLastName());
78: return input.nextDouble();
79: }
80:
81: //display all records in this file
82: private static void showData(RandomAccessFile f, String fName)
83: throws IOException {
84: try {
85: f = new RandomAccessFile(fName, "r");
86: Employee em = new Employee();

272
บทที่ 8: Streams I/O

87: for(int i = 0; i < 4; i++) {


88: em.read(f, i);
89: out.print("Record #" + (i+1) + ": ");

intro. to Java (FEU.faa)


90: em.show();
91: }
92: }
93: catch(IOException e) {
94: err.println("Error reading file");
95: e.printStackTrace();
96: System.exit(1);
97: }
98: finally {
99: if(f != null)
100: f.close();
101: }
102: }
103:
104: }

ผลลัพธที่เราไดจากการ run โปรแกรม Update.java คือ

D:\>java RandomAccessFileDemo
Enter file name: temp.dat
Records in file: 4 (288 bytes)

Record #3: Stephen Anderson 1985 5000.00


Record #4: Hendrix McCoy 1972 9500.00
Record #1: John Kawakami 1965 2400.00
Record #2: Paul Collins 1978 500.00

D:\>java Update temp.dat


Enter record # to update: 3
Enter new salary for Stephen Anderson: 45000
Record #1: John Kawakami 1965 2400.00
Record #2: Paul Collins 1978 500.00
Record #3: Stephen Anderson 1985 45000.00
Record #4: Hendrix McCoy 1972 9500.00

สรุป

เราไดแสดงตัวอยางของการทํางานกับ text file และ binary file การอาน การเขียน รวมไปถึง


กระบวนการตาง ๆ ที่เกี่ยวของกับไฟล โดยสรุปแลวเราไดพูดถึง

9 การตรวจสอบขอมูลที่เกี่ยวของกับไฟลดวย File และ method ตาง ๆ เชน ifFile()


canRead() เปนตน
9 การใช FileReader FileWriter FileInputStream FileOutputStream
9 การใช DataOutputStream BufferedOutputStream
9 การใช StreamTokenizer และ StringTokenizer
9 การสรางและใชงาน text file
9 การใช delimiter ในการอานขอมูลจาก text file
9 การสรางและใชงาน binary file
9 การสรางและใชงาน Random-access file
9 การ update ขอมูลใน random-access file

แบบฝกหัด

1. จงเขียนโปรแกรมที่ทําหนาที่แสดงขอมูลเกี่ยวกับไฟลทุกตัวที่มีอยูใน directory นั้น ๆ เชน


ถาเจอไฟลใหแสดงถึง ขนาด วันที่สราง ถาเจอ directory ใหแสดงถึงจํานวนไฟลที่มีอยูใน
directory นั้น พรอมกับวันที่ directory นี้ถูกสรางขึ้น

2. จงเขียนโปรแกรมที่ copy ขอมูลจากไฟลหนึง่ ไปยังอีกไฟลหนึ่งผานทาง command-line

3. จงเขียนโปรแกรมที่สรางขอมูลชนิด int ดวยการสุมจํานวนเทากับ 100 ตัว หลังจากนั้นใหนํา


ขอมูลทั้งหมดไปเก็บไวใน text file โดยกําหนดใหจํานวนของขอมูลตอแถวเทากับ 10 ตัว

273
เริ่มตนการเขียนโปรแกรมดวย Java

4. จงเขียนโปรแกรมที่อานขอมูลจากไฟลที่เขียนขึ้นในขอ 3 เสร็จแลวใหคํานวณหาผลรวม
ของขอมูลในแตละแถว หลังจากนั้นใหเขียนขอมูลในแตละแถวรวมทั้งผลรวมที่หาไดลงใน

intro. to Java (FEU.faa)


ไฟลตัวเดิม (ขอมูลในแตละแถวจะมีจํานวนเทากับ 11 ตัวโดยที่ตัวสุดทายในแถวเปนผลรวม
ของขอมูลในแถวนั้น)

5. จงปรับปรุงโปรแกรมที่เขียนขึ้นในขอ 4 โดยใหมีการคํานวณหาคาเฉลี่ยของขอมูลทุกตัวใน
แนวตั้ง (column) นําผลลัพธที่ไดพรอมทั้งขอมูลในแตละแถวไปเก็บไวในไฟลตัวใหม

6. จงเขียนโปรแกรมที่อานขอมูลในรูปแบบที่กําหนดใหดานลางนี้ หลังจากนั้นใหแสดงขอมูลที่
อานไดไปยังหนาจอ โดยไมตอ งแสดง delimiter ที่ใช ใหกําหนดจํานวนของขอมูลที่มีอยูใน
ไฟลจํานวนเทากับ 10 แถว

John Longman+25.0+30.50+48.00
Peter Wang+35.00+50.00+98.00


Mike Kawakami+90.00+80.00+85.00

7. จงเขียนโปรแกรมที่สราง binary file จาก class Student ที่กําหนดใหนี้ โดยทําการอาน


ขอมูลเบื้องตนมาจาก keyboard ซึ่งมีขอมูลดังนี้

ชื่อตน เปน String จํานวน 15 ตัวอักษร


นามสกุล เปน String จํานวน 15 ตัวอักษร
เกรด เปน array ที่เก็บ double เชน 85.0 90.5 ทั้งนี้จํานวนของเกรดที่มีอยูจะมีกี่ตัวก็ได
เสร็จแลวใหทําการอานขอมูลกลับออกมาจากไฟล พรอมทั้งคํานวณหาเกรดเฉลี่ยของขอมูล
(นักศึกษา) ทุกตัว เมื่อไดเกรดเฉลี่ยแลวใหแสดงผลออกทางหนาจอ

class Student {
String firstName;
String lastName;
double[] grades;
}

8. จงเขียนโปรแกรมที่สราง random-access file จากโครงสรางของ Student ที่มีอยูในขอ 7


โดยใหuser เปนผูกําหนดตําแหนงของ record ที่ตองการเขียนเอง ใหเขียน method ที่
แสดงขอมูลของ record (ที่กําหนดมาจาก keyboard) ไปยังหนาจอ

9. จงปรับปรุงโปรแกรมในขอ 6 ดวยการเพิ่ม method ในการคํานวณหาผลรวมของขอมูลใน


แตละแถว หลังจากนั้นใหนําขอมูลทั้งหมดไปเก็บไวในไฟลตัวใหม โดยใหผลรวมที่หาได
เปนขอมูลตัวสุดทายในแถวนัน

274

You might also like