Java

Java NIO qua một vài ví dụ

Java quá chậm, “biết rồi khổ lắm nói mãi”, đây là một trong những điểm yếu lớn trên một vài công nghệ của Java và I/O là một ví dụ điển hình. Là một nhà phát triển ứng dụng, bạn luôn phải đau đầu với những kêu ca từ khách hàng khi quá trình đọc và ghi tập tin của ứng dụng thực thi rất chậm . Hãy quên ngay điều đó , hãy từ bỏ các kỹ thuật truy xuất thông thường để tìm hiểu về NIO. Bạn có thể quản lí , kiểm soát bộ nhớ đệm ; tăng khả năng , tốc độ làm việc với Network , với các tập tin ; chuyển đổi các định dạng, tìm kiếm , mã hóa và giải mã dữ liệu ;…Sử dụng Java NIO API bạn có thể viết các ứng dụng tìm kiếm dữ liệu trên đĩa cứng, đọc và ghi tập tin, chuyển dữ liệu qua mạng, viết các HTTP Server …

Đây là những lời “quảng cáo khiêm tốn” của tôi về Java NIO trước khi bắt đầu bài viết của mình. Hãy cùng tôi khám phá những ưu điểm nổi trội của NIO so với các kỹ thuật IO cũ qua một vài ví dụ nhỏ. Trước tiên chúng ta hãy cùng xem:

Java NIO là gì ?

Bắt đầu tiếng tăm bằng Applet, một kỹ thuật cho phép tạo các ứng dụng chạy trên các trang HTML đã đánh dấu một bước tiến mới trong việc tạo ra các website tương tác. Nhưng khoảng thời gian sau đó, sự phát triển không ngừng của công nghệ đã đẩy Applet trở thành một công nghệ không mấy được ưa chuộng hiện nay, thay vào đó sẽ là các ngôn ngữ script, DHTML, Flash.Tuy nhiên, thế mạnh mới của Java đã được khơi dậy trong những lĩnh vực khác, đặt biệt là các ứng dụng mạng. Kiến trúc J2EE hình thành và một loạt các công nghệ kèm theo nó đã đẩy Java trở thành một ngôn ngữ thông dụng nhất hiện nay. Trong khi Internet ngày một phát triển, các ứng dụng chuyển dần sang dạng client–server, nhu cầu về xây dựng các công nghệ mạng trở nên cần thiết hơn bao giờ hết.
Theo đó Networking trong Java được đầu tư và trở thành điểm nổi trội nhất nhất mà Java có được để lôi kéo một cộng đồng các lớn các nhà phát triển phần mềm.

Nói đến Networking thì không thể né tránh Input/Output(I/O). Tuy nhiên các kỹ thuật I/O cũ trong JDK trở lên quá chậm. Làm sao để Tomcat, JRUN, JBoss, có thể xử lí nhanh hàng ngàn request được gửi đến trong một thời gian ngắn. Làm sao để các ứng dụng Client-Server có thể chuyển một lượng lớn các gói tin qua mạng. Và kết quả cuối cùng chúng ta có Java NIO. NIO là viết tắt của cụm từ New Input/Output, chính thức ra đời từ Java 1.4.0. Đây là thành quả đề xuất của Java Specification Request(JSR) 51, JSR là nơi chứa những đề xuất kỹ thuật mới trong nền tảng Java. Đầu tiên, chúng ta lướt qua các thành phần trong gói java.io cùng 4 gói nhỏ trong nó là java.nio.channels, java.nio.channels.spi, java.nio.charset và java.nio.charset.
Gói Java.nio : Gói này chứa các lớp buffer cung cấp các kỹ thuật để lưu trữ dữ liệu ở dạng nguyên thủy trong bộ nhớ trong.
Gói Java.nio.channels : Định nghĩa các kênh thay cho các liên kết tới các thực thể có khả năng thực hiện các thao tác truy xuất. Các thực thể có thể là các tập tin, socket hay các selector.
Gói java.nio.channels.spi : Chứa các lớp cung cấp các dịch vụ cho gói java.nio.channels.
Gói java.nio.charset : Chứa các charset, mã hóa, giải mã dữ liệu, các lớp chuyển đối dữ liệu.
Gói java.nio.charset.spi : Chứa các lớp cung cấp các dịch vụ cho gói java.nio.charset.
Đi sâu vào toàn bộ kiến trúc, chức năng, cách thức làm việc, của các lớp trong các gói NIO không nằm trong khuôn khổ bài viết này.
Để tìm hiểu thêm các thông tin đó bạn có thể tra trực tiếp chúng trên API Document hay tham khảo cuốn Java NIO của nhà xuất bản Oreilly. Ở đây tôi xin giới thiệu một vài đoạn code nhỏ minh họa cho việc cài đặt Java NIO. Và phần một của bài viết này sẽ tập trung vào nghiệp vụ xử lí cho các ứng dụng Desktop thường gặp.

