Lập trình mạng với Java - Chương 3: Các luồng vào ra
Khi lập bất kỳchương trình nào trong một ngôn ngữnào thì vấn đềvào ra dữliệu giữa
chương trình và nguồn dữliệu cũng như đích dữliệu là vấn đềmà người lập trình cần phải
quan tâm. Làm thếnào đểta có thểtruyền dữliệu cho một chương trình Java. Có hai cách
hiệu quả đểthực hiện điều này:
•Thông qua một tài nguyên tuần tựnào đó nhưfile hoặc qua một máy tính khác.
• Thông qua giao diện người máy.
Mục đích của chương này là xem xét cách truyền dữliệu cho một chương trình thông
qua một máy tính khác hay tập tin.
Phương thức put()được định nghĩa trong ByteBuffer. Tất cả các lớp buffer còn hỗ trợ các phương thức thực hiện các thao tác khác nhau trên vùng đệm. 6.3. Các kênh (Channel) Các kênh được định nghĩa trong gói java.io.channel. Một kênh biểu diễn một liên kết mở tới một nguồn hoặc đích vào ra. Ta có thể nhận được một kênh bằng cách gọi phương thức getChannel() trên một đối tượng hỗ trợ kênh. Java 2 phiên bản 1.4 đưa thêm vào phương thức getChannel() cho các lớp sau: • FileInputStream • FileOutputStream • RandomAccessFile • Socket • ServerSocket • DatagramSocket Để nhận được một kênh, trước tiên ta phải nhận một đối tượng của các lớp này và sau đó gọi phương thức getChannel() trên đối tượng đó. Kiểu kênh cụ thể được trả về phụ thuộc vào kiểu đối tượng chịu tác động của phương thưc getChannel(). Ví dụ khi gọi phương thức getChannel() trên đối tượng FileInputStream, FileOutputStream hoặc RandomFileAccess thì kênh trả về là FileChannel. Khi gọi phương thức getChannel() trên đối tượng Socket thì kiểu kênh trả về là SocketChannel(). Các kênh FileChannel và SocketChannel hỗ trợ các phương thức read() và write() cho phép ta thực hiện các thao tác vào ra thông qua kênh. Dưới đây là một số phương thức read() và write()được định nghĩa trong FileChannel. Phương thức Mô tả abstract int read(ByteBuffer bb) Đọc các byte từ kênh vào một vùng đệm bb cho tới khi đầy vùng đệm hoặc không còn dữ liệu trên kênh. Kiểu trả về là số byte thực sự đọc được abstract int read(ByteBuffer bb, long start) Đọc các byte từ kênh vào một vùng đệm, bắt đầu từ vị trí start cho tới khi đầy vùng đệm hoặc không còn dữ liệu đầu vào. Vị trí hiện thời không thay đổi. Trả về số byte đã đọc được hoặc –1 nếu kết thúc luồng abstract int write(ByteBuffer bb) Ghi nội dung của vùng đệm ra kênh, bắt đầu tại vị trí hiện hành. Trả về số byte đã được ghi. abtsract int write(ByteBuffer bb, int start) Ghi nội dung của vùng đệm ra kênh. Bắt đầu tại vị trí hiện start. Trả về số byte đã được ghi Bảng 3.5 Tất cả các kênh đều hỗ trợ các phương thức bổ trợ cho phép ta truy xuất và điều khiển kênh. Ví dụ, FileChannel hỗ trợ các phương thức để nhận và thiết lập vị trí hiện hành, truyền thông tin qua lại giữa các kênh, nhận kích thước hiện thời của kênh, khóa kênh,..FileChannel cũng cung cấp phương thức map()để ánh xạ một tệp vào một buffer. 6.4. Charset và Selector Hai thực thể khác được sử dụng bởi NIO là các CharSet và Selector. CharSet xác định cách ánh xạ các byte thành các ký tự. Ta có thể mã hóa một xâu ký tự bằng cách sử dụng một bộ mã hóa và cũng có thể giải mã một dãy các byte thành các ký 77 tự bằng cách sử dụng bộ giải mã. Charset, encoder và decoder được hỗ trợ bởi gói java.nio.charset. Selector hỗ trợ vào ra ghép kênh, không phong tỏa, dựa trên phím. Ngoài ra, selector còn cho phép ta làm việc với nhiều kênh. Selector được hỗ trợ bởi các lớp trong gói java.io.channels. Các selector ứng dụng nhiều nhất với các kênh dựa trên luồng. 6.5. Sử dụng hệ thống vào ra mới Đơn vị dữ liệu vào ra phổ biến nhất là tệp tin, trong phần này ta sẽ xem cách thức để truy xuất tới các tệp tin trên đĩa bằng cách sử dụng hệ thống vào ra mới. Do hầu hết các thao tác trên tệp là mức byte nên kiểu vùng đệm được sử dụng sẽ là ByteBuffer. 6.5.1. Đọc tệp Có một số cách để đọc dữ liệu từ một tệp tin bằng cách sử dụng hệ thống vào ra mới. Chúng ta sẽ xem xét hai cách. Cách thứ nhất đọc một tệp tin bằng cách ánh xạ nó vào một buffer và sau đó thực hiện một thao tác đọc. Cách thứ hai để đọc một tệp tin là tự động hóa quá trình đọc. • Cách 1: Bước 1: Mở một tệp tin để đọc bằng cách sử dụng luồng FileInputStream. Bước 2: Nhận một kênh từ đối tượng FileInputStream nhờ phương thưc FileChannel getChannel() Bước 3: Xác định kích thước của tệp tin bằng cách gọi phương thức size() Long size() throws IOException Bước 4: Gọi phương thức allocate()để phân bổ một vùng đệm đủ lớn để lưu giữ nội dung của tệp. static ByteBuffer allocate(int cap) Ví dụ import java.io.*; import java.nio.*; import java.nio.channels.*; public class ChannelRead { public static void main(String[] args) { FileInputStream fis; FileChannel fc; long fSize; ByteBuffer bb; try{ //Mo mot ep fis=new FileInputStream(args[0]); //Mo mot kenh toi tep fc=fis.getChannel(); 78 //Nhan kich thuoc tep tin fSize=fc.size(); //Phan bo mot vung dem co kich thuoc can thiet bb=ByteBuffer.allocate((int)fSize); //Doc tep tin vao vung dem fc.read(bb); //Mo tep de doc bb.rewind(); for(int i=0;i<fSize; i++) System.out.print((char)bb.get()); fc.close(); fis.close(); } catch(IOException e) { System.out.println(e); } } } Kết quả thực hiện chương trình C:\MyJava>javac ChannelRead.java C:\MyJava>java ChannelRead Bai3.java class Bai3 { public static void main( String args[] ) { double x = 42 ; System.out.println( x = 42 % 3 + 3 * 3 - 3 / 3 ); } } • Cách 2 Một cách dễ hơn để đọc một tệp tin là ánh xạ vào một vùng đệm. Ưu điểm cho của cách tiếp cận này là vùng đệm tự động lưu nội dung của tệp tin. Không cần thao tác đọc cụ thể nào. Các bước thực hiện Bước 1: Mở một tệp tin bằng cách sử dụng luồng FileInputStream Bước 2: Nhận một kênh tới tệp tin đó bằng cách gọi phương thức getChannel() trên đối tượng FileInputStream. Bước 3: Ánh xạ kênh với một vùng đệm bằng cách gọi phương thức map() trên đối tượng FileChannel. Phương thức map có dạng như sau: 79 MappedByteBuffer map(FileChannel.MapMode how, long pos, long size) throws IOException Phương thức map() làm cho dữ liệu trong tệp tin được ánh xạ vàơo vùng đệm trong bộ nhớ. Tham số how xác định kiểu thao tác được phép thực hiện trên tệp tin: MapMode.READ MapMode.READ_WRITE MapMode.PRIVATE Để đọc một tệp tin ta dùng chế đọ MapMode.READ. Để đọc và ghi tệp ta dùng chế độ MapMode.READ_WRITE. Chế độ MapMode.PRIVATE chỉ làm cho một bản sao riêng của một tệp bị thay đổi và những thay đổi này không ảnh hưởng tới tệp tin. Vị trí trong tệp tin bắt đầu ánh xạ được xác định bởi tham số pos và số byte ánh xạ được xác định bởi size. Phương thức trả về là một tham chiếu MappedByteBuffer, là một lớp con của ByteBuffer. Mỗi khi tệp tin được ánh xạ vào vùng đệm ta có thể đọc tệp từ vùng đệm. import java.io.*; import java.nio.*; import java.nio.channels.*; public class MappedChannelRead { public static void main(String[] args) { FileInputStream fis; FileChannel fc; MappedByteBuffer mbb; long fSize; try{ //Mo tep de doc fis=new FileInputStream(args[0]); //Mo kenh fc=fis.getChannel(); //Nhan kich thuoc tep fSize=fc.size(); // Anh xa file vao vung dem mbb=fc.map(FileChannel.MapMode.READ_ONLY,0,fSize); //Doc cac byte tu vung dem for(int i=0; i<fSize;i++) System.out.print((char)mbb.get()); fc.close(); fis.close(); } catch(IOException e) { 80 System.out.println(e.getMessage()); System.exit(1); } } } Kết quả thực hiện C:\MyJava>java MappedChannelRead Bai3.java class Bai3 { public static void main( String args[] ) { double x = 42 ; System.out.println( x = 42 % 3 + 3 * 3 - 3 / 3 ); } } 6.5.2. Ghi tệp tin Có một số cách để ghi tệp thông qua một kênh. Ở đây, chúng ta cũng tìm hiểu hai cách ghi tệp. Cách thứ nhất là ghi tệp thông qua một kênh bằng cách sử dụng các thao tác write. Cách thứ hai, nếu tệp tin được mở để thực hiện các thao tác đọc/ghi, ta có thể ánh xạ tệp vào một vùng đệm và sau đó ghi vào vùng đệm. Những thay đổi với vùng đệm sẽ được tự động ảnh hưởng đến tệp tin. Cả hai cách đều được mô tả trong mục này. Để ghi một tệp thông qua kênh bằng cách sử dụng các lời gọi tới phương thức write(), ta thực hiện các bước sau đây. Bước 1: Mở một tệp để ghi. Bước 2: Xác định một vùng đệm byte để ghi dữ liệu vào vùng đệm đó, sau đó gọi phương thức write(). import java.io.*; import java.nio.*; import java.nio.channels.*; public class ChannelWrite { public static void main(String[] args) { FileOutputStream fos; FileChannel fc; ByteBuffer bb; try { String s="This is a test of NIO system"; fos=new FileOutputStream(args[0]); fc=fos.getChannel(); bb=ByteBuffer.allocateDirect(s.length()); 81 //Ghi mot so byte vao vung dem byte[] b=s.getBytes(); for(int i=0;i<b.length;i++)bb.put(b[i]); bb.rewind(); fc.write(bb); fc.close(); fos.close(); } catch(Exception e) { System.err.println(e); } } } Sao chép một tệp bằng cách sử dụng tiện ích vào ra mới Hệ thống vào ra mới đơn giản hóa một số kiểu thao tác trên tệp tin. Ví dụ, chương trình dưới đây sao chép một tệp tin. import java.io.*; import java.nio.*; import java.nio.channels.*; public class NIOCopy { public static void main(String[] args) { FileOutputStream fos; FileInputStream fis; FileChannel fco,fci; long fSize; MappedByteBuffer mbb; try{ fis=new FileInputStream(args[0]); fos=new FileOutputStream(args[1]); fci=fis.getChannel(); fco=fos.getChannel(); 82 fSize=fci.size(); mbb=fci.map(FileChannel.MapMode.READ_ONLY,0,fSize); fco.write(mbb); fci.close(); fco.close(); fos.close(); fis.close(); } catch(Exception e) } } } 7. Kết luận Chương này chúng ta đã tìm hiểu các khái niệm căn bản về vào ra bằng cách sử dụng các luồng trong Java. Cũng trong chương này các luồng hướng byte và các luồng hướng ký tự trong Java đã được giới thiệu. Khái niệm vào ra mới bằng cách sử dụng các kênh (channel) và vùng đệm (buffer) cũng được giới thiệu trong chương này. Ở các chương tiếp theo các bạn sẽ thấy hầu hết các chương trình lập trình mạng đều vào ra dữ liệu bằng cách sử dụng các luồng. Việc hiểu biết sâu về luồng vào ra sẽ là một lợi thế để bạn đọc tiếp cận với các chương tiếp theo.
File đính kèm:
- Lập trình mạng với Java - Chương 3_Các luồng vào ra.pdf