Java - ĐH Công Nghệ - 27



Có thể bạn quan tâm!

Xem toàn bộ 251 trang tài liệu này.

Java - ĐH Công Nghệ - 27



Hình 12.7: Ví dụ sử dụng phương thức split.


12.6. CÁC DÒNG VÀO/RA TRONG Java API


Mục này trình bày lại một cách có hệ thống các kiến thức về thư viện vào ra dữ liệu của Java mà ta đã nói đến rải rác ở các mục trước. Nội dung mục này chỉ ở mức giới thiệu sơ qua về một số dòng vào ra quan trọng. Các chi tiết cần được tra cứu ở tài liệu Java API.

Java coi mỗi file như là một dòng tuần tự các byte. Mỗi dòng như vậy có thể được hiểu là thuộc về một trong hai dạng: dòng kí tự (character-based stream) dành cho vào ra dữ liệu dạng kí tự và dòng byte (byte-based stream) dành cho dữ liệu dạng nhị phân. Ví dụ, nếu 5 được lưu với dòng byte, nó sẽ được lưu trữ ở dạng nhị phân của giá trị số 5, hay chuỗi bit 101. Còn nếu lưu bằng dòng kí tự, nó sẽ được lưu trữ ở dạng nhị phân của kí tự 5, hay chuỗi bit 00000000 00110101 (dạng nhị phân của giá trị 53, là mã Unicode của kí tự 5). File được tạo bằng dòng byte là file nhị phân, còn file được tạo bằng dòng kí tự là file văn bản. Con người có thể đọc nội dung file văn bản bằng các trình soạn thảo văn bản, còn các file nhị phân được đọc bởi các chương trình biến đổi dữ liệu nhị phân ra định dạng con người đọc được.

Để trao đổi dữ liệu với một file hay một thiết bị, chương trình Java tạo một dòng kết nối và nối với file hay thiết bị đó. Ví dụ, ta đã có sẵn ba dòng: System.in là dòng vào chuẩn (thường nối với bàn phím), System.out là dòng ra chuẩn (thường nối với cửa sổ lệnh), và System.err là dòng báo lỗi chuẩn (luôn nối với cửa sổ lệnh).

Các dòng dành cho việc xử lý dữ liệu nhị phân nằm trong hai cây phả hệ: các dòng có tổ tiên là InputStream để đọc dữ liệu, còn các dòng có tổ tiên là OutputStream để ghi dữ liệu. Các dòng cơ sở InputStream/OutputStream chỉ cung cấp các phương thức cho phép đọc/ghi dữ liệu thô ở dạng byte. Các lớp con của chúng cho phép đọc/ghi các giá trị thuộc các kiểu dữ liệu phức tạp hơn hoặc cho phép kết nối với các loại thiết bị cụ thể. Một số dòng quan trọng trong đó gồm có:

FileInputStream/FileOutputStream: dòng kết nối để nối trực tiếp với file nhị phân cần đọc/ghi theo dạng tuần tự.

ObjectInputStream/ObjectOutputStream: dòng nối tiếp, có thể nối với một InputStream/OutputStream khác. Các dòng này cho phép đọc/ghi từng đối tượng thuộc loại chuỗi hóa được.

DataInputStream/DataOutputStream: dòng nối tiếp, có thể nối với một InputStream/OutputStream khác, cho phép đọc/ghi các giá trị thuộc các kiểu cơ bản như int, long, boolean, ... (xem ví dụ trong Hình 12.8)



Hình 12.8: Đọc và ghi dữ liệu kiểu cơ bản.


Các dòng dành cho việc xử lý dữ liệu văn bản nằm trong hai cây phả hệ: các dòng có tổ tiên là Reader đọc dữ liệu, còn các dòng có tổ tiên là Writer ghi dữ liệu. Các dòng cơ sở Reader/Writer chỉ cung cấp các phương thức cho phép đọc/ghi dữ liệu ở dạng char hoặc chuỗi char. Các lớp con của chúng cho phép đọc/ghi với hiệu

