|
|
|
Phần mềm : |
 |
|
Trình độ : Beginer |
| Đánh giá : |
 |
/ |
 |
|
|
| Thao tác với các tệp dữ liệu với bằng Java |
| (Thứ Tư, 10/03/2010-1:21 PM) |
|
|
 |
|
Java cung cấp một giao lập trình ứng dụng (API) chuẩn hoá, đơn giản để thực hiện các thao tác đọc/ghi tới như file, database và socket - API I/O. Bài này sẽ hướng dẫn bạn cách tiếp cận có hiệu quả trong việc đọc một lượng dữ liệu lớn trong trường hợp cần phải cân nhắc kỹ các các yếu tố như thời gian và cấp phát bộ nhớ để cải thiện hiệu nǎng tổng thể của hệ thống. |
|
|
|
Bài này sẽ
hướng dẫn bạn cách tiếp cận có hiệu quả trong việc đọc một lượng dữ
liệu lớn trong trường hợp cần phải cân nhắc kỹ các các yếu tố như thời
gian và cấp phát bộ nhớ để cải thiện hiệu nǎng tổng thể của hệ thống.
Cách
tốt nhất để bắt đầu là chúng ta hãy xem xét một thí dụ cụ thể. Giả sử
bạn cần đọc dữ liệu lớn từ một tệp nhị phân kích thước lớn và lưu vào
một mảng để tiếp tục xử lý. Các thao tác I/O của Java đều dựa trên các
stream (dòng), tức một chuỗi tuần tự các byte dữ liệu. Đầu tiên, bạn
phải chọn loại stream. Chúng ta làm việc với các tệp nhị phân, vì vậy
cần phải sử dụng lớp FileInputStream. Bạn có thể sử dụng lớp FileReader
khi làm việc với các stream dữ liệu ký tự. Chúng ta có thể mở một kết
nối (connection) tới tệp dữ liệu theo cách sau:
InputStream in = new FileInputStream (fileName);
Tại
thời điểm này, bạn đã có thể đọc dữ liệu từ tệp fileName. Tuy nhiên,
với ưu tiên về hiệu nǎng, chúng ta hãy xem xét một lớp khác từ gói
(package) java.io - BufferedInputStream. Đây là lớp bọc (wrapper) của
các stream nhập, nó cho phép lưu vào bộ đệm (buffering) các dữ liệu đầu
vào của các stream để cải thiện tiến trình đọc. Bạn có thể kết nối với
tệp như dưới đây:
InputStream is = new BufferedInputStream (new FileInputStream (fileName));
Khi
đã thiết lập được kết nối tới tệp, bạn cũng có thể bắt đầu đọc nội dung
của nó. Lớp InputStream có hai phương thức chính cho việc đọc dữ liệu:
int read() và int read (byte[] b, int off, int len). Phương thức thứ
nhất chỉ đọc một byte dữ liệu một lần. Trái lại, phương thức thứ hai
đọc nhiều byte một lúc (xác định theo tham số len) từ stream vào một
mảng. Phương thức thứ hai có hiệu quả cao hơn và chúng ta có thể sử
dụng nó trong Listing1.
Listing1
/** * Đọc tệp và lưu dữ liệu vào mảng. * Tham số file: tệp dữ liệu cần đọc * public byte[] read2array(String file) throws Exception { InputStream in = null; byte[] out = new byte[0]; try{ in = new BufferedInputStream(new FileInputStream(file)); // the length of a buffer can vary int bufLen = 20000*1024; byte[] buf = new byte[bufLen]; byte[] tmp = null; int len = 0; while((len = in.read(buf,0,bufLen)) != -1){ // mở rộng mảng tmp = new byte[out.length + len]; // copy dữ liệu System.arraycopy(out,0,tmp,0,out.length); System.arraycopy(buf,0,tmp,out.length,len); out = tmp; tmp = null; } }finally{ //đóng stream if (in != null) try{ in.close();}catch (Exception e){} } return out; }
Có
một số khía cạnh rất đáng quan tâm trong thí dụ này. Trước tiên, vì tệp
có kích thước lớn, chúng ta cần một bộ nhớ đệm cũng khá lớn (20Mb) khi
gọi phương thức đọc. Bộ nhớ đệm càng lớn, đọc dữ liệu sẽ càng nhanh.
Trên thực tế, có những trường hợp ta không thể biết trước số byte cần
đọc từ một stream nhập nên cần phải cấp phát bộ một dung lượng cố định
ban đầu cho bộ nhớ đệm. Điều này có thể giải quyết được bằng cách sử
dụng các phương thức thích hợp.
Tuy nhiên, phương thức này không
phải lúc nào cũng cho kết quả mong muốn và có thể sinh ra các biệt lệ
(exception). Biệt lệ có thể nảy sinh khi đọc các dữ liệu kiểu long hoặc
BLOB qua stream. Thứ hai, vì được khởi tạo ở bên ngoài vòng while, các
mảng out, buf và tmp có thể được sử dụng lại và hạn chế đối tượng cần
phải giải phóng ở thường trình thu nhặt rác. Thứ ba, khi bộ đệm đầy, nó
được copy vào một mảng có khả nǎng mở rộng kích thước bằng cách gọi
phương thức System.arraycopy. Mặc dù thuật toán này khá hiệu quả nhưng
tất cả các vòng lặp read đều tạo ra các mảng tạm thời và cần thực hiện
2 thao tác copy mảng trong một lần lặp.
Bạn có thể giảm thao tác copy dữ liệu và bộ nhớ cho mảng bằng cách thay đổi vòng lặp while như Thí dụ 2 dưới đây.
Listing2
/** * Đọc tệp và lưu dữ liệu vào một danh sách. Phương pháp nhanh * Tham số file: tệp dữ liệu cần đọc * */ public byte[] read2list(String file) throws Exception { InputStream in = null; byte[] buf = null; // output buffer int bufLen = 20000*1024; try{ in = new BufferedInputStream(new FileInputStream(file)); buf = new byte[bufLen]; byte[] tmp = null; int len = 0; List data = new ArrayList(24); // lưu "các mảnh" dữ liệu while((len = in.read(buf,0,bufLen)) != -1){ tmp = new byte[len]; System.arraycopy(buf,0,tmp,0,len); // vẫn cần copy data.add(tmp); } /*
Đoạn này có thể tuỳ chọn. Phương pháp này có thể trả về một List dữ liệuđể xử lý sau này
*/ len = 0; if (data.size() == 1) return (byte[]) data.get(0); for (int i=0;i<data.size();i++) len += ((byte[]) data.get(i)).length; buf = new byte[len]; // bộ đệm xuất cuối cùng len = 0; for (int i=0;i<data.size();i++){ // điền dữ liệu tmp = (byte[]) data.get(i); System.arraycopy(tmp,0,buf,len,tmp.length); len += tmp.length; } }finally{ if (in != null) try{ in.close();}catch (Exception e){} } return buf;
Ở đây, thay vì lưu dữ liệu ngay vào một mảng lớn và mở rộng mảng này vào
những lúc dữ liệu được thu về, chúng ta duy trì một danh sách với các
phần tử là các "mảnh" dữ liệu. Khi đã đến cuối stream, dữ liệu được lấy
ra từ danh sách và điền vào một mảng. Điều này cho phép bạn chỉ cần sử
dụng tới một mảng và thực hiện một thao tác copy dữ liệu. Nếu không cần
kết quả trả về dưới dạng mảng, bạn có thể trả về danh sách để tiết kiệm
thời gian và tài nguyên. Đọc dữ liệu sử dụng thuật toán này nhanh hơn
đáng để so với phương pháp thứ nhất. Sự khác biệt trong tốc độ phụ
thuộc vào kích thước mảng đệm sử dụng trong phương thức đọc.
Theo ITG - Java Group |
|
|
|
|
|
| Các bài trước |
|
|
| Các bài sau |
|
|
|
|
|
|
|