Vài thao tác với tập tin.

Trong việc xây dựng các ứng dụng desktop, chắc chắn bạn sẽ thường xuyên gặp phải bài toán lưu hay đọc dữ liệu từ một tập tin. Đối với các kỹ thuật java io thông thường, công việc đó sẽ được thực thi rất chậm, đặc biệt là các tập tin dung lượng lớn hay các tập tin ảnh.

Đọc một tập tin

Đoạn mã sau đây cho phép đọc một tập tin trong Java NIO giúp khắc phục được yếu điểm trên :

File file = new File("C:" +File.separator+"sample.htm");
FileInputStream f_in = new FileInputStream(file);
FileChannel fchan = f_in.getChannel();
long fsize = fchan.size();
ByteBuffer buff = ByteBuffer.allocate((int)fsize);
fchan.read(buff);
buff.rewind();
String str = new String(buff.array());
editor.setText(str);
fchan.close();
f_in.close();

Đầu tiên tôi khai báo một tập tin, sau đó đưa tập tin này vào một InputStream dưới dạng FileInputStream. Lớp FileInputStream có phương thức getChannel() trả về một đối tượng FileChannel tạo thành một “kênh” cho phép đọc, ghi, ánh xạ hay quản lí dữ liệu trên tập tin đó. Chú ý: có thể lấy ra một đối tượng FileChannel từ các lớp FileInputStream, FileOutputStream hay RandomAccessFile thông quan phương thức getChannel(). Sau đó, một đối tượng thuộc lớp ByteBuffer được khai báo cấp phát một dung lượng bộ nhớ bằng kích thước tập tin. Sau khi đọc dữ liệu, một đối tượng string sẽ được tạo từ dữ liệu này và cuối cùng đặt string này vào một editor. Để đọc dữ liệu ảnh bạn cũng có thể làm tương tự, sau khi đọc dữ liệu vào bộ nhớ bạn tạo một đối tượng thuộc lớp ImageIcon như sau:

ImageIcon icon = new ImageIcon(buff.array())

Và bây giờ bạn có thể thao tác trên đối tượng Icon này.

Ghi một tập tin

Để lưu trữ dữ liệu, bạn có thể thực hiện các bước như đoạn mã dưới đây :

FileOutputStream fout = new FileOutputStream(path);
FileChannel fchan = fout.getChannel();
String content = editor.getText();
byte[] data = content.getBytes();
ByteBuffer buff = ByteBuffer.allocateDirect( data.length);
for(int i=0; i<data.length; i++){
	buff.put(data[i]);
}
buff.rewind();
fchan.write(buff);
fchan.close();
fout.close();

Tạo một OutputStream dưới dạng FileOutputStream truyền đường dẫn tập tin dữ liệu sẽ được ghi là path, sau đó cũng tạo một kênh riêng để thực hiện các thao tác trên tập tin đó. Lấy dữ liệu dưới dạng string từ editor sau đó chuyển chúng về dạng nguyên thủy byte. Khai báo một đối tượng ByteBuffer và đặt dữ liệu đó vào, cuối cùng đối tượng thuộc lớp FileChannel thực hiện việc ghi dữ liệu đó xuống. Ánh xạ tới một tập tin được thực hiện qua lớp MapptedByteBuffer. Lớp này chứa dữ liệu dưới dạng byte trong một buffer đã được ánh xạ tới tập tin. Sau đây là đoạn mã minh họa :