quả cao hơn và cung cấp các tiện ích bổ sung. Một số dòng quan trọng trong đó gồm có:

FileReader/FileWriter: dòng kết nối để nối trực tiếp với file cần đọc/ghi dữ liệu văn bản theo dạng tuần tự. FileReader cho phép đọc String từ file. FileWriter cho phép ghi String ra file.

BufferedReader/BufferedWriter: dòng nối tiếp, có thể nối với một Reader/Writer khác để đọc/ghi văn bản với bộ nhớ đệm nhằm tăng tốc độ xử lý.

InputStreamReader/OutputStreamWriter : dòng nối tiếp, là cầu nối từ dòng kí tự tới dòng byte, có thể nối với một InputStream/OutputStream. Nó cho phép đọc/ghi dữ liệu dạng kí tự được mã hóa trong một dòng byte theo một bộ mã cho trước.

PrintWriter: cho phép ghi dữ liệu có định dạng ra dòng kí tự, có thể kết nối trực tiếp với File, String, hoặc nối tiếp với một Writer hay OutputStream.

Ví dụ về InputStreamReader được cho trong Hình 12.9. Trong đó, kết nối Internet là nguồn dữ liệu dòng byte. Đầu tiên, nguồn vào được nối với một InputStream để có thể đọc dữ liệu byte thô. Sau đó, nó được nối với một InputStreamReader để chuyển từ dữ liệu byte sang dữ liệu văn bản. Cuối dùng, ta nối một BufferReader vào InputStreamReader để có thể đọc văn bản với tốc độ cao hơn.


Hình 12.9: Đọc dữ liệu văn bản từ kết nối Internet.


Ví dụ về sử dụng dòng PrintWriter được cho trong Hình 12.10. Dòng này cung cấp các phương thức ghi dữ liệu ra tương tự như ta quen dùng với dòng System.out.



Hình 12.10: Dùng PrintWriter.

Đọc thêm

Chương này nói về các nét cơ bản và nguyên lý sử dụng của dòng vào ra dữ liệu, chỉ dừng lại ở việc giới thiệu sơ lược chứ không đi sâu vào việc sử dụng vào ra dữ liệu sử dụng thư viện chuẩn Java. Để tìm hiểu sâu hơn về hỗ trợ của Java cho việc quản lí và vào ra dữ liệu file, người đọc có thể đọc thêm tại các tài liệu đi sâu vào nội dung lập trình Java như:

1. Basic I/O, The JavaTM Tutorials, http://docs.oracle.com/javase/tutorial/essential/io/index.html

2. Chương 14, Deitel & Deitel, Java How to Program, 6th edition, Prentice Hall, 2005.

Một chủ đề khá liên quan đến vào ra dữ liệu là lập trình mạng. Người đọc có thể đọc thêm về chủ đề này tại các tài liệu như:

1. Networking Basics, The JavaTM Tutorials, http://docs.oracle.com/javase/tutorial/networking/overview/networking.html

2. Chương 15, Sierra, Bert Bates, Head First Java, 2nd edition, O'Reilly, 2008.

Bài tập

1. Đúng hay sai?

a) Chuỗi hóa là phương pháp thích hợp khi lưu dữ liệu cho các chương trình không được viết bằng Java sử dụng.

b) Chuỗi hóa là cách duy nhất để lưu trạng thái của đối tượng

c) Có thể dùng ObjectOutputStream để lưu các đối tượng được chuỗi hóa.

d) Các dòng nối tiếp có thể được dùng riêng hoặc kết hợp với các dòng kết nối.

e) Có thể dùng một lời gọi tới writeObject() có thể lưu nhiều đối tượng.

f) Mặc định, tất cả các lớp đều thuộc diện chuỗi hóa được.

g) Từ khóa transient đánh dấu các biến thực thể chuỗi hóa được

h) nếu một lớp cha không chuỗi hóa được thì lớp con của nó cũng không thể chuỗi hóa được.

i) Khi một đối tượng được khôi phục (khử chuỗi hóa), hàm khởi tạo của nó không chạy.

