The words you are searching are inside this book. To get more targeted content, please make full-text search by clicking here.

Introduction to Programming Java Language

Discover the best professional documents and content resources in AnyFlip Document Base.
Search
Published by ปิติ เกตุ, 2021-03-22 04:12:52

Introduction to Programming Java Language

Introduction to Programming Java Language

บทที่ 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


Click to View FlipBook Version