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.

pdf24 trang | Chuyên mục: Java | Chia sẻ: dkS00TYs | Lượt xem: 2864 | Lượt tải: 2download
Tóm tắt nội dung Lập trình mạng với Java - Chương 3: Các luồng vào ra, để xem tài liệu hoàn chỉnh bạn click vào nút "TẢI VỀ" ở trên
 
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:

  • pdfLập trình mạng với Java - Chương 3_Các luồng vào ra.pdf