j) Khi các đối tượng được khôi phục (khử chuỗi hóa), chúng được đọc theo thứ tự "ghi sau - đọc trước".

k) Cả hai việc chuỗi hóa đối tượng và lưu ra file văn bản đều có thể ném ngoại lệ.

l) BufferedWriter có thể nối với FileWriter.

m) Các đối tượng File đại diện cho file chứ không đại diện cho thư mục

n) Ta không thể buộc một buffer gửi dữ liệu của nó nếu nó chưa đầy.

o) Thay đổi bất kì đối với một lớp sẽ phá hỏng các đối tượng của lớp đó đã được chuỗi hóa từ trước.

2. Viết lớp Contact mô tả một mục trong danh bạ điện thoại, các trường dữ liệu gồm: tên, địa chỉ, số điện thoại; lớp ContactList quản lý danh bạ điện thoại, là một danh sách các đối tượng Contact. Lớp ContactList cần cung cấp các phương thức cho phép thêm mục mới, xóa mục cũ trong danh bạ, lưu danh bạ ra file và nạp từ file. Dùng cơ chế cài chồng để cho phép sử dụng cả hai cơ chế chuỗi hóa đối tượng và dùng file văn bản.


Chương 13. Lập trình tổng quát và các lớp collection

Ta thử hình dung một phương thức sort sắp xếp một loạt các giá trị kiểu int, một phương thức sort khác dành cho các đối tượng String, một phương thức sort dành cho các đối tượng kiểu Complex (số phức). Mã cài đặt các phương thức đó hầu như là giống hệt nhau, chỉ khác ở kiểu dữ liệu tại các dòng khai báo biến. Hình dung một lớp IntegerStack (ngăn xếp) để lưu trữ các đối tượng Integer, một lớp AnimalStack để lưu trữ các đối tượng Animal, một lớp StringStack để lưu trữ các đối tượng String, v.v.. Mã cài đặt các lớp này cũng hầu như là giống hệt nhau. Nếu như ta có thể viết duy nhất một phương thức sort dùng được cho cả int, String, Complex, một lớp Stack dùng để tạo được cả ngăn xếp Integer, ngăn xếp Animal, ngăn xếp String, thì đó là lập trình tổng quát. Lập trình tổng quát cho phép xây dựng các phương thức tổng quát và các lớp tổng quát, mà nhờ đó có được một tập các phương thức tương tự nhau từ chỉ một cài đặt phương thức, một tập các kiểu dữ liệu tương tự nhau từ chỉ một cài đặt lớp

Trước phiên bản 5.0 của Java API, ta có thể dùng quan hệ thừa kế và lớp Object để có các cấu trúc dữ liệu tổng quát. Chẳng hạn, ta tạo một lớp MyStack là ngăn xếp dành cho kiểu Object:



Hình 13.1: Cấu trúc dữ liệu chứa Object.


Do Object là lớp tổ tiên của tất cả các lớp khác, nên ta có thể dùng đối tượng MyStack đó để làm ngăn xếp cho các đối tượng kiểu Integer, hay cho các đối tượng String (xem Hình 13.1). Tuy nhiên, nhược điểm của cách làm này là khi lấy dữ liệu ra khỏi cấu trúc, ta cần phải ép kiểu trở lại kiểu ban đầu, do các phương thức của MyStack chỉ biết làm việc với tham chiếu kiểu Object. Ngoài ra, cũng vì MyStack đó coi tất cả các phần tử như là các Object, nên trình biên dịch không kiểm tra kiểu để đảm bảo một đối tượng MyStack chỉ chứa các đối tượng thuộc cùng một loại, chỉ toàn Integer hoặc chỉ toàn String. Các đối tượng Integer, hay String, hay thậm chí Cow thì cũng đều là Object cả. Các đối tượng ngăn xếp có tiềm năng trở thành hỗn độn và sẽ dễ sinh lỗi trong quá trình chạy. Đó không phải là sự linh hoạt mà ta mong muốn.

Ngày đăng: 24/12/2023