บทที่ 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 intro. to Java (FEU.faa)
24: if(file.isDirectory()) {
25: String []list = file.list();
26: out.println("\n" + file + " is a directory.");
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 intro. to Java (FEU.faa)
กําหนดใด ๆ แตถาตองการแสดงผลดว ยขอกาํ หนด เชน ตอ งการแสดงไฟลท กุ ตัวทม่ี คี าํ วา "te"
อยูในไฟลเราก็ใชคาํ สง่ั DirListing te เปนตน
โปรแกรมของเราตอ งเรียกใช 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 intro. to Java (FEU.faa)
editor พดู งาย ๆ ก็คือ เราสามารถทจ่ี ะอานขอ มูลเหลา นไ้ี ด (human-readable form)
เราเรยี กไฟลแบบนี้วา 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: } intro. to Java (FEU.faa)
17: in.close();
18: }
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 intro. to Java (FEU.faa)
19:
20: stream = new StreamTokenizer(in);
21: stream.eolIsSignificant(true); //ignored EOL
22:
23: }
24: catch(FileNotFoundException e) {
25:
26: err.println("Cannot find file: " + args[0]);
27:
28: System.exit(1);
29: }
30:
31: //arrays to store data only 3 items as example e.g.
32: //description prices unit
33:
34: //Coffee-Mug 12.50 30
35: String[] descs = new String[3];
36: double[] prices = new double[3];
37:
38: int[] units = new int[3];
39:
40: int i = 0, j = 0, k = 0; //arrays' indexes
41:
42: boolean first = true; //make sure correct data is read
43:
44: try {
45: //reading tokens into corresponding arrays
46:
47: //until EOF is reached
48:
49: while(stream.nextToken() != StreamTokenizer.TT_EOF) {
50:
51: //data is a string
52:
53: if(stream.ttype == StreamTokenizer.TT_WORD) {
54:
55: descs[i++] = stream.sval;
56:
57: }
58:
59: //data is a number (price or unit)
60:
61: if(stream.ttype == StreamTokenizer.TT_NUMBER) {
62:
63: //first data in line is price
64:
65: if(first == true) {
66: prices[j++] = stream.nval;
67:
68: first = false;
69:
70: }
71:
72: //next one is unit price
73:
74: else {
75:
76: units[k++] = (int)stream.nval;
77:
78: first = true;
79:
80: } }
81:
82: //ignore EOL
83:
84: if(stream.ttype == StreamTokenizer.TT_EOL) {
85: /* do nothing */
86: }
87:
88: } }
in.close(); //close the stream
}
//trouble reading file
catch(IOException e) {
err.println("Error reading file.");
e.printStackTrace();
System.exit(1);
}
//too many items for arrays
catch(ArrayIndexOutOfBoundsException e) {
err.println("Array index out of bound.");
e.printStackTrace();
System.exit(1);
}
display(descs, prices, units); //display data
}
//display data to screen
private static void display(String[] d, double[] p, int[] u) {
double total = 0.00;
for(int i = 0; i < d.length; i++) {
total = p[i] * u[i];
out.printf("%-15s %6.2f %4d %6.2f%n", d[i],
p[i], u[i], total);
}
}
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 24 300.00
12.50
ขอ มลู ท่เี กบ็ ไวในไฟล 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 intro. to Java (FEU.faa)
มขี อ มูลทเี่ ปน string อยรู ะหวา งขอมูลเหลา น)้ี ดังนั้นเราจึงไมนยิ มใช text file ในการเกบ็ ขอมูล
ทเี่ ปน primitive type เราควรใช text file ในการทํางานกบั ขอมูลท่ีเปน text เทานน้ั
เราสามารถท่ีจะทาํ ใหก ารทํางานกับขอ มูลท่ีเปน 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); intro. to Java (FEU.faa)
fReader = new FileReader(dataFile);
buffer = new BufferedReader(fReader);
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; intro. to Java (FEU.faa)
private int quantity;
11:
12: OfficeSupply() {
//calling constructor below
13: this(0, null, 0.0, 0);
14:
15: }
16: OfficeSupply(int id, String name,
17: double price, int quantity) {
18:
this.id = id;
19: this.name = name;
20: this.price = price;
this.quantity = quantity;
21: }
22:
23: public void setId(int id) {
this.id = id;
24:
25: }
26:
public void setName(String name) {
27: this.name = name;
28: }
29:
public void setPrice(double price) {
30: this.price = price;
31:
}
32:
33: public void setQuantity(int quantity) {
34: this.quantity = quantity;
35: }
36:
37: public int getId() {
return id;
38:
}
39:
40: public String getName() {
return name;
41:
}
42:
43: public double getPrice() {
44: return price;
45:
}
46:
public int getQuantity() {
47: return quantity;
48:
}
49:
50:
51:
52:
53:
54:
55:
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: //create an output file intro. to Java (FEU.faa)
CreateSequentialFile(String fName) {
15:
16: try {
fstream = new FileOutputStream(fName);
17: ostream = new ObjectOutputStream(fstream);
18:
19: }
catch(IOException ioe) {
20:
21: err.print("Cannot create file.%n");
22: System.exit(1);
}
23: }
24:
//reading records from user
25: public void addRecords() {
26:
27: OfficeSupply record;
28: out.printf("Enter id, description, price, and quantity%n> ");
29: Scanner input = new Scanner(System.in);
30: while(input.hasNext()) {
31: try {
//set record fileds from user
32: record = new OfficeSupply(input.nextInt(),
33: input.next(),
input.nextDouble(),
34: input.nextInt());
35:
//write record to file
36: ostream.writeObject(record);
37: }
38: catch(IOException ioe) {
err.println("Error writing to file");
39: System.exit(1);
40: }
41: catch(NoSuchElementException ne) {
err.println("Invalid input");
42: System.exit(1);
}
43: out.printf("Enter description, price, and quantity%n> ");
44: }
}
45:
//close output file
46: public void closeFile() {
47:
48: try {
49: if(ostream != null)
ostream.close();
50:
}
51: catch(IOException ioe) {
52:
err.println("Error closing file.");
53: System.exit(1);
}
54: }
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
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() { intro. to Java (FEU.faa)
OfficeSupply record;
28: out.printf("%4s %-10s %12s %9s %7s%n",
29: "Id", "Description", "Price", "Quantity", "Total");
try {
30: while(true) {
31: //retrieve record from file
32: record = (OfficeSupply)input.readObject();
33: //send it to screen
34: out.printf("%4d %-15s %8.2f %6d %10.2f%n",
35:
record.getId(),
36: record.getName(),
37: record.getPrice(),
record.getQuantity(),
38: record.getPrice()*record.getQuantity());
39: }
40: }
catch(EOFException eofe) {
41: //EOF is reached, terminate program
42: return;
43: }
catch(ClassNotFoundException cnfe) {
44: err.println("Cannot create object.");
System.exit(1);
45: }
46: catch(IOException ioe) {
err.println("Error reading file.");
47: System.exit(1);
48: }
}
49:
50: //close a file
51: public void closeFile() {
52: try {
53: if(input != null)
54: input.close();
55: }
catch(IOException ioe) {
56:
57: err.println("Error closing file.");
System.exit(1);
58: }
}
59:
60:
61:
62:
63:
64:
65:
66:
67:
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: intro. to Java (FEU.faa)
14: //reading sequential file and display records
15: AccessSequentialFile af = new AccessSequentialFile(args[0]);
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
writeInt(int) IOException เขยี นคา ของ short จาํ นวน 2 byte
writeLong(long) IOException เขียนคา ของ int จาํ นวน 4 byte
writeFloat(float) IOException
writeDouble(double) IOException เขยี นคา ของ long จํานวน 8 byte
writeChar(int) IOException เขยี นคาของ float จํานวน 4 byte
writeChars(String) IOException เขียนคาของ double จํานวน 8 byte
เขยี นคา ของ char จาํ นวน 2 byte
writeUTF(String) IOException เขยี น String จํานวน 2 byte ตอ 1
ตัวอกั ษร
เขยี น 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(); intro. to Java (FEU.faa)
total = price * unitPrice;
59: display(no, description, price, unitPrice, total);
60: }
}
61: catch(EOFException e) {
62: //use this exception to stop the while loop
63: //and exit the program
System.exit(1);
64: }
65: catch(IOException e) {
66: out.println("Error reading file: " + e.toString());
System.exit(1);
67: }
68: }
69: //display a record to screen
70: private static void display(int n, String d,
71:
double p, int u, double t) {
72: out.printf("%4d %-15s %8.2f %6d %10.2f%n",
73:
74: n, d, p, u, t);
}
75:
76:
77:
78:
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 intro. to Java (FEU.faa)
ตัวอยา งทีเ่ ห็นดา นบนเปนอกี วิธีหน่งึ ทเี่ ราตองการแสดงใหผ ูอา นเห็นวา เราสามารถท่จี ะใช
method อ่ืน ๆ ที่ Java มใี หในการเขียนหรืออานไฟลท ่อี ยูใ นรปู แบบของ binary ทงั้ นีก้ ็คงจะ
ขึ้นอยูก ับลักษณะของงาน หรือความถนดั ของผเู ขยี นเอง
ตัวอยางตอไปนจ้ี ะเปน การแสดงถึงความแตกตา งในการใช 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 intro. to Java (FEU.faa)
ปท เี่ กดิ (year of birth)
เงินเดอื น (salary)
เราจะกาํ หนดให 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 ทต่ี อ งการเขียนไปยงั ไฟล โดย intro. to Java (FEU.faa)
กําหนดใหก ารเขยี นขอมลู เปนไปตามลําดบั ของขอ มลู ทอ่ี ยใู น array ทงั้ 4 ตวั ดู code ดา นลา ง
ประกอบ
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; intro. to Java (FEU.faa)
this.lastName = lastName;
30: this.year = year;
31: this.salary = salary;
}
32:
33: //write record to file
34: public void write(RandomAccessFile file) throws IOException {
35: try {
36: //write equal-length strings
37: writeString(file, firstName, 15);
writeString(file, lastName, 15);
38: //write int and double
39: file.writeInt(year);
file.writeDouble(salary);
40:
41: }
42: catch(IOException e) {
43: err.println("Error writing file");
44: e.printStackTrace();
45: System.exit(1);
}
46: }
47: public void setFirstName(String name) {
48: firstName = name;
49: }
50:
public void setLastName(String name) {
51: lastName = name;
52:
53: }
54: public void setSalary(double pay) {
55: salary = pay;
56:
}
57:
public int recSize() {
58: return RECORD_SIZE;
59:
}
60:
public void setYear(int y) {
61: year = y;
62:
63: }
64:
//reading record from file with a given position
65: public void read(RandomAccessFile file, int position)
66: throws IOException {
67: try {
68: //locate position to read
file.seek((long)(position * RECORD_SIZE));
69: //reading strings
70: firstName = readString(file, 15);
lastName = readString(file, 15);
71: //reading int and double
year = file.readInt();
72: salary = file.readDouble();
73: }
74: catch(IOException e) {
75: err.println("Error reading file");
e.printStackTrace();
76: System.exit(1);
}
77: }
78:
//helper method to write a string with specified length
79: public void writeString(DataOutput out, String s, int len)
80: throws IOException {
81: for(int i = 0; i < len; i++) {
82:
if(i < s.length())
83: out.writeChar(s.charAt(i));
84:
85: else
out.writeChar(0);
86:
87: }
}
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
268
บทท่ี 8: Streams I/O
103: //helper method to read a string intro. to Java (FEU.faa)
public String readString(DataInput in, int len)
104:
105: throws IOException {
String s = "";
106: int i = 0;
107: while(i < len) {
108:
char c = in.readChar();
109: if(c != 0)
110:
111: s += c;
i++;
112: }
113: return s;
}
114:
115: //number of records in file
116: public int size(RandomAccessFile file) throws IOException{
117: int size = 0;
118: try {
119:
size = (int)file.length() / RECORD_SIZE;
120: }
catch(IOException e) {
121:
122: err.println("Error reading file.");
System.exit(1);
123: }
124: return size;
}
125:
126: //display a record to screen
127: public void show() {
128: out.printf("%-10s %-10s %5d %8.2f%n", firstName,
129: lastName, year, salary);
130:
}
131:
132:
133:
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));
0 72 144 216 288 position intro. to Java (FEU.faa)
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 intro. to Java (FEU.faa)
ผูอ า นจะเหน็ วา ผลลพั ธทีไ่ ดจ ากการอานน้ันเปนไปตามขอกาํ หนดท่ีอยใู น array random คอื {2,
3, 0, 1} ซงึ่ ไดเราปรับใหเปน ตวั เลขทีเ่ หมาะสมในการอา น ในตอนตอไปเราจะมาดูกนั ถงึ ขั้นตอน
และวิธกี ารในการปรับปรงุ ขอมูลทอ่ี ยภู ายใน 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 intro. to Java (FEU.faa)
4. จงเขยี นโปรแกรมที่อานขอ มูลจากไฟลทเ่ี ขียนขนึ้ ในขอ 3 เสร็จแลว ใหคาํ นวณหาผลรวม
ของขอ มูลในแตล ะแถว หลงั จากน้ันใหเขียนขอมลู ในแตละแถวรวมทัง้ ผลรวมท่หี าไดลงใน
ไฟลต วั เดมิ (ขอมลู ในแตล ะแถวจะมีจํานวนเทากับ 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
โดยใหu ser เปน ผกู าํ หนดตาํ แหนงของ record ท่ตี อ งการเขียนเอง ใหเขยี น method ท่ี
แสดงขอมูลของ record (ทีก่ าํ หนดมาจาก keyboard) ไปยงั หนาจอ
9. จงปรับปรงุ โปรแกรมในขอ 6 ดวยการเพมิ่ method ในการคํานวณหาผลรวมของขอ มลู ใน
แตละแถว หลงั จากน้ันใหนําขอมลู ท้งั หมดไปเกบ็ ไวในไฟลต ัวใหม โดยใหผ ลรวมทหี่ าได
เปนขอ มูลตัวสุดทายในแถวนนั้
274
ในบทท่ีเกานเี้ ราจะมาดกู นั ถงึ เรอ่ื งของการประมวลผลขอมูลหลาย ๆ ตวั ในเวลาพรอ ม ๆ กนั intro. to Java (FEU.faa)
(ไลเ ลยี่ กัน) โดยไมเกดิ ปญ หาท่ีขดั แยงกันระหวางโปรแกรม (หรือ โปรแกรมยอย) ซ่ึงเรา
สามารถทําไดด ว ยการเรียกใช thread ของ Java
หลงั จากจบบทเรียนนี้แลวผอู านจะไดท ราบถงึ
o ความหมายของ thread
o วงจรชวี ติ ของ thread
o การสรางและใช thread
o การ synchronize threads
o ตวั อยางการใช thread
9.1 Thread คืออะไร
การทาํ งานของส่งิ มชี วี ติ เชน ตัวเราเอง (คน) สามารถทจ่ี ะคดิ ถงึ ส่ิงตาง ๆ หรอื ทาํ งานหลาย ๆ
อยา งในเวลาเดียวกนั ไดไมย ากเยน็ นัก เชน เราสามารถท่ีจะคิดถึงเรื่องในอดีต เร่ืองในอนาคต
พรอม ๆ กับการทํางานบางอยางในเวลาเดียวกันได เราสามารถที่จะปนจักรยาน พรอ มกบั การฟง
เพลง และดดู โอเล้ียงในเวลาเดียวกันได (คงไมท กุ คนท่ีทําได?)
การ download เพลงจาก internet นนั้ ถาเรารอใหการ download เสร็จสิน้ กอนที่เราสามารถจะ
ฟงไดย อมไมใ ชสิง่ ทีพ่ งึ ประสงค คอมพวิ เตอรสามารถที่จะ download เพลงที่เราตอ งการจาก
web site พรอ ม ๆ กบั เลนเพลงใหเ ราฟงขณะท่กี าร download ยงั ดําเนินตอไปจนกวาการ
download จะสิน้ สุดลง
ระบบปฏบิ ตั ิการ (Operating System) เชน Windows XP สามารถทาํ งานไดหลาย ๆ อยา งใน
เวลาเดียวกนั เชน เลน เพลงที่เราตองการ พรอม ๆ กบั ยอมใหเ ราพิมพงาน และทอง web site
หรอื แมกระท่งั run โปรแกรมหลาย ๆ ตวั ในเวลาเดียวกัน
การทาํ งานที่ไดก ลา วมา นั้นเราตอ งอาศัยการทํางานทเี่ รียกวา concurrent programming ซึ่ง
Java มเี คร่อื งมือท่ีเอ้อื อาํ นวยถงึ การทํางานดงั กลา วใหเราไดใช โดยเราตองทาํ ผานกระบวนการ
ของการประมวลผลของ thread (threads of execution) ซง่ึ จะตองทําดว ยการกําหนดของเรา
เอง (programmer) ใหม ี thread หลาย ๆ ตวั ในโปรแกรมของเรา โดยท่แี ตล ะตัวทาํ งานตาม
หนาทีข่ องตวั เอง ซงึ่ อาจเปนการรวมกนั กบั thread ตวั อ่นื ๆ ท่ีอยใู นโปรแกรมนั้น ๆ
Thread มสี ว นประกอบทค่ี ลายกับการทาํ งานของโปรแกรมโดยท่วั ไป กลา วคอื thread จะมี
จุดเริม่ ตนของการประมวลชดุ คาํ สง่ั การประมวลชดุ คําส่ังตามลําดับขั้น และสดุ ทา ยก็คือ การยุติ
การประมวลชดุ คาํ สัง่ หนงั สือหลายเลม ใหค วามหมายของ thread วา เปน lightweight process1
ซ่ึงหมายถึงกระบวนการทีไ่ มต องการใชทรพั ยากรของระบบมากมายนกั ในภาพท่ี 9.1 เรามี
thread อยูส องตวั ที่มชี ุดคาํ สั่งตามลําดบั (แสดงดว ยเครอ่ื งหมายลกู ศร)
1 ดูขอ มลู เพิ่มเติมไดจ าก หนังสือท่เี กีย่ วกบั ระบบปฏบิ ตั กิ าร (Operating System)
เริม่ ตนการเขียนโปรแกรมดว ย Java
โปรแกรมทม่ี ี intro. to Java (FEU.faa)
thread อยู 2 ตัว
Thread 1 Thread 2
ภาพที่ 9.1 โปรแกรมทแ่ี บง การทาํ งานให thread 2 ตวั
ในมมุ มองของการเขียนโปรแกรมแบบ concurrent programming น้ัน thread ก็อาจเรยี กไดวา
เปน virtual CPU กลา วคอื มีสวนประกอบที่สําคัญสําหรบั การประมวลผลอยูสองสวนทเ่ี รยี กวา
1). Program code และ 2). Data ดงั นัน้ ถา เรามองการประมวลผลของ thread ใด ๆ แลว เราก็
สามารถแบง องคประกอบของ thread ออกไดเปนสามสว นคอื
1. A virtual CPU
2. code ที่ CPU ทาํ การประมวลผลอยู
3. data ที่ code ใชใ นการประมวลผล
CPU
CODE DATA
ภาพท่ี 9.2 สว นประกอบของ Thread
การเขียนโปรแกรมดว ยการใช thread ตองมคี วามระมดั ระวังเปนอยางสงู ทั้งน้ีกเ็ พราะวา thread
เปน กระบวนการทต่ี อ งการการดูแลเปน พเิ ศษ เพราะฉะน้นั Java จงึ ไดสราง class Timer และ
TimerTask ไวใ หเ ราใชแ ทนการใช thread (โดยตรง) ในงานบางชนิ้ ทม่ี ีการทาํ งานหลาย ๆ
อยางในเวลาเดียวกัน เชน โปรแกรมการหา compound interests ในบทท่ีสาม แตง านหลาย ๆ
อยางกห็ ลกี เลยี งการใช thread ไมไ ด Java กําหนดใหมวี ธิ กี ารอยสู องวิธใี นการใช thread นั่นก็
คือ 1). การสราง class ทมี่ ีการถา ยทอดจาก class Thread โดยตรง และ 2).การเรียกใช
interface Runnable
9.2 การสราง Thread เบ้อื งตน จาก class Thread
ณ เวลาหนึง่ ๆ thread จะอยใู นสถานะทต่ี าง ๆ กันขึ้นอยูกบั สภาพในขณะนั้นของ thread (life
cycle of a Thread) ซ่งึ มอี ยทู ัง้ หมด 4 สถานะดังนี้
1. new หรอื start
2. executing หรอื runnable
3. waiting หรอื blocked
276
บทท่ี 9: Thread
4. terminating หรอื dead intro. to Java (FEU.faa)
เพอ่ื ใหเหน็ ภาพชดั เจนถงึ สถานะของ thread ดภู าพ 9.2 ประกอบ
new
โปรแกรมเร่ิมการทาํ งาน
ของ thread
waiting Thread ตองรอ executing Thread สิ้นสุด terminating
บางอยางใหเ กิดข้ัน การทํางาน
การรอของ Thread สิ้นสุดลง
ภาพที่ 9.2 วงจรชวี ติ ของ Thread
Thread เร่มิ วงจรชวี ิตจากสถานะ new ซง่ึ มาจากการสราง thread ครงั้ แรก และจะเขาสสู ถานะ
executing เม่อื โปรแกรมสัง่ ให thread ทาํ งาน ในบางครั้ง thread อาจจะตองเขาสูส ถานะ
waiting เพ่ือรอให thread ตัวอนื่ ทาํ งานกอ น และเมอื่ ไดรับการสื่อสาร (signal) จาก thread ตัว
อน่ื ใหกลบั มาทาํ งานไดเทา น้นั thread ตวั น้ีจึงจะสามารถกลับเขาสูส ถานะ executing ได ซ่ึง
กระบวนการนอี้ าจเกดิ ขน้ึ หลาย ๆ ครัง้ ในชว งชวี ติ หน่งึ ๆ ของ thread และเมือ่ ส้นิ สดุ การทํางาน
แลว thread ก็จะเขา สสู ถานะ terminating ซึ่งหมายถงึ การจบการทํางานอยางปกติ หรือไมก ถ็ กู
บงั คบั ใหย ตุ ิการทาํ งานนัน้ ๆ ของ thread ก็ได เชน ระบบปฏิบตั ิการ (Operating System) ดงึ
เอา thread ออกจาก CPU เพื่อใหระบบ หรอื thread ตัวอนื่ ไดใชเวลาใน CPU แทน เปนตน
โปรแกรมตัวอยางตอไปน้เี ปน ตัวอยา งการสรา ง thread อยา งงา ย ๆ สามตวั ซง่ึ ทั้งสามตัวก็ไมท าํ
อะไรมาก แคเกิดมาแลว ก็นอนหลับสกั พักหนง่ึ เสร็จแลวกอ็ อกจากระบบ
1: /**
2: Simple Thread demonstration
3: */
4:
5: import java.util.Random;
6: import static java.lang.System.out;
7:
8: class Thread1Demo extends Thread {
9: private int count = 5;
10: private int sleepTime;
11: private static int threadCount = 0;
12: private static Random time;
13:
14: Thread1Demo(String name) {
15: super(name + ++threadCount);
16: time = new Random();
17: start();
18: }
19:
20: public void run() {
21: while(true) {
22: sleepTime = time.nextInt(1000);
23: out.printf("I'm %s, going to sleep for %d milliseconds%n",
getName(), sleepTime);
24: //maximum sleep time 1 second
25: try {
26: sleep(sleepTime);
27: }
28: catch(InterruptedException e) {
29: //do nothing
30: }
31: out.printf("%s done sleeping%n", getName());
277
เริม่ ตน การเขยี นโปรแกรมดวย Java
32: if(--count == 0) break; intro. to Java (FEU.faa)
}
33: out.printf("%s FINISHED.%n", getName().toUpperCase());
34: }
35: public static void main(String[] args) {
36: //creating 3 threads
37: for(int i = 0; i < 3; i++) {
Thread1Demo thread = new Thread1Demo("Thread#");
38: }
39: out.println("Done executing in main.");
40:
}
41:
42:
43:
44: }
โปรแกรม Thread1Demo.java สราง thread ดวยการ extends class Thread หลงั จากนน้ั กท็ าํ
การ override method run() ท่ีมีอยูใน class Thread เพอ่ื ทาํ การสงช่ือของ thread ออกไปยงั
หนา จอ
constructor ในโปรแกรมของเรา เปน จุดเรมิ่ ตนของการสรา งและเรม่ิ การทํางานของ thread เรา
สง ช่ือของ thread ตวั นไี้ ปให constructor ของ class Thread ดวยประโยค
super(name + ++threadCount);
ซ่งึ จะเอาคา ของ string name บวกกบั คา ของ threadCount เพราะฉะนนั้ ถาเราดใู น main() เรา
จะเห็นวาเราไดสราง thread สามตัวคือ Thread#1, Thread#2, และ Thread#3 (บรรทัดท่ี
40) หลังจากนน้ั เรากเ็ ริม่ การทํางานของ thread ทั้งสามตัวน้ี (ตามลาํ ดบั การสราง) ดว ยคําสงั่
start();
เราอาจมองไดว าการทาํ งานทเ่ี กดิ ข้ึน คอื Thread#1.start(), Thread#2.start(), และ
Thread#3.start() ถูกเรียกตามลาํ ดับ และเมอ่ื start() ถูกเรียก run() ก็จะถกู เรียกโดยอัตโนมตั ิ
เชนกัน ดงั ทีแ่ สดงในภาพที่ 9.3
Thread#1 Thread#2 Thread#3
start() start() start()
run() run() run()
Run จนกวา Run จนกวา Run จนกวา
count จะเทากับ count จะเทากบั count จะเทา กับ
0 ซ่ึงอาจเปน เวลา 0 ซ่งึ อาจเปน เวลา 0 ซึ่งอาจเปน เวลา
เทา ใดก็ได ข้นึ อยู เทา ใดก็ได ขึน้ อยู เทา ใดกไ็ ด ขึน้ อยู
กบั กบั กบั
nextInt(1000) nextInt(1000) nextInt(1000)
ภาพท่ี 9.3 การทาํ งานพรอ ม ๆ กันของ Thread 3 ตวั
278
บทท่ี 9: Thread intro. to Java (FEU.faa)
เพ่ือใหเ ห็นการทาํ งานของ thread ท้ังสามตัวชดั ข้นึ (กอนทีเ่ ราจะอธิบายถึงส่ิงทเ่ี กิดขน้ึ ) เราจึง
แสดงผลลัพธของการ run โปรแกรมใหดู
I'm Thread#1, going to sleep for 884 milliseconds
Done executing in main.
I'm Thread#2, going to sleep for 91 milliseconds
I'm Thread#3, going to sleep for 66 milliseconds
Thread#3 done sleeping
I'm Thread#3, going to sleep for 532 milliseconds
Thread#2 done sleeping
I'm Thread#2, going to sleep for 80 milliseconds
Thread#2 done sleeping
I'm Thread#2, going to sleep for 968 milliseconds
Thread#3 done sleeping
I'm Thread#3, going to sleep for 669 milliseconds
Thread#1 done sleeping
I'm Thread#1, going to sleep for 947 milliseconds
Thread#2 done sleeping
I'm Thread#2, going to sleep for 785 milliseconds
Thread#3 done sleeping
I'm Thread#3, going to sleep for 742 milliseconds
Thread#1 done sleeping
I'm Thread#1, going to sleep for 614 milliseconds
Thread#2 done sleeping
I'm Thread#2, going to sleep for 256 milliseconds
Thread#3 done sleeping
I'm Thread#3, going to sleep for 329 milliseconds
Thread#2 done sleeping
THREAD#2 FINISHED.
Thread#3 done sleeping
THREAD#3 FINISHED.
Thread#1 done sleeping
I'm Thread#1, going to sleep for 71 milliseconds
Thread#1 done sleeping
I'm Thread#1, going to sleep for 230 milliseconds
Thread#1 done sleeping
THREAD#1 FINISHED.
ภายใน method run() เรากาํ หนดใหม กี ารแสดงช่อื ของ thread (บรรทดั ท่ี 23) พรอมกับแสดง
จํานวนเวลาที่ thread ตองรอ หลังจากนั้นเราก็บังคบั ให thread พักการทาํ งานโดยเขาสสู ถานะ
ของการรอ ดว ยการเรยี กใช sleep() ดว ยคา ที่มาจากการสุมในบรรทดั ท่ี 22 ระหวา งการรอนี้
thread ตวั อืน่ กจ็ ะไดรบั โอกาสใหทาํ การประมวลผล และหลงั จากท่ีการรอสน้ิ สดุ ลง thread ก็จะ
กลับเขา สสู ถานะ executing อีกครง้ั หนงึ่ ซึ่ง thread แตล ะตัวจะเวียนวา ยอยูใ นวงจรนี้จนกวาคา
ของ count จะเปนศนู ย (บรรทัดท่ี 32) เพราะฉะนนั้ ผลลัพธข องการ run ในแตละคร้งั ของ
โปรแกรม Thread1Demo.java ก็จะออกมาไมเ หมือนกนั ขน้ึ อยูก บั เวลาทีใ่ ชไ ปในการรอ
(sleep) thread ทใ่ี ชเวลานอ ยในการนอนหลับก็จะต่นื กอน thread ท่ีใชเ วลามาก
ผอู านจะสงั เกตเห็นวาประโยคการแสดงผลในบรรทัดท่ี 42 ไดรับการประมวลผลกอนท่ี thread
ทั้งสามตวั จะส้ินสดุ การทํางานของตัวเอง ทง้ั นีก้ ็เนอื่ งจากวาการประมวลผลของ main() ก็เปน
การเรียกใช thread ของ Java Virtual Machine เหมอื นกัน (เชนเดยี วกันเราก็อาจมองไดวา เปน
main.start()) แต main() ก็ยงั ไมส ามารถที่จะยตุ ิการทาํ งานไดจ นกวาการทํางานของ thread
ท้ังสามตวั จะเสรจ็ สนิ้ ลง
9.4 การสราง Thread เบ้อื งตน จาก Interface Runnable
โปรแกรมตัวอยางกอ นหนา นใี้ ชการถา ยทอดคณุ สมบตั จิ าก class Thread เปน ตวั สรา งและใช
thread แต Java ยงั มีวธิ ีการท่ีเราสามารถใชไ ดอกี วิธีหนง่ึ น่ันก็คือ การใช Interface Runnable
ในการใช Interface Runnable นน้ั เราไมจ าํ เปน ทจ่ี ะตองเรียกใช start() เหมอื นท่ีเราทาํ ตอนใช
class Thread เราเพียงแตเรยี กใช run() เพียงตัวเดยี วเทา นน้ั ในการประมวลผลทีเ่ ราตอ งการ
แตส ิ่งสําคัญทเ่ี ราตอ งทาํ ก็คอื เรยี กใช Interface Executor ทง้ั นี้กเ็ นอื่ งจากวา Runnable จะ
ไดรบั การประมวลผลจาก object ทเี่ กดิ มาจาก class ทม่ี ีการถายทอดจาก Interface Executor
ผานทาง method execute()
279
เร่มิ ตน การเขยี นโปรแกรมดวย Java intro. to Java (FEU.faa)
โปรแกรม Thread2Demo.java ท่เี ราเขยี นขึ้นสราง thread 3 ตัวเหมือนเดมิ แตเราใช Interface
Runnable และเราเลอื กที่จะใช Interface ExecutorService ซึง่ เปน sub-interface ของ
Executor ในการจดั การกับ thread ทีม่ อี ยทู ง้ั สามตัว (เรยี กวา thread pool) แตไ มไ ด
หมายความวา เราจะตอ งใช ExceuterService เทา น้ันเรายงั สามารถท่ีจะใชการเรียกใช start()
ไดเ หมอื นเดมิ
และส่งิ สาํ คญั อกี อยางหนง่ึ ในการเรียกใช sleep() กค็ อื เราตองกาํ หนดใหเ รียกโดยตรงผา นทาง
class Thread เชน ในบรรทดั ท่ี 28 เราเรียกใช Thread.sleep(sleepTime)
1: /**
2: Simple Thread demonstration
3: */
4:
5: import java.util.Random;
6: import java.util.concurrent.Executors;
7: import java.util.concurrent.ExecutorService;
8: import static java.lang.System.out;
9:
10: class Thread2Demo implements Runnable {
11: private int count = 5;
12: private int sleepTime;
13: private static int threadCount = 0;
14: private String name;
15: private static Random time;
16:
17: Thread2Demo(String name) {
18: this.name = name + ++threadCount;
19: time = new Random();
20: }
21:
22: public void run() {
23: while(true) {
24: sleepTime = time.nextInt(1000);
25: out.printf("I'm %s, going to sleep for %d milliseconds%n",
this.name, sleepTime);
26: //maximum sleep time 1 second
27: try {
28: Thread.sleep(sleepTime);
29: }
30: catch(InterruptedException e) {
31: //do nothing
32: }
33: out.printf("%s done sleeping%n", this.name);
34: if(--count == 0) break;
35: }
36: out.printf("%s FINISHED.%n", this.name.toUpperCase());
37: }
38:
39: public static void main(String[] args) {
40: //creating 3 threads
41: Thread2Demo []thread = new Thread2Demo[3];
42: for(int i = 0; i < 3; i++) {
43: thread[i] = new Thread2Demo("Thread#");
44: }
45: ExecutorService exec = Executors.newFixedThreadPool(3);
46: exec.execute(thread[0]);
47: exec.execute(thread[1]);
48: exec.execute(thread[2]);
49:
50: exec.shutdown();
51:
52: out.println("Done executing in main.");
53: }
54: }
ใน method main() เราใช array เปนตวั เกบ็ thread ทัง้ สามตัวเพอ่ื ใหงายตอการสง ไปให
method execute() ทีอ่ ยูใ น class ExecutorService
280
บทท่ี 9: Thread intro. to Java (FEU.faa)
Interface Executors มี static method ช่ือ newFixedThreadPool() ที่เปน ตัวสราง thread
pool ทป่ี ระกอบไปดวย thread ตามจํานวนท่กี ําหนดให (ในที่นี้สามตวั ดบู รรทดั ท่ี 45) ซงึ่
thread เหลานีจ้ ะถูกใชโดยตัวแปร exec ในการ run (บรรทัดท่ี 46 – 48) ถา หากวา method
executor() ถูกเรียกและ thread ที่มีอยใู น ExecutorService ทกุ ตัวกาํ ลงั ทาํ งานอยู Runnable
กจ็ ะถูกเกบ็ ไวใ น queue พรอมท่จี ะให thread ตวั อืน่ ทเ่ี สร็จงานแลว มาใชตอ ไป เพ่ือใหเห็นภาพ
ชัดข้ึน เราจะอธิบายการทาํ งานของโปรแกรม Thread2Demo.java ตามขั้นตอนตอไปน้ี
1. โปรแกรมสราง Runnable object สามตวั คือ Thread#1, Thread#2, และ
Thrdead#3 เกบ็ ไวใน array ชอ่ื thread (บรรทดั ท่ี 41 – 43)
2. โปรแกรมสรา ง thread pool จากคําสัง่ ในบรรทดั ท่ี 47 โดยกาํ หนดใหมี thread สูงสดุ
ได 3 ตัว
3. โปรแกรมเรียก method execute() ใน Interface ExecutorService ดว ย Runnable
object ทม่ี อี ยใู น array thread
method execute() จะทาํ งานอยูสองอยางคอื
3.1 สราง thread ภายใน ExecutorService เพือ่ รองรับการประมวลผลของ
Runnable object ทถี่ ูกสงไปให
3.2 เปล่ียนสถานะของ Runnable object จาก new เปน runnable (หรือทีเ่ รา
เรียกวา executing)
4. โปรแกรมเรียกใช method shutdown() เพอ่ื ยตุ กิ ารทาํ งานของ thread แตละตัวทม่ี ีอยู
ใน ExecutorService หลังจากท่ี thread สิ้นสุดการทาํ งานของ runnable object นนั้ ๆ
[ทาํ ไมถงึ ใช Thread pool]
การสราง thread เปน กระบวนการท่ีมีคา ใชจา ยสงู ทั้งน้ีกเ็ พราะวา ตอ งติดตอกับระบบปฏบิ ัติการ
อยูอยา งสม่ําเสมอ ดังน้ันถาเรามจี าํ นวนของ thread อยใู นระบบเยอะเรากค็ วรทจ่ี ะใช thread
pool เปน ตัวชว ย
Thread pool ประกอบไปดวย idle thread ท่ีพรอ มที่จะทาํ งานทนั ที (ready state) เมื่อเรา
กําหนดให thread pool ท่สี รางขน้ึ เปน Runnable หนึง่ ในจํานวน thread ทม่ี ีอยูเรียก method
run() และเมื่อเสร็จงาน thread ไมตายแตยังคงอยเู พ่ือคอยใหบริการตอการรอ งขอครง้ั ใหม
โปรแกรมตวั อยา งกอนหนานีเ้ รียกใช newFixedThreadPool ซ่งึ กาํ หนดใหม ี idle thread อยู
ตลอดเวลา แต Java ยังมี thread pool ตวั อน่ื ๆ ใหเ ลอื กใชดงั น้ี
• newCachedThreadPool สราง thread ตามจาํ นวนที่กําหนดและ thread เหลา นจ้ี ะมี
ชวี ิตอยูแค 60 วนิ าที
• newSingleThreadExecuter เปน pool ทมี่ ี thread อยตู วั เดยี วทีส่ งงานตามลําดบั
(sequential)
• newScheduledThreadPool เปน thread pool ท่ีตายตวั สาํ หรับการกําหนดการทํางาน
(scheduled execution)
• newSingleThreadScheduledExecutor เปน pool ที่มี thread ตัวเดียวสาํ หรบั การ
กาํ หนดการทาํ งาน
ถาไมใ ช thread pool โปรแกรมตวั อยา งของเรากต็ องเขียนอกี แบบหน่ึงดังนี้ (ภายใน main())
Thread2Demo []thread = new Thread2Demo[3];
for(int i = 0; i < 3; i++) {
thread[i] = new Thread2Demo("Thread#");
}
new Thread(thread[0]).start();
new Thread(thread[1]).start();
new Thread(thread[2]).start();
ผลลพั ธจากการ run โปรแกรม Thread2Demo.java กค็ ลาย ๆ กับทีเ่ ราไดจากโปรแกรม
Thread1Demo.java
281
เร่มิ ตน การเขยี นโปรแกรมดว ย Java intro. to Java (FEU.faa)
9.5 Priority ของ Thread
Thread ท่ีถกู สรางขน้ึ หรือเรียกใชง านจะมี priority ทไ่ี ดก ําหนดไว โดยคา ของ priority ที่ Java
มใี ห thread แตล ะตวั จะมีคา อยูระหวาง 1 (MIN_PRIORITY) ถึง 10 (MAX_PRIORITY)
thread ท่ีมี priority สูงจะไดรับเวลาในการประมวลผลใน CPU กอ น thread ที่มี priority นอ ย
กวา แตลําดับของการประมวลผลของ thread ไมมีสวนเกีย่ วของกบั priority แตอ ยางใด โดย
ปกติแลว thread เม่ือถกู สรา งทุกตัวจะไดรบั คา priority เทากับ 5 (NORM_PRIORITY) และจะ
ไดรับการถายทอดคณุ สมบัตจิ าก thread ทเ่ี ปน ผสู รา ง thread น้ี
9.5.1 กาํ หนด และตรวจสอบ priority ของ Thread
โปรแกรม ThreadPriority.java ที่เหน็ นเี้ ราดัดแปลงมาจากโปรแกรม Thread1Demo.java โดย
มกี ารเรยี กใช setPriority() และ getPriority() เพอ่ื กําหนดและแสดง priority ของ thread ท่ี
สรา งขน้ึ
1: /**
2: Thread priority
3: */
4:
5: import java.util.Random;
6: import java.util.concurrent.Executors;
7: import java.util.concurrent.ExecutorService;
8: import static java.lang.System.out;
9:
10: class ThreadPriority extends Thread {
11: private int count = 5;
12: private int sleepTime;
13: private static int threadCount = 0;
14: private String name;
15: private static Random time;
16:
17: ThreadPriority(String name, int priority) {
18: this.name = name + ++threadCount;
19: setPriority(priority);
20: time = new Random();
21: }
22:
23: public void run() {
24: while(true) {
25: sleepTime = time.nextInt(1000);
26: out.printf("I'm %s, with a priority of %d%n",
this.name, getPriority());
27: //maximum sleep time 1 second
28: try {
29: Thread.sleep(sleepTime);
30: }
31: catch(InterruptedException e) {
32: //do nothing
33: }
34: out.printf("%s done sleeping%n", this.name);
35: if(--count == 0) break;
36: }
37: out.printf("%s FINISHED.%n", this.name.toUpperCase());
38: }
39:
40: public static void main(String[] args) {
41: //creating 3 threads with different priority
42: ThreadPriority []thread = new ThreadPriority[3];
43: for(int i = 0; i < 3; i++) {
44: thread[i] = new ThreadPriority("Thread#", i+3);
45: }
46:
47: //using Executor to manage threads
48: ExecutorService exec = Executors.newFixedThreadPool(3);
49:
50: //start threads
51: exec.execute(thread[0]);
52: exec.execute(thread[1]);
53: exec.execute(thread[2]);
282
บทที่ 9: Thread intro. to Java (FEU.faa)
54:
55: exec.shutdown(); //terminate threads
56: }
57: }
Code ในบรรทัดท่ี 19 เปน การกําหนด priority ของ thread สว น code ในบรรทัดท่ี 26 เปนการ
เรียกดู priority ของ thread ขอ ควรระวงั ในการใช setPriority() ตอ งไมก าํ หนดให priority มคี า
สูงเกิน MAX_PRIORITY หรอื ตํ่ากวา MIN_PRIORITY ผลลัพธท เ่ี ราไดจากการ run คอื
I'm Thread#1, with a priority of 3
I'm Thread#2, with a priority of 4
I'm Thread#3, with a priority of 5
Thread#2 done sleeping
I'm Thread#2, with a priority of 4
Thread#1 done sleeping
I'm Thread#1, with a priority of 3
Thread#3 done sleeping
I'm Thread#3, with a priority of 5
Thread#3 done sleeping
I'm Thread#3, with a priority of 5
Thread#3 done sleeping
I'm Thread#3, with a priority of 5
Thread#2 done sleeping
I'm Thread#2, with a priority of 4
Thread#1 done sleeping
I'm Thread#1, with a priority of 3
Thread#2 done sleeping
I'm Thread#2, with a priority of 4
Thread#3 done sleeping
I'm Thread#3, with a priority of 5
Thread#1 done sleeping
I'm Thread#1, with a priority of 3
Thread#2 done sleeping
I'm Thread#2, with a priority of 4
Thread#3 done sleeping
THREAD#3 FINISHED.
Thread#1 done sleeping
I'm Thread#1, with a priority of 3
Thread#2 done sleeping
THREAD#2 FINISHED.
Thread#1 done sleeping
THREAD#1 FINISHED.
9.6 Thread Synchronization (การประสานเวลาการทํางาน)
ในการทํางานของ thread หลาย ๆ ตวั ที่มกี ารใชท รพั ยากรรว มกัน เชน คอมพวิ เตอรในระบบ
เครอื ขายใช printer รว มกนั โปรแกรมท่เี กีย่ วกบั งานระบบบัญชี สินคา คงคลัง หรือการปรับปรงุ
(update) ขอมลู ที่อยใู นบญั ชีธนาคาร หาก thread ตัวใดตวั หนึ่งกาํ ลงั ปรับปรุงขอมูลอยู และ
thread อกี ตัวกพ็ ยายามทีจ่ ะปรับปรุงขอมูล ผลลพั ธท อ่ี อกมาอาจไมสะทอนถงึ ขอมูลทถี่ กู ตองได
ดังนัน้ การแกไขก็ตอ งกาํ หนดให thread ตัวใดตวั หนึ่งมีสทิ ธใิ นการปรับปรุงขอมลู อยา งสงู สดุ นน่ั
ก็คอื thread ตวั อ่นื ไมส ามารถท่ีจะปรบั ปรงุ ขอ มูลชุดเดยี วกนั ไดจนกวา thread ตัวแรกจะ
ปรบั ปรงุ เสร็จ วิธีการนีจ้ ะทาํ ใหขอมลู ทีไ่ ดรับการปรบั ปรงุ เปนขอ มูลทถ่ี ูกตอ งและเช่อื ถือได2
กอนทเ่ี ราจะไปดูถงึ วธิ ีการของการทํางานรวมกันของ thread เราจะทดลองเขยี นโปรแกรม
สาํ หรับการนําเงนิ เขาและออกจากบัญชี โดยไมมีการใชเครื่องมือของการประสานเวลาชวย เรา
ไดอ อกแบบให UnSyncAccount.java เปน โปรแกรมสาํ หรบั การนาํ เงนิ เขา (deposit) และการ
ถอนเงนิ ออก (withdraw) จากบัญชที ่ใี ชรวมกันระหวา ง thread 4 ตัว ไฟล Deposit.java เปน
กระบวนงานที่เรียกใช method deposit() ท่ีมีอยูใน UnSyncAccount สวนไฟล Withdraw.java
เปน กระบวนงานที่เรยี กใช method withdraw() ของ UnSyncAccount
ไฟล UnSyncAccountTest.java เปนไฟลห ลกั ท่เี ราเขยี นเพ่ือทดสอบการทํางานรว มกนั ของ
thread ซ่ึงมีหนา ตาดังน้ี
2 หนังสือหลาย ๆ เลมเรียกกระบวนการน้วี า Mutual Exclusion
283
เรมิ่ ตน การเขยี นโปรแกรมดว ย Java intro. to Java (FEU.faa)
1: /**
2: unsynchronized bank account
3: */
4:
5: import static java.lang.System.out;
6:
7: class UnSyncAccountTest {
8: public static void main(String[] args) {
9: //print header
10: out.printf("%15s%15s%15s%n", "Deposit", "Withdrawal", "Balance");
11:
12: //set up shared account with initial balance
13: UnSyncAccount sharedAccount = new UnSyncAccount(0);
14:
15: Deposit d1 = new Deposit(sharedAccount, 100.0);
16: Withdraw w1 = new Withdraw(sharedAccount, 100.0);
17: Deposit d2 = new Deposit(sharedAccount, 100.0);
18: Withdraw w2 = new Withdraw(sharedAccount, 100.0);
19:
20: //perform deposit and withdrawal simultaneously
21: new Thread(d1).start();
22: new Thread(w1).start();
23: new Thread(d2).start();
24: new Thread(w2).start();
25: }
26: }
ในโปรแกรม UnSyncAccountTest.java เรากําหนดให sharedAccount เปน บัญชีทีม่ ีผูใช
รว มกันอยสู ค่ี น คือ d1, d2, w1, และ w2 โดยสองคนแรกเปน การนําเงนิ เขาบัญชี สวนสองคนท่ี
เหลอื เปนการถอนเงนิ ออกจากบญั ชี ซึ่งถา ดตู ามการกาํ หนดแลว (บรรทัดที่ 15 – 18) เมือ่
โปรแกรมยุติการทาํ งานจาํ นวนเงินท่มี ีอยใู นบัญชคี วรจะมคี าเปน ศนู ย (จากการกาํ หนดในบรรทัด
ที่ 13)
ในการนาํ เงนิ เขา และออกจากบัญชีจะถูกทําใน method deposit() และ withdraw() ทอี่ ยูใน
ไฟล UnSyncAccount.java ซึง่ มหี นาตาดงั น้ี
1: /**
2: unsynchronized bank account
3: */
4:
5: import static java.lang.System.out;
6:
7: class UnSyncAccount {
8: //a shared balance
9: private double balance;
10:
11: //setup initial balance
12: public UnSyncAccount(double balance) {
13: this.balance = balance;
14: out.printf("%15c%15c%,15.2f%n", ' ', ' ', balance);
15: }
16:
17: //return a balance
18: public double getBalance() {
19: return balance;
20: }
21:
22: //deposit amount into account
23: public void deposit(double amount) {
24: out.printf("%s deposits %.2f%n",
25: Thread.currentThread(), amount);
26: balance += amount;
27: out.printf("%,15.2f%15c%,15.2f%n", amount,
28: ' ', getBalance());
29: }
30:
31: //withdraw some amount
32: public void withdraw(double amount) {
33: //do nothing when amount is not enough
34: if(amount < balance)
284
บทที่ 9: Thread intro. to Java (FEU.faa)
35: return;
36: out.printf("%s withdraws %.2f%n",
37: Thread.currentThread(), amount);
38: balance -= amount;
39: out.printf("%15c%,15.2f%,15.2f%n",
40: ' ', amount, getBalance());
41: }
42: }
เรากําหนดใหก ารทาํ งานกับบญั ชตี ัวนีใ้ นไฟล Deposit.java และ Withdraw.java ซงึ่ มี code
สําหรบั การทํางานดังน้ี
1: /**
2: Program to deposit into a shared account
3: */
4:
5: import static java.lang.System.out;
6: import java.util.Random;
7:
8: class Deposit implements Runnable {
9: //shared saving account
10: private UnSyncAccount saving;
11: //amount to deposit
12: private double amount;
13: //waiting time
14: private Random sleepTime = new Random();
15:
16: //set the shared account
17: public Deposit(UnSyncAccount saving, double amount) {
18: this.saving = saving;
19: this.amount = amount;
20: }
21:
22: //deposit amount 5 times
23: public void run() {
24: try {
25: for(int i = 0; i < 5; i++) {
26: saving.deposit(amount);
27: Thread.sleep(sleepTime.nextInt(2000));
28: }
29: }
30: catch(InterruptedException ie) {
31: ie.printStackTrace();
32: }
33: }
34: }
1: /**
2: Program to withdraw from a shared account
3: */
4:
5: import static java.lang.System.out;
6: import java.util.Random;
7:
8: class Withdraw implements Runnable {
9: //shared account
10: private UnSyncAccount saving;
11: //amount to withdraw
12: private double amount;
13: //busy time
14: private Random sleepTime = new Random();
15:
16: //set a shared account
17: public Withdraw(UnSyncAccount saving, double amount) {
18: this.saving = saving;
19: this.amount = amount;
20: }
21:
22: //withdraw amount five times
23: public void run() {
24: try {
25: for(int i = 0; i < 5; i++) {
285
เรม่ิ ตนการเขียนโปรแกรมดวย Java
26: saving.withdraw(amount); intro. to Java (FEU.faa)
27: Thread.sleep(sleepTime.nextInt(2000));
28: }
29: }
30: catch(InterruptedException ie) {
31: ie.printStackTrace();
32: }
33: }
34: }
Method หลกั ในการทาํ งานก็คือ run() โดยเราจะใหม ีการนาํ เงนิ เขาและออกเปน จาํ นวนเทา กนั
คือ 5 ครัง้ และจํานวนเงินจะถกู กาํ หนดมาจากโปรแกรม UnSyncAccountTest.java ซึ่งในการ
ทดสอบของเราเราใหจาํ นวนเงินเขา และออกเทา กนั คอื 100 เมื่อเรา run ดูผลลัพธที่เราไดคอื
Deposit Withdrawal Balance
0.00
Thread[Thread-0,5,main] deposits 100.00
100.00 100.00
Thread[Thread-1,5,main] withdraws 100.00
100.00 0.00
Thread[Thread-2,5,main] deposits 100.00
100.00 100.00
Thread[Thread-3,5,main] withdraws 100.00
100.00 0.00
Thread[Thread-1,5,main] withdraws 100.00
100.00 -100.00
Thread[Thread-2,5,main] deposits 100.00
100.00 0.00
Thread[Thread-3,5,main] withdraws 100.00
100.00 -100.00
Thread[Thread-0,5,main] deposits 100.00
100.00 0.00
Thread[Thread-0,5,main] deposits 100.00
100.00 100.00
Thread[Thread-0,5,main] deposits 100.00
100.00 200.00
Thread[Thread-2,5,main] deposits 100.00
100.00 300.00
Thread[Thread-0,5,main] deposits 100.00
100.00 400.00
Thread[Thread-2,5,main] deposits 100.00
100.00 500.00
Thread[Thread-2,5,main] deposits 100.00
100.00 600.00
ถาผูอ า น run โปรแกรมตัวน้ดี อู าจตองทําหลาย ๆ ครัง้ ถงึ จะสงั เกตเห็น error ทเ่ี กดิ ขนึ้ บางครั้งก็
เกดิ ข้นึ ทันที แตบางครัง้ กต็ องใชเวลานานในการ run ถงึ จะเหน็ error ดังกลาว ผอู า นอยา ลมื วา
thread ทั้งสต่ี วั ทาํ งานในเวลาที่ไลเล่ยี กัน (concurrent programming) กับทรัพยากรตัว
เดยี วกนั ดังนั้นถา ทั้งสีต่ วั ตางทํางานโดยไมม กี ารประสานกัน ผลลพั ธท ีไ่ ดก อ็ าจไมตรงกบั ความ
เปน จริง
ในโปรแกรมตัวอยางทแี่ สดงไวกอ นหนาน้ี เราไดก าํ หนดใหม ีการนาํ เงินเขาสองครงั้ และออกสอง
ครงั้ ในจํานวนที่เทากัน สิง่ ท่ีเราคาดหวังทจี่ ะเห็นก็คือ เม่อื โปรแกรมยุตกิ ารทํางานเงินในบญั ชี
จะตอ งมคี า เทา กับตอนเรมิ่ ตน (ศนู ย) แตป ระโยคที่วา
balance += amount;
ซ่งึ อาจตีความเปนลาํ ดับขนั้ การทาํ งาน (ระดับลา ง) ดังน้ี
1. load balance เขา สู register
2. บวก amount
3. ยา ยผลลพั ธที่ไดจ ากการบวกเขาสู balance
ทีนลี้ องสมมติวา thread ตวั หน่ึงประมวลผลข้นั ตอนท่ี 1 และ 2 แลวถกู interrupt และสมมติ
ตอ ไปวา thread ตัวทสี่ องถูกปลุกใหต นื่ พรอมกบั ทําการประมวลผลท่ีเดยี วกันกบั thread ตวั ท่ี
286
บทท่ี 9: Thread intro. to Java (FEU.faa)
หน่ึง และ ณ เวลานีเ้ อง thread ตวั ทห่ี น่ึงถกู ปลกุ ใหต่ืนขึ้นมาประมวลผลชุดคาํ สง่ั ท่ีสาม ซึง่ การ
กระทําดังกลาวทําใหคา ของ balance ไมถ ูกตอ ง (ดูภาพที่ 9.4 ประกอบ)
Thread 1 register Thread 2 register balance
load 1000 1000
Thread 2 sleeps
add 1500 1000
Thread 1 sleeps load 1000 1000
add 2000 1000
store 2000 2000 Thread 2 sleeps
store 1500 1500
ภาพท่ี 9.4 การเขา หาทรพั ยากรรว มของ thread 2 ตวั
ส่ิงทเี่ กิดข้ึนกับโปรแกรมของเรา มีชอ่ื เรยี กวา Race condition ซงึ่ เปนปรากฏการณท เี่ กิดข้นึ
ถาเราไมก าํ หนดใหมกี ารประสานเวลาของ thread เมอ่ื มีการใชท รพั ยากรรว มกนั ในขณะใด
ขณะหนง่ึ เม่อื มกี ระบวนการใด ๆ ทาํ อะไรสักอยา งหนงึ่ กบั ทรพั ยากรทีใ่ ชรว มกันอยู กระบวนการ
อ่นื จะตอ งรอใหก ระบวนการดงั กลา วเสรจ็ สน้ิ การทํางานกบั ทรัพยากรนน้ั กอน จึงจะมีสิทธท จี่ ะ
ทํางานกบั ทรัพยากรนัน้ ได
Java มีเครื่องมือทีช่ วยใหก ารทํางานทีต่ อ งมีการประสานเวลากนั เปน ไปได อยู 2 ทางคือ
1. ใช locks
2. ใช synchronized
9.6.1 การใช locks ในการประสานเวลา
เม่อื thread ตัวใดตัวหนง่ึ ตอ งการท่ีจะกระทาํ กระบวนการอยางใดอยา งหนึง่ กบั ทรพั ยากรที่ใช
รว มกัน thread ตวั น้กี จ็ ะทําการ lock ทรัพยากรน้เี พ่ือตัวเอง thread ตวั อ่ืนกไ็ มส ามารถทาํ อะไร
กบั ทรัพยากรน้ีได จนกวา thread ตวั แรกจะปลอ ยทรัพยากร (unlock) ใหกบั ระบบ ถาหากมี
thread หลาย ๆ ตัวพยายามทจี่ ะทําการ lock ทรพั ยากรนีเ้ พื่อตัวเอง จะมีเพียง thread ตวั เดียว
เทาน้นั ท่ี lock ทรพั ยากรได ตวั อ่ืน ๆ ทเ่ี หลอื อยูจะเขา สูสถานะของการรอ (wait)
เราจะแสดงใหดถู งึ การใช lock กบั บญั ชีในธนาคารที่มผี ูรว มกนั อยู (ซึ่งเราไดด ดั แปลงมาจาก
โปรแกรมที่แสดงใหดกู อ นหนา น้ี) การนําเงินเขา (deposit) และการนาํ เงินออก (withdraw)
จะตอ งทาํ เพยี งทีละครงั้ หากคนใดนําเงนิ เขา อกี คนหน่ึงจะตอ งรอจนกวากระบวนการจะส้นิ สุดลง
การนําเงินออกกเ็ ชนเดียวกนั ไมเชนน้ัน เงนิ ทอ่ี ยใู นบัญชีจะไมตรงกนั กบั ความเปน จรงิ
public void deposit(double amount) {
//lock this account
account.lock();
try {
balance += amount;
out.printf("%,15.2f%15c%,15.2f%n", amount, ' ', getBalance());
287
เริ่มตน การเขยี นโปรแกรมดว ย Java intro. to Java (FEU.faa)
}
finally {
//unlock this account
account.unlock();
}
}
เราปรับปรงุ การนําเงินเขา สบู ัญชดี วยการปองกันไมใ ห thread ตวั อนื่ เขามาวนุ วายดว ยการใช
class ReentrantLock เปน ตัวกาํ หนด ซง่ึ เราตองประกาศ ดงั น้ี
private final Lock account = new ReentrantLock();
เมอ่ื ประกาศแลว การ lock ก็ทําไดดวยการเรยี กใช method lock() ดงั ทีเ่ หน็ ใน method
deposit()
Method deposit() เริ่มการทาํ งานดวยการใช Lock เพือ่ ไมให thread ตวั อื่นสามารถเขา มา
ทํางานในเวลาเดยี วกนั นี้ได ประโยค account.lock() เปนการบอกถึงความพยายามของ thread
ทีจ่ ะใหไ ดก ารควบคมุ การใชบ ัญชีทีใ่ ชรวมกนั อยู หลังจากทไี่ ด Lock มาแลวเรากน็ าํ เงนิ เขา สู
บญั ชีของเราตอไป ผอู า นจะเหน็ วา thread ตัวอนื่ ไมสามารถท่จี ะเขามาทาํ อะไรกบั บัญชนี ้ีได
จนกวา lock ของเราจะถูกเปด ออกดวยคาํ สง่ั unlock() สาํ หรับการถอนเงินออกจากบัญชีก็
เชนกัน เราก็เขียนไดงา ย ๆ เหมอื นกบั ทเี่ ราทํากบั deposit() ดงั นี้
public void withdraw(double amount) {
//lock this account
account.lock();
try {
//if balance is too low, do nothing
if(balance < amount)
return;
//reset the balance
balance -= amount;
out.printf("%15c%,15.2f%,15.2f%n", ' ', amount, getBalance());
}
finally {
//unlock this account
account.unlock();
}
}
แต code ทเ่ี หน็ น้กี ไ็ มส ามารถทจ่ี ะรบั รองถึงความถูกตองของการทํางานรวมกันของ thread
ทัง้ หลายได ทง้ั นก้ี อ็ าจเปนเพราะวา เมือ่ thread เขา สู critical section หรอื จุดวกิ ฤต (ภาพที่
9.5 แสดงจดุ วิกฤตของ thread – การปรับปรุงจาํ นวนเงิน) แลว ยงั ทําอะไรไมไ ดจ นกวา
condition บางอยางจะเอื้ออาํ นวย เชนในกรณขี องการถอนเงิน เราทาํ ไมไ ดถ า มีเงนิ ไมพอ
ประโยค
if(balance < amount)
return;
ก็ไมสามารถท่จี ะการันตีใหเ ราไดวา จะไมเ กิดการถอนเงินออกถา มีเงินไมพอในบญั ชี (ดูผลลพั ธ
กอ นหนา น้ี) ทงั้ น้กี เ็ น่ืองจากวา โอกาสท่ี thread จะถูกระงบั การทํางานระหวางการเปรียบเทียบ
ดงั กลา ว กับการถายโอนการทาํ งานกลับไปยังผเู รียก (return) น้ันมีความเปน ไปไดสงู
if(balance < amount)
//Thread อาจถูกระงบั การทาํ งาน ณ เวลานี้ได – กอ นการ return
return;
เพราะฉะนัน้ เราตองกําหนดใหม กี ารใช condition object และตวั แปรทีเ่ ปน boolean เปนตวั
ชวยในการท่ีจะไมใหม กี ารถอนเงนิ ในบญั ชถี า จาํ นวนเงินนน้ั มไี มพ อ หรอื มี thread ตัวอื่นกาํ ลงั
ทาํ งานอยกู ับบญั ชีน้ี ดงั นี้
public void withdraw(double amount) {
//lock this account
account.lock();
288
บทที่ 9: Thread intro. to Java (FEU.faa)
try {
//if balance is too low or other
//thread is busy with balance, we wait...
while(balance < amount || !occupied) {
out.printf("waiting for deposit...%n");
funds.await();
}
//reset the balance
balance -= amount;
out.printf("%15c%,15.2f%,15.2f%n",
' ', amount, getBalance());
occupied = false;
//signal threads waiting for the account
funds.signalAll();
}
catch(InterruptedException ie) {
ie.printStackTrace();
}
finally {
//unlock this account
account.unlock();
}
}
ผูอา นจะเห็นวา เราจะกําหนดใหมีการรอถา เงนิ ในบัญชขี องเรามไี มพอ ดว ยการใช while/loop
และ condition object: funds.await() ซ่งึ ตอ งประกาศใชดังนี้
private Condition funds = account.newCondition();
ตวั condition object จะตอ งถกู กําหนดใหเ ปน condition ของ lock ทีเ่ ราใชอยู (account)
ประโยค funds.await() จะทําให thread ทก่ี ําลังทํางานอยู (current thread) ถกู block ซ่งึ เปน
การเปดโอกาสให thread ตวั อืน่ สามารถทํางานกบั บัญชีนี้ได ซง่ึ อาจเปน thread ท่ีนําเงนิ เขาสู
บัญชีของเรา (นเ่ี ปน ความหวัง แตกม็ ีสิทธเ ปนไปไดทีจ่ ะเปน thread สําหรบั การถอนเงนิ ก็ได)
หลงั จากทีก่ ารรอคอยของเราส้ินสุดลง เราก็ถอนเงินออกจากบัญชี (balance -= amount)
พรอ มท้ังสงสญั ญาณไปยัง thread ตวั อ่ืนทีอ่ าจรออยดู วยการเรียกใช
funds.signalAll();
[และเรากต็ อ งเรยี กใชค ําสง่ั เดียวกันหลังจากทเ่ี รานาํ เงินเขาสบู ัญชดี วยเหมอื นกัน เพราะฉะน้นั
deposit() ก็จะกลายเปน ]
public void deposit(double amount) {
//lock this account
account.lock();
try {
//if other thread is busy with balance, we wait...
while(occupied) {
out.printf("waiting for withdrawal...%n");
funds.await();
}
balance += amount;
out.printf("%,15.2f%15c%,15.2f%n", amount, ' ', getBalance());
occupied = true;
//signal threads waiting for the account
funds.signalAll();
}
catch(InterruptedException ie) {
ie.printStackTrace();
}
finally {
//unlock this account
account.unlock();
}
}
289
เริม่ ตน การเขยี นโปรแกรมดว ย Java
แผนภาพทเ่ี หน็ ดา นลางนแ้ี สดงถึงการท่ีท้งั deposit thread และ withdraw thread ทํางานกบั intro. to Java (FEU.faa)
บัญชีทีใ่ ชรว มกันอยูเม่ือตวั ใดตวั หนงึ่ ทํางานเสร็จก็จะสง สญั ญาณไปใหอ กี ตวั หน่ึงท่รี ออยใู น
waiting queue
Deposit Thread Withdraw Thread
CRITICAL account.lock(); account.lock();
SECTION while(occupied) { while(balance < amount ||
… !occupied) {
funds.await(); …
} funds.await();
}
UPDATE BALANCE
UPDATE BALANCE
occupied = true;
occupied = false;
funds.signalAll();
funds.signalAll();
account.unlock();
account.unlock();
ภาพที่ 9.5 Thread สาํ หรับการฝากและถอนเงนิ ออกจากบัญชี
Code ของ Deposit.java และ Withdraw.java กเ็ หมือนเดิม เราเพียงแตเ ปลี่ยนการเรยี กใช
ภายใน ดว ยการเรียกใช SyncAccount แทน UnSyncAccount
สวนสาํ คญั อีกสว นหน่ึงท่จี ําเปนตองมกี ค็ อื การเรียกใช thread ทงั้ หลายซ่งึ เราไดเ ขยี นไวใน
โปรแกรม SyncAccountTest.java ดงั น้ี
1: /**
2: Synchronization on a bank account
3: */
4:
5: import java.util.concurrent.Executors;
6: import java.util.concurrent.ExecutorService;
7: import static java.lang.System.out;
8:
9: class SyncAccountTest {
10: public static void main(String[] args) {
11: //create pool of threads
12: ExecutorService app = Executors.newFixedThreadPool(4);
13:
14: //print header
15: out.printf("%15s%15s%15s%n", "Deposit", "Withdrawal", "Balance");
16:
17: //set up shared account with initial balance
18: SyncAccount sharedAccount = new SyncAccount(0);
19:
20: Deposit p1 = new Deposit(sharedAccount, 100.0);
21: Withdraw p2 = new Withdraw(sharedAccount, 1000.0);
22: Withdraw p3 = new Withdraw(sharedAccount, 100.0);
23: Deposit p4 = new Deposit(sharedAccount, 1000.0);
24:
25: //perform deposit and withdrawal simultaneously
26: try {
27: app.execute(p1);
28: app.execute(p2);
29: app.execute(p3);
30: app.execute(p4);
31: }
32: catch(Exception ex) {
33: ex.printStackTrace();
290
บทท่ี 9: Thread
34: } intro. to Java (FEU.faa)
35: //stop synchronization
36: app.shutdown();
37: }
38: }
เราไดเ ปลี่ยน code ในโปรแกรมตวั นมี้ ากพอสมควร เรายกเลิกการใช Thread โดยตรงเหมอื นท่ี
เราไดท าํ มากอ นหนาน้ใี นโปรแกรม UnSycAccountTest.java และหนั มาใช class
ExecuterService แทนในการจัดการกับ thread กระบวนการกไ็ มย าก โดยกอ นอื่นเราก็สรา ง
thread pool จํานวนสี่ตวั ดวยการเรยี ก
ExecutorService app = Executors.newFixedThreadPool(4);
หลงั จากท่เี ราสรางบญั ชีที่ thread เหลา น้ีใชรวมกนั (SyncAccount sharedAccount = new
SyncAccount(0)) เรากส็ ราง thread 4 ตวั คอื p1, p2, p3, และ p4 ขน้ึ มาทําการฝากและถอน
เงินออกจากบัญชีโดยกําหนดให p1 และ p4 เปน ผูฝ ากสวน p2 และ p3 เปน ผูถ อน
เราฝากและถอนดวยการเรยี ก app.execute() กบั thread ทั้งส่ตี วั ดังทีแ่ สดงใหดใู นบรรทัดท่ี 27
– 30 และเราจะเรยี กใช app.shutdown() หลงั จากท่ี thread ทัง้ ส่ีทํางานเสร็จแลว จากการ
ทดลอง run โปรแกรมดู ผลลัพธท เี่ ราไดค ือ
Deposit Withdrawal Balance
0.00
100.00
waiting for deposit... 100.00
100.00 0.00
waiting for deposit... 1,000.00
1,000.00 0.00
1,000.00
100.00
waiting for deposit... 0.00
waiting for deposit...
100.00
100.00
0.00
100.00
waiting for deposit... 1,000.00
0.00
100.00
waiting for deposit... 100.00
waiting for withdrawal...
0.00
100.00
waiting for deposit... 1,000.00
1,000.00 0.00
100.00
1,000.00
0.00
waiting for deposit...
100.00 1,000.00
0.00
waiting for deposit...
waiting for withdrawal... 1,000.00
0.00
waiting for withdrawal...
100.00
waiting for deposit...
1,000.00
waiting for withdrawal...
1,000.00
100.00
waiting for deposit...
waiting for withdrawal...
100.00
waiting for deposit...
1,000.00
1,000.00
1,000.00
1,000.00
ผูอานควรสังเกตถงึ ผลลพั ธท ไ่ี ดว า ถกู ตอ งตามขั้นตอนหรือไม ซง่ึ สามารถตรวจสอบไดดว ยการ
run หลาย ๆ ครั้ง แนน อนวา ผลลพั ธท ไี่ ดจะไมเหมือนกัน แตส ิง่ ที่เหมอื นกนั คอื balance จะตอง
เปน ศูนยเ สมอเมอื่ การทํางานส้ินสุดลง
โครงสรา งหลัก ๆ ในการเรยี กใช lock และ condition object มดี ังนี้คอื
291
เร่มิ ตนการเขียนโปรแกรมดว ย Java intro. to Java (FEU.faa)
someLock.lock();
try {
while(!(ok to proceed))
condition.await();
CRITICAL SECTION
condition.signalAll();
}
catch() {
…//some exception might be thrown here
}
finally {
//must unlock the lock even though exception is thrown
someLock.unlock();
}
เราอาจเรียกใช signal() แทน signalAll() กไ็ ด แตถ า เราตอ งการให thread ตวั ใด ๆ มสี ิทธใ น
การใชท รพั ยากร (หลังจากทีถ่ กู สงคนื ) เทา ๆ กันเราก็ควรเรียกใช signalAll() มากกวา signal()
และกอ นทเ่ี ราจะไปดูเรือ่ งของการประสานเวลาดว ยการใช synchronized เราจะมาสรุปการใช
lock ดังนี้
1. lock จะปกปอง code จากการ execute ของ thread ตัวอื่น
2. lock จะเปน ตัวบริหารจัดการ การเขา หา code ของ thread ท่มี อี ยูในระบบ
3. เราสามารถใช lock รว มกบั condition object อืน่ ๆ ได
9.6.2 การใช synchronize ในการประสานเวลา
การใช synchronized (keyword) เปน วธิ ีการประสานเวลาแบบเดมิ ที่ Java ใชก อนที่จะมีการใช
lock เหมือนกบั ที่เราไดแสดงใหดใู นตอนกอ นหนานี้ ถา object ที่ java สรางขึ้นจะมี lock ของ
ตวั เองอยูถา method ใดมกี ารเรียกใช synchronized code ของ method นน้ั จะไดร บั การ
ปองกนั จาก lock นั้น ภาพที่ 9.6 แสดงการเปรียบเทยี บการใช synchronized และการใช lock
ในการประสานเวลา
การใช synchronized การใช lock
public synchronized void method() { public void method() {
…
Code ภายใน method implicit.lock();
… try {
} …
Code ภายใน method
หรอื
…
public void method() { }
synchronized(lock) {
… finally {
Code … implicit.unlock();
}
}
} }
ภาพที่ 9.6 code แสดงการประสานเวลาดวยการใช synchronized และการใช lock
โปรแกรมตัวอยางตอไปจะเปน ตัวอยา งการประสานเวลาดวยการใช synchronized เปนตวั ชว ย
โดยเราจะปรับปรงุ การทาํ งานของโปรแกรม SyncAccount.java ดังน้ี
1: /**
2: Account using keyword synchronized
3: BankAccount is shared by Account1 and Account2
4: */
5:
292
บทท่ี 9: Thread
6: import static java.lang.System.out;
7:
8: class BankAccount { intro. to Java (FEU.faa)
9: private double balance;
10: private boolean occupied = false;
11: private Object THIS_ACCOUNT; //critical section
12:
13: //set up initial balance and critical section
14: BankAccount(double balance) {
15: this.balance = balance;
16: THIS_ACCOUNT = new Object();
17: out.printf("%15c%15c%,15.2f%n", ' ', ' ', balance);
18: }
19:
20: //deposit amount into a shared account
21: public void deposit(double amount) {
22: //synchronize shared account
23: synchronized(THIS_ACCOUNT) {
24: try {
25: //wait for other thread
26: while(occupied) {
27: out.println("Wait for withdrawal ...");
28: THIS_ACCOUNT.wait();
29: }
30: balance += amount; //update balance
31: occupied = true;
32: out.printf("%,15.2f%15c%,15.2f%n", amount,
33: ' ', balance);
34: THIS_ACCOUNT.notifyAll(); //notify threads
35: }
36: catch(InterruptedException e) {
37: e.printStackTrace();
38: }
39: }
40: }
41:
42: //withdraw amount from a shared account
43: public void withdraw(double amount) {
44: //synchronize shared account
45: synchronized(THIS_ACCOUNT) {
46: try {
47: //wait for other thread
48: while(balance < amount || !occupied) {
49: out.println("Wait for deposit ...");
50: THIS_ACCOUNT.wait();
51: }
52: occupied = false;
53: balance -= amount; //update balance
54: out.printf("%15c%,15.2f%,15.2f%n",
55: ' ', amount, balance);
56: THIS_ACCOUNT.notifyAll(); //notify other thread
57: }
58: catch(InterruptedException e) {
59: e.printStackTrace();
60: }
61: }
62: }
63: }
ภายในโปรแกรม BankAccount เราไดก าํ หนดใหมกี ารใชต ัวแปร THIS_ACCOUNT เปน ตัว
ประสานเวลา โดยสรางมาจาก Object (บรรทัดที่ 11) และเราจะทาํ การประสานเวลาใน method
deposit() และ withdraw() ซงึ่ ในการทํางานของ deposit() และ withdraw() นัน้ เราจะ
กาํ หนดใหมีการรอเหมือนกับที่เราทาํ ในตวั อยา งกอนหนานี้ สว นสําคัญของการประสานเวลาก็คอื
synchronized(. . .) {
//code … ทม่ี ีการเรียกใช wait() และ notify() หรือ notifyAll()
}
wait() ก็คอื การรอให thread ตวั อื่นเสรจ็ จากงานท่ที าํ อยู
notify() และ notifyAll() เปน การบอกให thread ตวั อ่นื รวู า critical section นน้ั เปด โอกาสให
เขาไปทาํ งานไดแลว
293
เร่มิ ตน การเขยี นโปรแกรมดวย Java
เชน code ท่ีอยใู น synchronized block เชนการประสานเวลาของการนาํ เงนิ เขา บัญชนี ี้ intro. to Java (FEU.faa)
synchronized(THIS_ACCOUNT) {
try {
//wait for other thread
while(occupied) {
out.println("Wait for withdrawal ...");
THIS_ACCOUNT.wait();
}
balance += amount; //update balance
occupied = true;
out.printf("%,15.2f%15c%,15.2f%n", amount, ' ', balance);
THIS_ACCOUNT.notifyAll(); //notify other thread
}
catch(InterruptedException e) {
e.printStackTrace();
}
ในสว นของ Account1.java และ Account2.java กเ็ ปนการนาํ เงนิ เขา และการถอนเงนิ ออกจาก
บญั ชตี ามลําดับ โดยมกี ระบวนการทํางานดงั น้ี
1: /**
2: Program to access a shared account
3: */
4:
5: import static java.lang.System.out;
6: import java.util.Random;
7:
8: class Account1 extends Thread {
9: //shared saving account
10: private BankAccount saving;
11: //amount to deposit
12: private double amount;
13: //waiting time
14: private Random sleepTime = new Random();
15:
16: //set the shared account
17: public Account1(BankAccount saving, double amount) {
18: this.saving = saving;
19: this.amount = amount;
20: }
21:
22: //deposit random amounts 5 times
23: public void run() {
24: try {
25: for(int i = 0; i < 5; i++) {
26: saving.deposit(amount);
27: Thread.sleep(sleepTime.nextInt(2000));
28: }
29: }
30: catch(InterruptedException ie) {
31: ie.printStackTrace();
32: }
33: }
34: }
1: /**
2: Program to access a shared account
3: */
4:
5: import static java.lang.System.out;
6: import java.util.Random;
7:
8: class Account2 extends Thread {
9: //shared account
10: private BankAccount saving;
11: //amount to withdraw
12: private double amount;
13: //busy time
14: private Random sleepTime = new Random();
294
บทที่ 9: Thread
15: //set a shared account intro. to Java (FEU.faa)
public Account2(BankAccount saving, double amount) {
16:
17: this.saving = saving;
this.amount = amount;
18: }
19:
20: //withdraw random amounts five times
public void run() {
21:
22: try {
23: for(int i = 0; i < 5; i++) {
saving.withdraw(amount);
24: Thread.sleep(sleepTime.nextInt(2000));
25: }
26: }
27: catch(InterruptedException ie) {
28:
ie.printStackTrace();
29: }
30: }
31:
32:
33:
34: }
การทํางานของท้งั สองโปรแกรมก็คลา ย ๆ กับ Deposit และ Withdraw เราเพียงแตท ําการ
extends Thread ใหก บั ทง้ั สอง class แทนการ implement Runnable สว นการทดสอบ
กระบวนการของทัง้ สองโปรแกรมเราทาํ ในโปรแกรม BankAccountTest.java ซง่ึ มีขั้นตอนดังนี้
1: /**
2: Synchronization on a bank account
3: */
4:
5: import static java.lang.System.out;
6:
7: class BankAccountTest {
8: public static void main(String[] args) {
9: out.printf("%15s%15s%15s%n", "Deposit", "Withdrawal", "Balance");
10: //set up a shared account
11: BankAccount sharedAccount = new BankAccount(0);
12:
13: //create 2 accounts
14: Account1 acc1 = new Account1(sharedAccount, 100.0);
15: Account2 acc2 = new Account2(sharedAccount, 100.0);
16: Account1 acc3 = new Account1(sharedAccount, 100.0);
17: Account2 acc4 = new Account2(sharedAccount, 100.0);
18:
19: //start running the two processes
20: acc1.start();
21: acc2.start();
22: acc3.start();
23: acc4.start();
24: }
25: }
ผลลัพธข องการ run ของโปรแกรมนีก้ ค็ ลา ย ๆ กบั ทีเ่ ราได run กอ นหนา น้ี คือ
Deposit Withdrawal Balance
0.00
100.00
100.00 100.00
0.00
100.00
100.00 100.00
0.00
100.00
100.00 100.00
0.00
Wait for deposit process ...
100.00
100.00 0.00
100.00
100.00
Wait for deposit process ... 0.00
100.00 100.00
0.00
100.00
100.00 100.00
0.00
100.00
100.00
100.00
295
เริ่มตน การเขยี นโปรแกรมดว ย Java
Wait for deposit process ... 100.00 intro. to Java (FEU.faa)
0.00
100.00
100.00 100.00
0.00
Wait for deposit process ...
100.00 100.00
100.00 0.00
Wait for deposit process ...
100.00
100.00
เราใชรูปแบบของ synchronized แบบที่ไมต อ งใช object (เชนทีเ่ ราใช object:
THIS_ACCOUNT ในโปรแกรมตวั อยาง) โดยตรงก็ได เราเพยี งแคเ ปลย่ี น code ของเราใหเ ปน
(แสดงเฉพาะ deposit() ผอู านควรพิจารณาวา withdraw() นา จะเปลยี่ นอยางไร)
public synchronized void deposit(double amount) {
try {
//wait for other thread
while(occupied) {
out.println("Wait for withdrawal process ...");
wait();
}
balance += amount;
occupied = true;
out.printf("%,15.2f%15c%,15.2f%n", amount,
' ', balance);
notifyAll();
}
catch(InterruptedException e) {
e.printStackTrace();
}
}
9.6.3 Deadlock
ลองดูตวั อยา งท่เี ราไดจากการ run โปรแกรมการฝากและถอนเงินนี้
Deposit Withdrawal Balance
0.00
100.00
waiting for deposit... 100.00
100.00 0.00
waiting for deposit...
waiting for withdrawal...
waiting for withdrawal...
waiting for deposit...
เหตกุ ารณนเ้ี กิดขนึ้ เมือ่ เราเปล่ียน code ของการถอนเงินใหเปน
account.lock();
try {
while(balance < amount) {
out.printf("waiting for deposit...%n");
funds.await();
}
//reset the balance
balance -= amount;
out.printf("%15c%,15.2f%,15.2f%n", ' ', amount, getBalance());
//signal threads waiting for the account
funds.signalAll();
}
catch(InterruptedException ie) {
ie.printStackTrace();
}
finally {
//unlock this account
account.unlock();
}
296