1.1 Giới thiệu
Một chương trình máy tính cần phải lấy dữ liệu từ bên ngoài vào chương trình để xử lý. Sau khi chương trình xử lý xong sẽ xuất kết quả ra ngoài cho thành phần khác để tiếp tục xử lý hoặc lưu trữ. Dữ liệu được truyền từ chương trình này sang chương trình khác bằng các luồng dữ liệu. Để cài đặt việc truyền dữ liệu này, Java cung cấp package java.iovới rất nhiều lớp và các tính năng phong phú.
Xét theo loại dữ liệu, Java chia các luồng thành hai loại là luồng byte (byte stream) và luồng ký tự (character stream). Giữa hai loại dữ liệu này có các lớp trung gian để phục vụ cho việc chuyển đổi.
Xét theo thao tác, Java chia các luồng thành hai loại là input (đọc byte) và output(ghi byte) hoặc reader (đọc character) và writer (ghi character).
Byte streams | Character streams | |
Source streams | InputStream | Reader |
Sink streams | OutputStream | Writer |
Các stream cơ bản trong Java
Minh họa các nguồn dữ liệu |
1.2 Các luồng Byte
Các luồng byte có chức năng đọc hoặc ghi dữ liệu dạng byte. Dựa vào thao tác, các luồng byte được chia ra hai loại chính: nhóm input được đại diện bởi lớp InputStream và nhóm output được đại diện bởi OutputStream. Các lớp xử lý trên luồng byte đều được kế thừa từ hai lớp này.
Sơ đồ tổ chức của các stream đọc byte |
Sơ đồ tổ chức của các stream ghi byte |
1.2.1 Các luồng byte tổng quát
Gồm các lớp InputStreamvà OutputStream. Đây hai là lớp trừu tượng, nó quy định các phương thức đọc và ghi dữ liệu dạng byte và một số phương thức hỗ trợ khác:
Phương thức | Ý nghĩa |
Lớp InputStream | |
int available() | Trả về số byte còn lại trong stream. |
void close() | Đóng stream. |
void mark(int readlimit) | Đánh dấu vị trí hiện tại. Nếu sau khi mark ta đọc quá readlimit byte thì chỗ đánh dấu không còn hiệu lực. |
boolean markSupported() | Trả về true nếu stream hỗ trợ mark và reset. |
abstract int read() | Đọc byte kế tiếp trong stream. Quy ước: Trả về -1 nếu hết stream. Đây là phương thức trừu tượng. |
int read(byte[]buffer) | Đọc một dãy byte và lưu kết quả trong mảng buffer. Số byte đọc được tối đa là sức chứa (length) của buffer. Nếu buffer có sức chứa là 0 thì sẽ không đọc được gì. Sau khi đọc xong sẽ trả về số byte đọc được thật sự (nếu đang đọc mà hết stream thì số byte đọc được thật sự sẽ nhỏ hơn sức chứa của buffer). Nếu stream đã hết mà vẫn đọc nữa thì kết quả trả về là -1. |
int read(byte[]buffer, int offset, int len) | Đọc một dãy byte và lưu kết quả trong mảng buffer kể tự byte thứ offset. Số byte đọc được tối đa là len. Nếu len là 0 thì sẽ không đọc được gì. Sau khi đọc xong sẽ trả về số byte đọc được thật sự (nếu đang đọc mà hết stream thì số byte đọc được thật sự sẽ nhỏ hơn len). Nếu stream đã hết mà vẫn đọc nữa thì kết quả trả về là -1. |
void reset() | Trở lại chỗ đã đánh dấu bằng phương thức mark(). |
long skip(long n) | Bỏ qua n byte (để đọc các byte tiếp sau n byte đó). |
Lớp OutputStream | |
void close() | Đóng stream. |
void flush() | Buộc stream ghi hết dữ liệu trong vùng đệm ra ngoài. |
void write(byte[]buffer) | Ghi một dãy byte vào stream. Số byte được ghi sẽ là buffer.length. |
int write(byte[]buffer, int offset, int len) | Ghi một dãy byte vào stream. Bắt đầu ghi từ byte thứ offset, và ghi tổng cộng len byte. |
abstract void write(int b) | Ghi một byte vào stream. Đây là phương thức trừu tượng. |
Các phương thức cơ bản của các byte stream tổng quát
Lưu ý: Các phương thức đọc/ghi sẽ phát sinh IOException nếu có lỗi xảy ra.
1.2.2 Các luồng đọc byte hiện thực
Để sử đọc/ghi luồng dữ liệu dạng byte, các lớp con hiện thực của InputStream và OutputStreamthường được sử dụng gồm:
§ FileInputStream: đọc (các) byte từ tập tin.
§ FileOutputStream: ghi (các) byte vào tập tin.
§ ByteArrayInputStream: chứa bộ đệm là mảng byte để đọc dữ liệu.
§ ByteArrayOutputStream: chứa bộ đệm là mảng byte để ghi dữ liệu.
§ PipedInputStream: đọc (các) byte từ một piped output stream.
§ PipedOutputStream: ghi (các) byte vào một piped input stream.
Nhìn chung các lớp này đều có những chức năng chính tương tự như nhau. Dưới đây chỉ trình bày những phương thức đặc thù của chúng.
Stream | Phương thức | Ý nghĩa |
File Input Stream | FileInputStream(File file) | Tạo FileInputStream để đọc dữ liệu từ một tập tin liên kết tới đối tượng File. |
FileInputStream(String name) | Tạo FileInputStream để đọc dữ liệu từ một tập tin có tên là name. | |
File Output Stream | FileOutputStream(File file) | Tạo FileOutputStream để ghi dữ liệu vào một tập tin liên kết tới đối tượng File. |
FileOutputStream(File file, boolean append) | Tạo FileOutputStream để ghi dữ liệu tập tin liên kết tới đối tượng File. Nếu append là true thì sẽ ghi tiếp vào cuối tập tin, ngược lại thì ghi đè lên tập tin. | |
FileOutputStream(String name) | Tạo FileOutputStream để ghi dữ liệu vào một tập tin có tên là name. | |
FileOutputStream(String name, boolean append) | Tạo FileOutputStream để ghi dữ liệu vào một tập tin có tên là name. Nếu append là true thì sẽ ghi tiếp vào cuối tập tin, ngược lại thì ghi đè lên tập tin. | |
Byte Array Input Stream | ByteArrayInputStream(byte[] buf) | Tạo ra ByteArrayInputStream và dùng buf để làm vùng đệm. |
ByteArrayInputStream(byte[] buf, int off, int len) | Tạo ra ByteArrayInputStream và dùng một phần của buf để làm vùng đệm. | |
Byte Array Output Stream | ByteArrayOutputStream() | Tạo ra một ByteArrayOutputStream với vùng đệm 32 byte và có thể tăng nếu cần. |
ByteArrayOutputStream(int size) | Tạo ra một ByteArrayOutputStream với vùng đệm size byte. | |
byte[]toByteArray() | Tạo ra mảng byte là bản sao của vùng đệm của this. | |
String toString() | Tạo ra chuỗi là bản sao của vùng đệm của this với các byte được đổi thành ký tự tương ứng. | |
void writeTo(OutputStream out) | Ghi dữ liệu trong vùng đệm vào một output stream khác. | |
Piped Input Stream | PipedInputStream() | Tạo ra một piped input stream. Stream này chưa được kết nối với piped output stream nào. |
PipedInputStream (PipedOutputStream src) | Tạo ra một piped input stream kết nối với piped output stream src. | |
connect(PipedOutputStream src) | Kết nối tới piped output stream src. | |
Piped Output Stream | PipedOutputStream() | Tạo ra một piped output stream. Stream này chưa được kết nối với piped input stream nào. |
PipedOutputStream ( PipedInputStream snk) | Tạo ra một piped output stream kết nối với piped input stream snk. | |
connect(PipedInputStream snk) | Kết nối tới piped input stream snk. |
Bảng 8: Những phương thức đặc trưng của các byte stream cụ thể
1.2.3 Các ví dụ
Để dùng các stream, trước hết cần import package java.io.
importjava.io.*;
Dưới đây sẽ trình bày các ví dụ về file stream và piped stream.
File stream:
String fileName="C:\\TestB.txt";
// Phát sinh và xuất mảng ngẫu nhiên
byte[]a=new byte[10];
System.out.print("Du lieu phat sinh: ");
for(inti=0;i<a.length;i++)
{
a[i]=(byte)Math.round(Math.random()*20);
System.out.print(a[i]+" ");
}
System.out.println();
// Ghi mảng a vào tập tin
FileOutputStream fo=newFileOutputStream(fileName);
fo.write(a);
fo.close();
// Đọc tập tin vào mảng b
FileInputStream fi=newFileInputStream (fileName);
byte[]b=new byte[fi.available()];
fi.read(b);
fi.close();
// Xuất mảng b
System.out.print("Du lieu doc duoc : ");
for(inti=0;i<b.length;i++)
{
System.out.print(b[i]+" ");
}
System.out.println();
Kết quả thực thi (các con số sẽ khác nhau với mỗi lần chạy):
Du lieu phat sinh: 4 14 0 16 2 19 10 9 9 15
Du lieu doc duoc : 4 14 0 16 2 19 10 9 9 15
Press any key to continue...
Ví dụ về luồng mảng: Tạo một mảng gồm 100 byte rồi gắn vào mảng này một luồng ByteArrayInputStream để lấy dữ liệu ra.
import java.io.*;
public class LuongNhapMang
{
public static void main(String[] args)
{
byte[] b = new byte[100];
for(byte i=0;i<b.length;i++) b[i]=i;
try{
InputStream is = new ByteArrayInputStream(b);
for(byte i=0;i<b.length;i++)
System.out.print(is.read()+" ");
}
catch(IOException e)
{
System.err.println(e);
}
}
}
Kết quả thực hiện chương trình
C:\MyJava\Baitap>java LuongNhapMang
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
Ví dụ luồng viết mảng: Viết chương trình tạo lập một luồng xuất mảng (ByteArrayOutputStream) 100 byte. Ghi vào luồng xuất mảng 100 phần tử từ 0 đến 99. Đổ dữ liệu từ luồng xuất mảng vào mảng b. In dữ liệu từ mảng b ra màn hình.
import java.io.*;
class LuongXuatMang
{
public static void main(String[] args)
{
try{
//Tao mot luong xuat mang 100 byte
ByteArrayOutputStream os = new ByteArrayOutputStream(100);
//Ghi du lieu vao luong
for(byte i=0;i<100;i++) os.write(i);
//Doc du lieu tu luong vao mang
byte[] b = os.toByteArray();
for(byte i=0;i<100;i++) System.out.print(b[i]+" ");
os.close();
}catch(IOException e)
{
System.err.println(e);
}
}
}
Kết quả thực hiện chương trình:
C:\MyJava\Baitap>java LuongXuatMang
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
Ví dụ về Piped stream:
PipedInputStream pi= newPipedInputStream();
PipedOutputStream po= newPipedOutputStream(pi);
// Ghi các số nguyên ngẫu nhiên bằng PipedOutputStream
System.out.print("Du lieu ghi duoc: ");
for(int i=0;i<10;i++)
{
int b=(int)Math.round(Math.random()*20);
po.write(b);
System.out.print(b+" ");
}
po.flush();
System.out.println();
// Đọc các số nguyên từ PipedOutputStream bằng PipedInputStream
System.out.print("Du lieu doc duoc: ");
while(pi.available()>0)
{
int b=pi.read();
System.out.print(b+" ");
}
System.out.println();
// Đóng các stream
pi.close();
po.close();
Kết quả thực thi (các con số sẽ khác nhau với mỗi lần chạy):
Du lieu ghi duoc: 16 19 9 11 19 3 2 13 18 16
Du lieu doc duoc: 16 19 9 11 19 3 2 13 18 16
Press any key to continue...
1.3 Các luồng ký tự
Các luồng ký tự (character stream) có chức năng đọc hoặc ghi dữ liệu dạng ký tự. Dựa vào thao tác, các character stream được chia ra hai loại chính: nhóm input được đại diện bởi lớp Readervà nhóm output được đại diện bởi Writer. Các lớp xử lý trên character stream đều được kế thừa từ hai lớp này.
Hình 2: Sơ đồ tổ chức của các stream đọc ký tự |
Hình 3: Sơ đồ tổ chức của các stream ghi ký tự |
1.3.1 Các luồng ký tự tổng quát
Gồm các lớp Readervà Writer. Đây hai là lớp trừu tượng, nó quy định các phương thức đọc và ghi dữ liệu dạng character và một số phương thức hỗ trợ khác:
Phương thức | Ý nghĩa |
Lớp Reader | |
void close() | Đóng stream. |
void mark(int readlimit) | Đánh dấu vị trí hiện tại. Nếu sau khi đánh dấu ta đọc quá readlimit ký tự thì chỗ đánh dấu không còn hiệu lực. |
boolean markSupported() | Trả về true nếu stream hỗ trợ mark và reset. |
abstract int read() | Đọc ký tự kế tiếp trong stream. Quy ước: Trả về -1 nếu hết stream. Đây là phương thức trừu tượng. |
int read(char[]cbuf) | Đọc một dãy ký tự và lưu kết quả trong mảng cbuf. Sau khi đọc xong sẽ trả về số ký tự đọc được thật sự (nếu đang đọc mà hết stream thì số ký tự đọc được thật sự sẽ nhỏ hơn sức chứa của cbuf). Nếu stream đã hết mà vẫn đọc nữa thì kết quả trả về là -1. |
int read(char[]cbuf, int offset, int len) | Đọc một dãy ký tự và lưu kết quả trong mảng cbuf kể tự ký tự thứ offset. Sau khi đọc xong sẽ trả về số ký tự đọc được thật sự (nếu đang đọc mà hết stream thì số ký tự đọc được thật sự sẽ nhỏ hơn len). Nếu stream đã hết mà vẫn đọc nữa thì kết quả trả về là -1. |
void read(CharBuffer target) | Đọc một dãy ký tự và lưu kết quả trong target. |
Trả về true nếu stream sẵn sàng để đọc. | |
void reset() | Trở lại chỗ đã đánh dấu bằng phương thức mark(). |
long skip(long n) | Bỏ qua n ký tự (để đọc các ký tự tiếp sau). |
Lớp Writer | |
Writer append(char c) | Nối đuôi ký tự c vào stream. |
Writer append(CharSequence csq) | Nối đuôi dãy ký tự csq vào stream. |
Writer append(CharSequence csq, int start, int end ) | Nối đuôi một phần của dãy ký tự csq vào stream. |
void close() | Đóng stream. |
void flush() | Buộc stream ghi hết dữ liệu trong vùng đệm ra ngoài. |
void write(char[]cbuf) | Ghi một mảng ký tự vào stream. Số ký tự được ghi sẽ là cbuffer.length. |
abstract void write(int c) | Ghi một ký tự vào stream. Đây là phương thức trừu tượng. |
void write(String c) | Ghi một chuỗi vào stream. |
void write(String str, int off, int len) | Ghi một phần của chuỗi vào stream. |
Các phương thức cơ bản của character stream tổng quát
Lưu ý: Các phương thức đọc/ghi sẽ phát sinh IOException nếu có lỗi xảy ra.
1.3.2 Các luồng ký tự hiện thực
Để sử đọc/ghi ký tự, ta phải dùng các lớp con của Reader và Writer. Các lớp thường dùng gồm:
§ FileReader: đọc (các) ký tự từ tập tin.
§ FileWriter: ghi (các) ký tự vào tập tin.
§ CharArrayReader: chứa bộ đệm là mảng ký tự để đọc dữ liệu.
§ CharArrayWriter: chứa bộ đệm là mảng ký tự để ghi dữ liệu.
§ PipedReader: đọc (các) ký tự từ một piped writer.
§ PipedWriter: ghi (các) ký tự vào một piped reader.
§ StringReader: đọc chuỗi ký tự.
§ StringWriter: ghi chuỗi ký tự.
Nhìn chung các lớp này đều có những chức năng chính tương tự như nhau. Dưới đây chỉ trình bày những phương thức đặc thù của chúng.
Stream | Phương thức | Ý nghĩa |
File Reader | FileReader(File file) | Tạo FileReader để đọc dữ liệu từ một tập tin liên kết tới đối tượng File. |
FileReader(String name) | Tạo FileReader để đọc dữ liệu từ một tập tin có tên là name. | |
File Writer | FileWriter (File file) | Tạo FileWriter để ghi dữ liệu vào một tập tin liên kết tới đối tượng File. |
FileWriter(File file, boolean append) | Tạo FileWriter để ghi dữ liệu tập tin liên kết tới đối tượng File. Nếu append là true thì sẽ ghi tiếp vào cuối tập tin, ngược lại thì ghi đè lên tập tin. | |
FileWriter(String name) | Tạo FileWriter để ghi dữ liệu vào một tập tin có tên là name. | |
FileWriter(String name, boolean append) | Tạo FileWriter để ghi dữ liệu vào một tập tin có tên là name. Nếu append là true thì sẽ ghi tiếp vào cuối tập tin, ngược lại thì ghi đè lên tập tin. | |
Char Array Reader | CharArrayReader(char[] buf) | Tạo ra CharArrayReader và dùng buf để làm vùng đệm. |
CharArrayReader(char[] buf, int off, int len) | Tạo ra CharArrayReader và dùng một phần của buf để làm vùng đệm. | |
Char Array Writer | CharArrayWriter() | Tạo ra một CharArrayWriter. |
CharArrayWriter(int size) | Tạo ra một CharArrayWriter với vùng đệm size ký tự. | |
char[]toCharArray() | Tạo ra mảng ký tự là bản sao của vùng đệm của this. | |
String toString() | Tạo ra chuỗi là bản sao của vùng đệm của this với các byte được đổi thành ký tự tương ứng. | |
void writeTo(Writer out) | Ghi dữ liệu trong vùng đệm vào một writer khác. | |
Piped Reader | PipedReader() | Tạo ra một piped reader. Stream này chưa được kết nối với piped output stream nào. |
PipedReader
( PipedWriter src) | Tạo ra một piped reader kết nối với piped output stream src. | |
connect( PipedWriter src) | Kết nối tới piped writer src. | |
Piped Writer | PipedWriter() | Tạo ra một piped writer. Stream này chưa được kết nối với piped input stream nào. |
PipedWriter (PipedReader snk) | Tạo ra một piped writer kết nối với piped reader snk. | |
connect(PipedReader snk) | Kết nối tới piped reader snk. | |
String Reader | StringReader(String s) | Tạo ra một StringReader. |
String Writer | StringWriter() | Tạo ra một StringReader, dùng vùng đệm có kích thước mặc định. |
StringWriter(int initialSize) | Tạo ra một StringReader, dùng vùng đệm có kích thước là initialSize. | |
StringBuffer getBuffer() | Trả về vùng đệm của stream. |
Bảng 9: Những phương thức đặc trưng của các character stream cụ thể
1.3.3 Các ví dụ
Dùng file reader và file writer:
String fileName="C:\\TestC.txt";
String s="Hello File Reader/Writer!";
System.out.println("Du lieu ban dau : "+s);
// Ghi s vào tập tin
FileWriter fw=newFileWriter(fileName);
fw.write(s);
fw.close();
// Đọc tâp tin vào chuỗi sb
FileReader fr=newFileReader(fileName);
StringBuffer sb=newStringBuffer();
charca[]=new char[5]; // Đọc mỗi lần tối đa 5 ký tự
while(fr.ready())
{
int len=fr.read(ca); // len: số ký tự đọc được thật sự
sb.append(ca,0,len);
}
fr.close();
// Xuất chuỗi sb
System.out.println("Du lieu doc duoc : "+sb);
Kết quả thực thi:
Du lieu ban dau : Hello File Reader/Writer!
Du lieu doc duoc : Hello File Reader/Writer!
Press any key to continue...
Dùng piped reader và piped writer:
PipedReader pr=newPipedReader();
PipedWriter pw=newPipedWriter(pr);
// PipedWriter gởi dữ liệu
String s="Hello Piped Reader/Writer!";
System.out.println("Du lieu ghi : "+s);
pw.write(s);
pw.close();
// PipedReader nhận dữ liệu
StringBuffer sb=newStringBuffer();
charca[]=new char[5]; // Đọc mỗi lần tối đa 5 ký tự
while(pr.ready())
{
int len=pr.read(ca); // len: số ký tự đọc được thật sự
sb.append(ca,0,len);
}
pr.close();
// Kết quả
System.out.println("Du lieu doc duoc: "+sb);
Kết quả thực thi:
Du lieu ghi : Hello Piped Reader/Writer!
Du lieu doc duoc: Hello Piped Reader/Writer!
Press any key to continue...
1.4 Các luồng lọc dữ liệu (Filter Stream)
Bởi vì các luồng dữ liệu được chuyển tải dạng byte hoặc ký tự, nên cần có một bộ biến đổi các luồng dữ liệu này sang những giá trị có định kiểu khác nhau. Java cung cấp thêm các luồng lọc dữ liệu. Khi các luồng này được tạo ra, nó phải được gắn với một luồng dữ liệu cụ thể để lọc nó. Do vậy, việc đọc dữ liệu từ một nguồn là sự phối hợp giữa các luồng tạo thành một dây chuyền xử lý trên các luồng khi có thao tác đọc/ghi xảy ra.
· Dây chuyền đọc bắt đầu bởi một luồng đọc cơ bản và kết thúc bởi một luồng lọc dữ liệu. Ở giữa có thể có thểm các luồng xử lý đọc trung gian.
· Dây chuyền ghi bắt đầu bởi một luồng xử lý ghi và kết thúc bởi một luồng ghi cơ bản. Ở giữa có thể có các luồng xử lý ghi trung gian.
Ví dụ:
Ví dụ về dây chuyền đọc/ghi dữ liệu
1.4.1 Các luồng lọc tổng quát
Trong Java, các luồng xử lý lọc tổng quát là các lớp trừu tượng sau:
· FilerInputStream: luồng xử lý cho thao tác đọc dữ liệu dạng byte.
· FilerOutputStream: luồng xử lý cho thao tác ghi dữ liệu dạng byte.
· FilerReader: luồng xử lý cho thao tác đọc dữ liệu dạng ký tự.
· FilerWriter: luồng xử lý cho thao tác ghi dữ liệu dạng ký tự.
1.4.2 Các luồng lọc hiện thực
Các luồng lọc hiện thực các luồng lọc tổng quát chịu trách nhiệm hiện thực các thao tác biến đổi dữ liệu có định kiểu thành luồng byte/ký tự hoặc ngược lại từ luồng byte/ký tự thành giá trị có định kiểu. Cụ thể, các luồng lọc này là DataInputStream, DataOutputStream. Các lớp này có những phương thức đặc trưng như sau:
Stream | Phương thức | Ý nghĩa |
Data Input Stream | DataInputStream (InputStream in) | Tạo stream để đọc dữ liệu theo định dạng. |
boolean readBoolean() | Đọc một giá trị kiểu boolean. Các kiểu byte, char, double, float,... cũng có những phương thức tương ứng. | |
Data Output Stream | DataOutputStream (OutputStream out) | Tạo stream để ghi dữ liệu theo định dạng. |
int size() | Trả về số byte mà stream đã ghi. | |
void writeBoolean (boolean v) | Ghi một biến kiểu boolean. Các kiểu byte, char, double, float,... cũng có những phương thức tương ứng. | |
void writeBytes(String s) | Ghi chuỗi thành một dãy các byte. | |
void writeChars(String s) | Ghi chuỗi thành một dãy các ký tự. |
Lưu ý:
· Khi đọc các biến, phải đọc đúng theo thứ tự đã ghi.
· Để ghi chuỗi hoặc các đối tượng khác, ta phải dùng chức năng đọc/ghi object.
· Ta có thể kết hợp đọc có định dạng với vùng đệm.
Ví dụ: đọc/ghi các biến trên tập tin
String name="C:\\TestD.txt";
// Ghi dữ liệu
DataOutputStream fo=newDataOutputStream(new FileOutputStream(name));
fo.writeBoolean(true);
fo.writeInt(786);
fo.writeFloat(0.123f);
fo.close();
// Đọc dữ liệu
DataInputStream fi=newDataInputStream(new FileInputStream (name));
booleanb=fi.readBoolean();
inti=fi.readInt();
floatf=fi.readFloat();
fo.close();
// Xuất dữ liệu đọc được ra màn hình
System.out.println("Du lieu doc duoc: ");
System.out.println(String.valueOf(b));
System.out.println(i);
System.out.println(String.valueOf(f));
Kết quả thực thi:
Du lieu doc duoc:
true
786
0.123
Press any key to continue...
1.5 Các luồng đệm dữ liệu
Để tăng tốc độ đọc/ghi, nhất là đối với thao tác đọc/ghi trên bộ nhớ phụ, người ta dùng kỹ thuật vùng nhớ đệm.
· Vùng đệm đọc: dữ liệu sẽ được đọc vào vùng đệm thành từng khối, sau đó lấy ra dùng từ từ, khối hết thì sẽ đọc tiếp khối kế à giảm số thao tác đọc.
· Vùng đệm ghi: dữ liệu cần ghi sẽ được gom lại đến khi đủ số lượng cần thiết thì sẽ được ghi một lần à giảm số thao tác ghi.
Như vậy, vùng đệm càng lớn thì càng tăng tốc độ, nhưng sẽ càng dễ xảy ra mất dữ liệu khi chương trình bị chấm dứt đột ngột. Ví dụ: ta yêu cầu ghi nhưng do chưa đủ dữ liệu nên chương trình chưa ghi thật sự vào đĩa, sau đó bị cúp điện àcác dữ liệu được ta yêu cầu ghi đó sẽ mất.
Trong Java, các lớp luồng đệm hỗ trợ thao tác đọc/ghi như:
· BufferedInputStream: đọc dữ liệu dạng byte, có dùng vùng đệm
· BufferedOutputStream: ghi dữ liệu dạng byte, có dùng vùng đệm
· BufferedReader: đọc dữ liệu dạng ký tự, có dùng vùng đệm
· BufferedWriter: ghi dữ liệu dạng ký tự, có dùng vùng đệm
Khi tạo ra một đối tượng luồng đệm, nó phải gắn với một đối tượng luồng dữ liệu khác. Nhìn chung các luồng đệm có những phương thức giống như luồng thông thường, nhưng có khác biệt về cách khởi tạo:
Stream | Phương thức | Ý nghĩa |
Buffered Input Stream | BufferedInputStream (InputStream in) | Tạo một stream vùng đệm để đọc dữ liệu dạng byte. |
BufferedInputStream (InputStream in, int size) | Tạo một stream với vùng đệm kích thước size để để đọc dữ liệu dạng byte. | |
Buffered Output Stream | BufferedOutputStream (OutputStream out) | Tạo một stream vùng đệm để ghi dữ liệu dạng byte. |
BufferedOutputStream (OutputStream out, int sz) | Tạo một stream với vùng đệm kích thước sz byte để để ghi dữ liệu dạng byte. | |
Buffered Reader | BufferedReader(Reader in) | Tạo một stream vùng đệm để đọc dữ liệu dạng ký tự. |
BufferedReader (Reader in, int sz) | Tạo một stream với vùng đệm kích thước sz để để đọc dữ liệu dạng ký tự. | |
Buffered Writer | BufferedWriter(Writer out) | Tạo một stream vùng đệm để ghi dữ liệu dạng ký tự. |
BufferedWriter (Writer out, int sz) | Tạo một stream với vùng đệm kích thước sz byte để để ghi dữ liệu dạng byte. | |
void newLine() | Ghi ký tự xuống dòng vào stream. |
Bảng 10: Những phương thức đặc trưng của các stream vùng đệm
Ví dụ:
String fileName="C:\\TestC.txt";
String s="Hello Buffered File Reader/Writer!";
System.out.println("Du lieu ban dau : "+s);
// Ghi s vào tập tin
BufferedWriter bw=newBufferedWriter(new FileWriter(fileName));
bw.write(s);
bw.close();
// Đọc tâp tin vào chuỗi sb
BufferedReader br=newBufferedReader(new FileReader(fileName));
StringBuffer sb=newStringBuffer();
charca[]=new char[5]; // Đọc mỗi lần tối đa 5 ký tự
while(br.ready())
{
int len=br.read(ca); // len: số ký tự đọc được thật sự
sb.append(ca,0,len);
}
br.close();
// Xuất chuỗi sb
System.out.println("Du lieu doc duoc : "+sb);
Kết quả:
Du lieu ban dau : Hello Buffered File Reader/Writer!
Du lieu doc duoc : Hello Buffered File Reader/Writer!
Press any key to continue...
Nhìn vào ví dụ trên, ta thấy dùng stream vùng đệm và không dùng cho kết quả không khác gì nhau. Vậy đâu là ưu điểm của stream vùng đệm? Hãy xem ví dụ kế tiếp.
Ví dụ: dưới đây là ví dụ so sánh tốc độ khi dùng và không dùng stream vùng đệm:
String fileName="C:\\TestB.txt";
longn=50000;
// Ghi với stream vùng đệm
{
long t=System.currentTimeMillis();
FileOutputStream fo=newFileOutputStream(fileName);
BufferedOutputStream bo=newBufferedOutputStream(fo);
for(inti=0;i<n;i++)
bo.write(i);
bo.close();
t=System.currentTimeMillis()-t;
System.out.println("Ghi co vung dem : mat "+t+"ms.");
}
// Ghi không có stream vùng đệm
{
long t=System.currentTimeMillis();
FileOutputStream fo=newFileOutputStream(fileName);
for(inti=0;i<n;i++)
fo.write(i);
fo.close();
t=System.currentTimeMillis()-t;
System.out.println("Ghi khong vung dem: mat "+t+"ms.");
}
Kết quả thực thi: thời gian chạy rất chênh lệch (kết quả có thể khác nhau tùy máy)
Ghi co vung dem : mat 15ms.
Ghi khong vung dem: mat 1703ms.
Press any key to continue...
Ví dụ: kết hợp đọc/ghi có định dạng trên tập tin, có dùng vùng đệm
String fileName="C:\\TestB.txt";
// Ghi các số từ 0 tới 9 và căn bậc 2 tương ứng của chúng
// Tạo các stream
FileOutputStream fo=newFileOutputStream(fileName);
BufferedOutputStream bo=newBufferedOutputStream(fo);
DataOutputStream dos=newDataOutputStream(bo);
// Ghi các số
for(inti=0;i<10;i++)
{
dos.writeInt(i);
dos.writeDouble(Math.sqrt(i));
}
dos.close();
/* Đọc các số nguyên và căn bậc hai tương ứng */
// Tạo các stream
FileInputStream fi=newFileInputStream(fileName);
BufferedInputStream bi=newBufferedInputStream(fi);
DataInputStream dis=newDataInputStream(bi);
// Đọc các số và xuất ra màn hình
while(dis.available()>0)
{
int i=dis.readInt();
double sinI=dis.readDouble();
System.out.println("sqrt("+i+")"+" = "+sinI);
}
dis.close();
Kết quả thực thi:
sqrt(0) = 0.0
sqrt(1) = 1.0
sqrt(2) = 1.4142135623730951
sqrt(3) = 1.7320508075688772
sqrt(4) = 2.0
sqrt(5) = 2.23606797749979
sqrt(6) = 2.449489742783178
sqrt(7) = 2.6457513110645907
sqrt(8) = 2.8284271247461903
sqrt(9) = 3.0
Press any key to continue...
1.6 Các lớp định dạng luồng dữ liệu nhập/xuất
Java hỗ trợ hai lớp đối tượng Scanner (thuộc gói java.util.*) và PrintWriter (thuộc gói java.io.*) rất hiệu quả cho việc định dạng dữ liệu của một luồng nhập/xuất. Bảng đưới đây trình bày một số phương phức thông dụng của hai lớp đối tượng này.
Phương thức | Ý nghĩa |
Lớp Scanner | |
Scanner(InputStream source) | Khởi tạo đối tượng Scanner lấy dữ liệu từ một luồng nhập. |
Scanner(ReadableByteChannel source) | Khởi tạo đối tượng Scanner lấy dữ liệu từ một kênh nhập. |
void close() | Đóng đối tượng Scanner. |
bool nextBoolean() | Đọc một khối dữ liệu trong luồng và ép nó sang kiểu logic bool. |
int nextInt() | Đọc một khối dữ liệu trong luồng và ép nó sang kiểu số nguyên int. |
long nextLong() | Đọc một khối dữ liệu trong luồng và ép nó sang kiểu số nguyên long. |
float nextFloat() | Đọc một khối dữ liệu trong luồng và ép nó sang kiểu số thực float. |
double nextDouble() | Đọc một khối dữ liệu trong luồng và ép nó sang kiểu số thực double. |
String nextLine() | Đọc các khối dữ liệu trong luồng và ép nó sang kiểu chuỗi String. |
bool hasNext() | kiểm tra luồng còn khối dữ liệu nào không. |
Lớp PrintWriter | |
PrintWriter(OutputStream out) | Khởi tạo đối tượng PrintWriter để viết dữ liệu ra luồng xuất byte out. |
PrintWriter(Writer out) | Khởi tạo đối tượng PrintWriter để viết dữ liệu ra luồng xuất ký tự out. |
void close() | Đóng đối tượng PrintWriter. |
void print(String s) | Ghi một chuỗi ra luồng. |
void print(bool b) | Ghi giá trị bool ra luồng. |
void println() | Ghi ký hiệu kết thúc dòng ra luồng |
void println(int i) | Ghi một số nguyên i và ký hiệu kết thúc dòng ra luồng. |
void println(String s) | Ghi một chuỗi s và ký hiệu kết thúc dòng ra luồng. |
void flush() | Ghi toàn bộ dữ liệu trong PrintWriter ra luồng. |
Ví dụ: Đọc và ghi dữ liệu có định dạng từ hai luồng nhập xuất cơ bản System.in và System.out
import java.util.*;
import java.io.*;
public class NhapXuat {
public static void main(String[] args) {
//hien thi day so vua nhap len man hinh su dung PrintWriter
PrintWriter pw=new PrintWriter(System.out);
//Tao doi tuong Scanner de dinh dang du lieu cho luong
//nhap ban phim
Scanner sc=new Scanner(System.in);
//doc so luong phan tu day so can nhap
pw.println("nhap so phan tu n:"); pw.flush();
int n =sc.nextInt();
//doc vao mot day so n nguyen luu vao mang
int a[] =new int[n];
for(int i=0;i<n;i++){
pw.println("a[" + i+"]=");pw.flush();
a[i]=sc.nextInt();
}
pw.println("Hien thi day so su dung doi tuong Printer");
for(int i=0;i<n;i++)
pw.print(a[i]);
pw.close();
}
}
Kết quả thực hiện
nhap so phan tu n:
3
a[0]=1
a[1]=2
a[2]=3
Hien thi day so su dung doi tuong Printer
1
2
3
1.7 Tuần tự hóa đối tượng (object serialization)
Object serialization là khả năng biến đổi một đối tượng thành một dãy byte để lưu trữ trên bộ nhớ phụ hoặc truyền đi nơi khác. Trong Java, object serialization đã được tự động hóa hoàn toàn và hầu như lập trình viên không phải làm gì thêm.
Để một đối tượng có thể được "serialize", ta chỉ cần cho nó implement interface Serializable. Mọi việc sau đó như đọc/ghi/truyền/nhận đều do Java thực hiện.
Lưu ý:
· Chỉ có các thuộc tính (dữ liệu) của đối tượng mới được serialize.
· Các thuộc tính được đánh dấu bằng từ khóa transient (nghĩa là có tính tạm thời) sẽ không được serialize.
· Sau khi serialize, trạng thái của đối tượng được lưu trữ trên bộ nhớ ngoài được gọi là persistence (nghĩa là được giữ lại một cách bền vững).
Ví dụ: tạo ra lớp học sinh có thể được serialize:
public class HocSinh implements Serializable
{
protected String hoTen;
protected int namSinh;
protected float diemVan, diemToan;
protected transientfloat diemTrungBinh;
// ...
}
1.8 Luồng viết đối tượng
Trong Java, các lớp đảm nhận việc đọc/ghi đối tượng gồm:
· ObjectInputStream: đọc dãy byte và chuyển thành đối tượng phù hợp.
· ObjectOutputStream: chuyển đối tượng thành dãy byte và ghi.
Các đối tượng đọc/ghi này cũng không thể hoạt động độc lập mà phải được liên kết với stream khác. Chúng có các phương thức chính như sau:
Stream | Phương thức | Ý nghĩa |
Object Input Stream | ObjectInputStream (InputStream in) | Tạo stream để đọc đối tượng. |
Object readObject() | Đọc một đối tượng.Nếu đối tượng không được khai báo trong chương trình thì sẽ phát sinh ClassNotFoundException. | |
Object Output Stream | ObjectOutputStream (OutputStream out) | Tạo stream để ghi đối tượng. |
void reset() | Phục hồi lại stream và hủy bỏ thông tin về các đối tượng mà stream đã ghi. | |
writeObject(Object obj) | Ghi một đối tượng. |
Bảng 11: Những thao tác đặc trưng của các object stream
Tạo ra lớp phân số có thể được serialize:
public class PhanSo implements Serializable
{
protected int tu,mau;
private transient float val=0;
publicPhanSo()
{
tu=0;
mau=1;
}
publicPhanSo(inttu, intmau)
{
this.tu=tu;
this.mau=mau;
val=(float)tu/mau;
}
public String toString()
{
return tu+"/"+mau+" ("+val+")";
}
}
Ghi và đọc các đối tượng phân số (có dùng stream vùng đệm):
// Ghi dữ liệu
FileOutputStream fo=newFileOutputStream("C:\\TestO.bin");
BufferedOutputStream bo=newBufferedOutputStream(fo);
ObjectOutputStream oo=newObjectOutputStream(bo);
System.out.println("Du lieu ghi duoc:");
for(inti=0;i<4;i++)
{
int tu =(int)(Math.random()*10);
intmau=(int)(Math.random()*9)+1;
PhanSo ps=newPhanSo(tu,mau);
oo.writeObject(ps);
System.out.println(ps);
}
oo.close();
// Đọc dữ liệu
FileInputStream fi=newFileInputStream("C:\\TestO.bin");
BufferedInputStream bi=newBufferedInputStream(fi);
ObjectInputStream oi=newObjectInputStream(bi);
System.out.println("\nDu lieu doc duoc:");
while(bi.available()>0)
{
PhanSo ps=(PhanSo)oi.readObject();
System.out.println(ps);
}
oi.close();
Kết quả thực thi có dạng như sau (kết quả thực tế sẽ khác nhau với mỗi lần chạy):
Du lieu ghi duoc:
2/2 (1.0)
6/8 (0.75)
5/6 (0.8333333)
3/5 (0.6)
Du lieu doc duoc:
2/2 (0.0)
6/8 (0.0)
5/6 (0.0)
3/5 (0.0)
Press any key to continue...
Ta thấy thuộc tính valkhông được ghi do nó là transient. Nếu trong lớp phân số, ta bỏ từ khóa transient thì kết quả sẽ có dạng như sau:
Du lieu ghi duoc:
2/6 (0.33333334)
3/7 (0.42857143)
2/1 (2.0)
2/4 (0.5)
Du lieu doc duoc:
2/6 (0.33333334)
3/7 (0.42857143)
2/1 (2.0)
2/4 (0.5)
Press any key to continue...
1.9 Bài tập
1. Viết chương trình cài đặt tất cả các ví dụ chong chương.
2. Cho biết các loại lớp biểu diễn luồng dữ liệu trong Java. Nêu tính năng chính của từng loại.
Nội Quy Khi Gửi Bình Luận: