Bài 8: Luồng vào ra
Lê Hồng Phương phuonglh@gmail.com Khoa Toán-Cơ-Tin học Trường Đại học Khoa học Tự nhiên Hà Nội
Nội dung
● Tổng quan về luồng vào ra
● Các kiểu luồng vào ra:
– Luồng byte
– Luồng kí tự
– Luồng có đệm
– Luồng dữ liệu
– Luồng đối tượng
2012-2013
Object-Oriented Programming: I/O Streams
2
Luồng vào ra
● Luồng vào ra (input/output streams – IO streams)
– Biểu diễn một nguồn vào và một đích ra
● Có nhiều kiểu nguồn và đích:
– tệp trên đĩa, thiết bị, chương trình, bộ nhớ
● Có nhiều kiểu dữ liệu khác nhau:
– các bytes
– các kiểu dữ liệu đơn giản
– các đối tượng
2012-2013
Object-Oriented Programming: I/O Streams
3
Luồng vào ra
● Định nghĩa tổng quát: mỗi luồng là một chuỗi dữ liệu.
● Mỗi chương trình thường sử dụng:
– Một luồng vào để đọc dữ liệu từ một nguồn, mỗi
lần đọc một đơn vị dữ liệu.
– Một luồng ra để ghi dữ liệu ra một đích, mỗi lần
ghi một đơn vị dữ liệu.
stream
Program
010111000111
2012-2013
Object-Oriented Programming: I/O Streams
4
Data source
Luồng vào ra
● Các chương trình sử dụng luồng byte để đọc/ghi các
byte (8 bits).
● Trong Java, có nhiều lớp luồng byte. Các lớp này đều
phái sinh từ hai lớp:
– InputStream
– OutputStream
● Khi đọc/ghi dữ liệu byte từ/vào tệp, ta sử dụng
– FileInputStream
– FileOutputStream
2012-2013
Object-Oriented Programming: I/O Streams
5
Luồng byte
● Viết chương trình sao chép một tệp, mô phỏng lệnh
– cp input.txt output.txt (Unix, Linux)
– copy input.txt output.txt (MS Windows)
● Sử dụng hai luồng byte gắn với hai tệp:
– Một luồng để đọc tệp input.txt
– Một luồng để ghi tệp output.txt
● Vòng lặp: mỗi lần đọc một byte của tệp input.txt và ghi nó vào tệp output.txt. Khi hết tệp thì hàm đọc trả về -1.
2012-2013
Object-Oriented Programming: I/O Streams
6
Luồng byte
public class CopyBytes {
public static void main(String[] args) throws IOException {
InputStream in = null; OutputStream out = null; try {
in = new FileInputStream("input.txt"); out = new FileOutputStream("output.txt"); int c; while ((c = in.read()) != -1) {
out.write(c);
Tại sao không có các khối catch? }
} finally {
Luôn phải đóng mọi luồng dữ liệu khi sử dụng xong. if (in != null) in.close(); if (out != null) out.close();
}
}
2012-2013
Object-Oriented Programming: I/O Streams
7
}
Luồng byte
● Tuy nhiên, luồng byte biểu diễn các thao tác vào/ra mức thấp, ta nên tránh sử dụng trực tiếp các luồng này.
● Vì các tệp input.txt, output.txt chứa các dữ liệu dạng
kí tự, cách tốt nhất là sử dụng các luồng kí tự.
● Nếu không nên dùng trực tiếp, tại sao lại giới thiệu
luồng byte?
– Vì luồng byte là cơ sở để xây dựng các kiểu luồng
khác.
2012-2013
Object-Oriented Programming: I/O Streams
8
Luồng kí tự
● Nền tảng Java lưu các giá trị kí tự sử dụng chuẩn
Unicode.
● Mọi lớp biểu diễn luồng kí tự đều phái sinh từ hai lớp
Reader và Writer.
● Khi đọc/ghi các tệp văn bản (luồng kí tự trong tệp), ta
sử dụng các lớp
– FileReader
– FileWriter
2012-2013
Object-Oriented Programming: I/O Streams
9
Luồng kí tự
public class CopyCharacters {
public static void main(String[] args) throws IOException {
Reader in = null; Writer out = null; try {
in = new FileReader("input.txt"); out = new FileWriter("output.txt"); int c; while ((c = in.read()) != -1) {
out.write(c);
}
} finally {
if (in != null) in.close(); if (out != null) out.close();
}
}
2012-2013
Object-Oriented Programming: I/O Streams
10
}
Luồng có đệm
● Luồng không đệm:
– Mỗi lệnh đọc, ghi được thực hiện trực tiếp bởi hệ điều
hành.
– Chương trình kém hiệu quả, do mỗi lệnh đều đòi hỏi
truy xuất ổ đĩa, truyền dữ liệu qua mạng hoặc các thao tác tốn kém khác.
● Luồng có đệm hiệu quả hơn nhiều:
– Đọc/ghi dữ liệu từ/vào một vùng nhớ (gọi là bộ đệm)
– Khi nào bộ đệm đầy thì mới gọi các lệnh đọc/ghi của hệ
điều hành
2012-2013
Object-Oriented Programming: I/O Streams
11
Luồng có đệm
● Các lớp hỗ trợ luồng có đệm:
– BufferedInputStream, BufferedOutputStream
– BufferedReader, BufferedWriter
● Kích hoạt việc đọc/ghi bộ đệm khi bộ đệm chưa đầy:
– Gọi phương thức flush()
● Để chuyển một luồng không đệm thành một luồng có
đệm, ta tạo một luồng có đệm (tương ứng):
BufferedReader bufferedReader = new
BufferedReader(new FileReader("input.txt"));
BufferedWriter bufferedWriter = new
2012-2013
Object-Oriented Programming: I/O Streams
12
BufferedWriter(new FileWriter("output.txt"));
Luồng dữ liệu
● Luồng dữ liệu hỗ trợ việc đọc, ghi các giá trị thuộc
các kiểu dữ liệu cơ sở:
– boolean, char, byte, short, int, long, float, double
– String
● Mọi luồng dữ liệu cài đặt một trong hai giao diện sau:
– DataInput, DataOutput
● Hai cài đặt được sử dụng nhiều là
– DataInputStream, DataOutputStream
2012-2013
Object-Oriented Programming: I/O Streams
13
Luồng dữ liệu
● Ví dụ, giả sử ta có 3 biến price, unit và desc lần lượt chứa các giá trị thuộc các kiểu double, int và String.
● Ghi các giá trị vào tệp:
Sử dụng luồng có đệm để ghi vào tệp dataFile.dat
DataOutputStream out; try {
out = new DataOutputStream(new BufferedOutputStream(
new FileOutputStream("dataFile.dat")));
Gọi các phương thức tương ứng của out out.writeDouble(price); out.writeInt(unit); out.writeUTF(desc);
} catch (FileNotFoundException e) {
e.printStackTrace(); } catch (IOException e) { e.printStackTrace();
2012-2013
Object-Oriented Programming: I/O Streams
14
}
Luồng dữ liệu
● Đọc dữ liệu từ tệp và gán giá trị cho các biến tương
ứng:
DataInputStream in; try {
in = new DataInputStream(new BufferedInputStream(
new FileInputStream("dataFile.dat")));
Gọi các phương thức tương ứng của in
double price = in.readDouble(); int unit = in.readInt(); String desc = in.readUTF(); } catch (FileNotFoundException e) {
e.printStackTrace(); } catch (IOException e) { e.printStackTrace();
2012-2013
Object-Oriented Programming: I/O Streams
15
}
Luồng đối tượng
● Luồng đối tượng hỗ trợ việc đọc, ghi các đối tượng.
● Nếu đối tượng thuộc một lớp cài đặt giao diện
Serializable thì ta có thể sử dụng luồng đối tượng để đọc, ghi đối tượng đó.
● Hai lớp hỗ trợ luồng đối tượng:
– ObjectInputStream, ObjectOutputStream
● Hai lớp này tương ứng cài đặt các giao diện
– ObjectInput và ObjectOutput
2012-2013
Object-Oriented Programming: I/O Streams
16
Luồng đối tượng
● ObjectInput và ObjectOutput tương ứng là các giao diện
con của các giao diện DataInput và DataOutput.
● Chính vì vậy, mọi phương thức vào, ra đối với các kiểu
dữ liệu cơ sở do DataInput, DataOutput hỗ trợ đều được cài đặt trong ObjectInput, ObjectOutput.
ObjectOutput oo = null; try {
oo = new ObjectOutputStream(new BufferedOutputStream(
new FileOutputStream("dates.dat"))); oo.writeObject(Calendar.getInstance());
} catch (FileNotFoundException e) {
e.printStackTrace(); } catch (IOException e) { e.printStackTrace();
2012-2013
Object-Oriented Programming: I/O Streams
17
}
Luồng đối tượng
ObjectInput oi = null; try {
oi = new ObjectInputStream(new BufferedInputStream(
new FileInputStream("dates.dat"))); Calendar date = (Calendar)oi.readObject();
} catch (FileNotFoundException e) {
e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); Phương thức readObject() luôn trả về kiểu Object nên ta phải ép thành kiểu thích hợp. } catch (ClassNotFoundException e) {
e.printStackTrace();
2012-2013
Object-Oriented Programming: I/O Streams
18
}