1. Luồng dữ liệu
Hiếm khi một chương trình tồn tại như một hòn đảo riêng. Các chương trình thường bằng cách nào đó tương tác với "thế giới bên ngoài". Điều này có thể xảy ra thông qua việc đọc dữ liệu từ bàn phím, gửi tin nhắn, tải xuống các trang từ Internet hoặc ngược lại, tải tệp lên máy chủ từ xa.
Chúng ta có thể đề cập đến tất cả các hành vi này trong một từ: trao đổi dữ liệu giữa chương trình và thế giới bên ngoài. Đợi đã, đó không chỉ là một từ.
Tất nhiên, bản thân việc trao đổi dữ liệu có thể được chia thành hai phần: nhận dữ liệu và gửi dữ liệu. Ví dụ: bạn đọc dữ liệu từ bàn phím bằng một Scanner
đối tượng — đây là nhận dữ liệu. Và bạn hiển thị dữ liệu trên màn hình bằng System.out.println()
lệnh — đây là gửi dữ liệu.
Trong lập trình, thuật ngữ "luồng" được sử dụng để mô tả việc trao đổi dữ liệu. Thuật ngữ đó đến từ đâu?
Trong cuộc sống thực, bạn có thể có một dòng nước hoặc một dòng ý thức. Trong lập trình, chúng ta có các luồng dữ liệu .
Luồng là một công cụ đa năng. Chúng cho phép chương trình nhận dữ liệu từ bất kỳ đâu (luồng đầu vào) và gửi dữ liệu đến bất kỳ đâu (luồng đầu ra). Như vậy, có hai loại:
- Một luồng đầu vào là để nhận dữ liệu
- Một luồng đầu ra là để gửi dữ liệu
Để làm cho các luồng 'hữu hình', những người tạo ra Java đã viết hai lớp: InputStream
và OutputStream
.
Lớp InputStream
có một read()
phương thức cho phép bạn đọc dữ liệu từ nó. Và OutputStream
lớp có một write()
phương thức cho phép bạn ghi dữ liệu vào nó. Họ cũng có các phương pháp khác, nhưng nhiều hơn về điều đó sau.
luồng byte
Chúng ta đang nói về loại dữ liệu nào? Nó có định dạng gì? Nói cách khác, các lớp này hỗ trợ những kiểu dữ liệu nào?
Đây là các lớp chung, vì vậy chúng hỗ trợ kiểu dữ liệu phổ biến nhất — tệp byte
. An OutputStream
có thể ghi byte (và mảng byte) và InputStream
đối tượng có thể đọc byte (hoặc mảng byte). Vậy là xong — chúng không hỗ trợ bất kỳ loại dữ liệu nào khác.
Do đó, các luồng này còn được gọi là luồng byte .
Một đặc điểm của luồng là dữ liệu của chúng chỉ có thể được đọc (hoặc ghi) tuần tự. Bạn không thể đọc dữ liệu từ giữa luồng mà không đọc tất cả dữ liệu trước nó.
Đây là cách đọc dữ liệu từ bàn phím hoạt động thông qua Scanner
lớp: bạn đọc dữ liệu từ bàn phím một cách tuần tự, từng dòng một. Chúng ta đọc một dòng, rồi dòng tiếp theo, rồi dòng tiếp theo, v.v. Một cách phù hợp, phương thức đọc dòng được gọi là nextLine()
.
Ghi dữ liệu vào một OutputStream
cũng xảy ra tuần tự. Một ví dụ điển hình về điều này là đầu ra của bảng điều khiển. Bạn xuất một dòng, tiếp theo là dòng khác và dòng khác. Đây là đầu ra tuần tự. Bạn không thể xuất dòng đầu tiên, sau đó là dòng thứ mười và sau đó là dòng thứ hai. Tất cả dữ liệu chỉ được ghi vào luồng đầu ra một cách tuần tự.
Luồng ký tự
Gần đây, bạn đã biết rằng chuỗi là loại dữ liệu phổ biến thứ hai và thực tế là như vậy. Rất nhiều thông tin được truyền xung quanh dưới dạng ký tự và toàn bộ chuỗi. Một máy tính vượt trội trong việc gửi và nhận mọi thứ dưới dạng byte, nhưng con người không hoàn hảo như vậy.
Giải thích cho thực tế này, các lập trình viên Java đã viết thêm hai lớp: Reader
và Writer
. Lớp Reader
tương tự như lớp InputStream
, nhưng read()
phương thức của nó không đọc byte, mà đọc các ký tự ( char
). Lớp Writer
tương ứng với OutputStream
lớp. Và cũng giống như Reader
lớp, nó hoạt động với các ký tự ( char
), không phải byte.
Nếu chúng ta so sánh bốn lớp này, chúng ta sẽ có được bức tranh sau:
Byte (byte) | Ký tự (char) | |
---|---|---|
Đọc dữ liệu |
|
|
Ghi dữ liệu |
|
|
ứng dụng thực tế
Bản thân các lớp InputStream
, OutputStream
, Reader
và Writer
không được sử dụng trực tiếp bởi bất kỳ ai, vì chúng không được liên kết với bất kỳ đối tượng cụ thể nào mà từ đó dữ liệu có thể được đọc (hoặc dữ liệu có thể được ghi vào). Nhưng bốn lớp này có rất nhiều lớp hậu duệ có thể làm được nhiều việc.
2. InputStream
lớp học
Lớp này InputStream
thú vị vì nó là lớp cha của hàng trăm lớp con. Nó không có bất kỳ dữ liệu nào của riêng nó, nhưng nó có các phương thức mà tất cả các lớp dẫn xuất của nó kế thừa.
Nói chung, hiếm khi các đối tượng luồng lưu trữ dữ liệu bên trong. Luồng là một công cụ để đọc/ghi dữ liệu, nhưng không lưu trữ. Điều đó nói rằng, có những trường hợp ngoại lệ.
Các phương thức của InputStream
lớp và tất cả các lớp con của nó:
phương pháp | Sự miêu tả |
---|---|
|
Đọc một byte từ luồng |
|
Đọc một mảng byte từ luồng |
|
Đọc tất cả các byte từ luồng |
|
Bỏ qua n các byte trong luồng (đọc và loại bỏ chúng) |
|
Kiểm tra xem còn lại bao nhiêu byte trong luồng |
|
Đóng luồng |
Chúng ta hãy xem qua các phương pháp này một cách ngắn gọn:
read()
phương pháp
Phương read()
thức đọc một byte từ luồng và trả về nó. Bạn có thể bị nhầm lẫn bởi int
kiểu trả về. Loại này được chọn vì int
là loại số nguyên tiêu chuẩn. Ba byte đầu tiên của int
sẽ bằng không.
read(byte[] buffer)
phương pháp
Đây là biến thể thứ hai của read()
phương pháp. Nó cho phép bạn đọc một mảng byte từ InputStream
tất cả cùng một lúc. Mảng sẽ lưu trữ các byte phải được truyền dưới dạng đối số. Phương thức này trả về một số — số byte thực sự được đọc.
Giả sử bạn có bộ đệm 10 kilobyte và bạn đang đọc dữ liệu từ tệp bằng cách sử dụng lớp FileInputStream
. Nếu tệp chỉ chứa 2 kilobyte, tất cả dữ liệu sẽ được tải vào mảng bộ đệm và phương thức sẽ trả về số 2048 (2 kilobyte).
readAllBytes()
phương pháp
Một phương pháp rất tốt. Nó chỉ đọc tất cả dữ liệu từ cho InputStream
đến khi hết và trả về dưới dạng một mảng byte đơn. Điều này rất thuận tiện để đọc các tệp nhỏ. Các tệp lớn có thể không vừa với bộ nhớ và phương thức này sẽ đưa ra một ngoại lệ.
skip(long n)
phương pháp
Phương pháp này cho phép bạn bỏ qua n byte đầu tiên từ InputStream
đối tượng. Bởi vì dữ liệu được đọc tuần tự nghiêm ngặt, phương pháp này chỉ đọc n byte đầu tiên từ luồng và loại bỏ chúng.
Trả về số byte đã thực sự bị bỏ qua (trong trường hợp luồng kết thúc trước khi n
byte bị bỏ qua).
int available()
phương pháp
Phương thức trả về số byte vẫn còn trong luồng
void close()
phương pháp
Phương close()
thức đóng luồng dữ liệu và giải phóng các tài nguyên bên ngoài được liên kết với nó. Khi một luồng bị đóng, không thể đọc thêm dữ liệu từ luồng đó.
Hãy viết một chương trình ví dụ sao chép một tệp rất lớn. Chúng ta không thể sử dụng readAllBytes()
phương pháp đọc toàn bộ tệp vào bộ nhớ. Ví dụ:
Mã số | Ghi chú |
---|---|
|
InputStream để đọc từ tệp OutputStream để ghi vào tệp Bộ đệm mà chúng ta sẽ đọc dữ liệu miễn là có dữ liệu trong luồng Đọc dữ liệu vào bộ đệm Ghi dữ liệu từ bộ đệm vào luồng thứ hai |
Trong ví dụ này, chúng tôi đã sử dụng hai lớp: FileInputStream
là lớp con của InputStream
việc đọc dữ liệu từ một tệp và FileOutputStream
là lớp con của OutputStream
việc ghi dữ liệu vào một tệp. Chúng ta sẽ nói về lớp thứ hai một lát sau.
Một điểm thú vị khác ở đây là real
biến. Khi khối dữ liệu cuối cùng được đọc từ một tệp, nó có thể dễ dàng có ít hơn 64KB dữ liệu. Theo đó, chúng ta không cần xuất toàn bộ bộ đệm mà chỉ một phần của bộ đệm - real
các byte đầu tiên. Đây chính xác là những gì xảy ra trong write()
phương pháp.
3. Reader
lớp học
Lớp Reader
là một tương tự hoàn toàn của InputStream
lớp. Điểm khác biệt duy nhất là nó hoạt động với các ký tự ( char
), không phải với byte. Cũng giống như InputStream
lớp, Reader
lớp không được sử dụng riêng ở bất cứ đâu: nó là lớp cha của hàng trăm lớp con và định nghĩa các phương thức chung cho tất cả chúng.
Các phương thức của Reader
lớp (và tất cả các lớp con của nó):
phương pháp | Sự miêu tả |
---|---|
|
Đọc một char từ luồng |
|
Đọc một char mảng từ luồng |
|
Bỏ qua n chars trong luồng (đọc và loại bỏ chúng) |
|
Kiểm tra xem có còn thứ gì trong luồng không |
|
Đóng luồng |
Các phương thức rất giống với các phương thức của InputStream
lớp, mặc dù có một số khác biệt nhỏ.
int read()
phương pháp
Phương thức này đọc một char
từ luồng và trả về nó. Loại char
mở rộng thành int
, nhưng hai byte đầu tiên của kết quả luôn bằng 0.
int read(char[] buffer)
phương pháp
Đây là biến thể thứ hai của read()
phương pháp. Nó cho phép bạn đọc một mảng ký tự từ Reader
tất cả cùng một lúc. Mảng sẽ lưu trữ các ký tự phải được truyền dưới dạng đối số. Phương thức này trả về một số — số lượng ký tự thực sự được đọc.
skip(long n)
phương pháp
Phương thức này cho phép bạn bỏ qua n ký tự đầu tiên của Reader
đối tượng. Nó hoạt động giống hệt như phương thức tương tự của lớp InputStream
. Trả về số ký tự đã thực sự bị bỏ qua.
boolean ready()
phương pháp
Trả về true
nếu có byte chưa đọc trong luồng.
void close()
phương pháp
Phương close()
thức đóng luồng dữ liệu và giải phóng các tài nguyên bên ngoài được liên kết với nó. Khi một luồng bị đóng, không thể đọc thêm dữ liệu từ luồng đó.
Để so sánh, hãy viết chương trình sao chép tệp văn bản:
Mã số | Ghi chú |
---|---|
|
Reader để đọc từ tệp Writer để ghi vào tệp Bộ đệm mà chúng ta sẽ đọc dữ liệu miễn là có dữ liệu trong luồng Đọc dữ liệu vào bộ đệm Ghi dữ liệu từ bộ đệm vào luồng thứ hai |
GO TO FULL VERSION