FileInputStream input = new FileInputStream( path);
FileChannel channel = input.getChannel();
int length = (int)channel.size();
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, length);

Chú ý: Trạng thái ánh xạ MapMode có 3 trạng thái: chỉ đọc (READ_ONLY), bảo vệ (PRIVATE) hay được phép cả ghi lẫn đọc(READ_WRITE). Một khi lớp MappedByteBuffer được tạo, bạn có thể truy nhập giống như bất cứ lớp Buffer nào khác trong gói này. Để chuyển đổi giữa các định dạng buffer khác nhau bạn có thể làm như sau:

Charset charset = Charset.forName("UTF-8");
CharsetDecoder decoder = charset.newDecoder();
CharBuffer charBuffer = decoder.decode(buffer);

Lock một tập tin

Để lock(khóa) một tập tin bạn làm như sau :

File file = new File(path);
RandomAccessFile raf = new RandomAccessFile(file, "rw");
FileChannel channel = raf.getChannel();
FileLock lock = channel.lock(0, Long.MAX_VALUE, true);

Tìm kiếm nội dung trong một tập tin

Đoạn mã được cài đặt dưới đây sẽ là một ví dụ:

Charset charset = Charset.forName("utf-8");
Pattern pattern = Pattern.compile("thu", Pattern.CASE_INSENSITIVE);

File file = new File("C:\\Example\\NIO\\test.txt");
FileInputStream stream = new FileInputStream(file);
FileChannel f = stream.getChannel();
ByteBuffer bytes = f.map(FileChannel.MapMode.READ_ONLY, 0, f.size( ));
stream.close( );
CharBuffer chars = charset.decode(bytes);

Matcher matcher = pattern.matcher(chars);
if (matcher.find()) {
	System.out.println("Da tim thay o vi tri "+Integer.toString(matcher.start()));
} else {
	System.out.println("Khong tim thay");
}

Dòng đầu tiên cho phép bạn tạo một định dạng charset là utf-8. Dòng tiếp theo cho phép bạn tạo ra một mẫu tìm không phân biệt chữ hoa chữ thường. Tiếp đó là khai báo một file, mở luồng và đọc dữ liệu. Sau đó dữ liệu này được decode theo định dạng utf-8 ở trên. Cuối cùng một đối tượng thuộc lớp Matcher được tạo ra cho phép tìm kiếm trong phương thúc find() của nó. Hàm start() của lớp Matcher trả về vị trí bắt đầu trong tập tin của mẫu tìm kiếm Pattern. Với đoạn mã này bạn có thể phát triển thành một ứng dụng nhỏ cho phép tìm kiếm nội dung tập tin trên đĩa cứng.

Dưới đây là một vài đoạn code nhỏ có thể sẽ hữu ích cho bạn trong quá trình phát triển các ứng dụng:

Copy File

File src = new File(from);
File des = new File(to);
FileChannel srcChannel = new FileInputStream(from).getChannel();
FileChannel desChannel = new FileOutputStream(to).getChannel();
srcChannel.transferTo(0, srcChannel.size(), desChannel);
srcChannel.close();
desChannel.close();

Ghi thêm dữ liệu vào file:

FileOutputStream output = new FileOutputStream(file, true);
FileChannel fchan = output.getChannel();
ByteBuffer buff = ByteBuffer.allocateDirect(data.length);
buff.put(data);
buff.rewind();
if(fchan.isOpen()) {
	fchan.write(buff);
}
buff.clear();
fchan.close();
output.close();

Bạn chú ý là FileOutputStream được khởi tạo có truyền thêm một thông số là giá trị boolean cho phép append data vào file hay không?

Tài liệu tham khảo cho bài viết :
Cuốn Java InstantCode: Developing Applications Using Java NIO – nhà xuất bản SkillSoft Press.
Bài viết về Java New I/O tại http://java.sun.com/developer/technicalArticles/releases/nio/.

From http://blog.vietspider.org/

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s