[Java IO - Từ tổng quan tới chi tiết] Bài 03: Các lớp dẫn xuất của InputStream: FileInputStream
Bài đăng này đã không được cập nhật trong 3 năm
Chào các bạn! Chúng ta lại gặp nhau trong series Java IO - Từ tổng quan tới chi tiết.
Trong bài này, chúng ta sẽ đi tìm hiểu về cả FileInputStream
và FileOutputStream
luôn nhé!
Để giúp các bạn dễ hình dung 2 lớp này nằm ở đâu trong cây phân cấp, mình có đánh dấu ở hình dưới đây. (Hơi mờ chút, các bạn thông cảm nhé)
Ngoài ra, bạn có thể tìm hiểu thêm tại Document của Oracle tại FileInputStream và FileOutputStream
1. FileInputStream
Lớp java.io.FileInputStream
được dùng với mục đích thu nhận các byte đầu vào từ một file.
Lớp java.io.FileInputStream
có 3 hàm khởi tạo, các bạn có thể xem chi tiết tại đây
FileInputStream(File file)
FileInputStream(FileDescriptor fdObj)
FileInputStream(String name)
Để hiểu xem cách đọc file, các bạn xem qua ví dụ sau:
import java.io.*;
/**
* Created by nhs3108 on 05/07/2017.
*/
public class FileInputStreamExample {
public static void main(String[] args) {
String absoluteFilePath = "/home/nhs3108/Desktop/test.txt";
try {
String content = FileInputStreamExample.getContentFile(absoluteFilePath);
System.out.println("-------------------------------------------------");
System.out.println(content);
System.out.println("-------------------------------------------------");
} catch (IOException e) {
e.printStackTrace();
}
}
public static String getContentFile(String absoluteFilePath) throws IOException {
StringBuilder result = new StringBuilder();
FileInputStream fileInputStream = new FileInputStream(absoluteFilePath);
int b;
while ((b = fileInputStream.read()) != -1) {
result.append((char) b);
}
fileInputStream.close();
return result.toString();
}
}
GIẢI THÍCH CHÚT NHÉ:
Khi khởi tạo fileInputStream, bạn có thể hình dung ra có 1 con trỏ đang trỏ tới byte đầu tiên của dãy bytes của file. Hàm read()
được gọi sẽ làm 2 việc
- Một là di chuyển con trỏ tới byte tiếp theo trong dãy bytes
- Hai là thực hiện trả về byte code tại vị trí con trỏ đang trỏ tới
Quá trình diễn ra có thể được hình dung như sau
Khi khởi tạo đối tượng fileInputStream
tạo kết nối tới file cần thao tác đọc. Với * thể hiện cho một con trỏ đánh dấu vị trí byte mà nó đang trỏ tới. Ta có thể hình dung các bytes (mình ví dụ 3 bytes thôi nhé) được sắp xếp như sau.
HEAD | byte-1 | byte-2 | byte-3 | TAIL |
---|---|---|---|---|
* |
Khi gọi hàm read lần đầu tiên, con trỏ di chuyển tới byte-1 và trả về byte code của byte-1
HEAD | byte-1 | byte-2 | byte-3 | TAIL |
---|---|---|---|---|
* |
Tiếp theo, là vị trí byte-2
HEAD | byte-1 | byte-2 | byte-3 | TAIL |
---|---|---|---|---|
* |
Tiếp theo, là vị trí byte-3
HEAD | byte-1 | byte-2 | byte-3 | TAIL |
---|---|---|---|---|
* |
Tại thời điểm này, ta gọi hàm read()
, nó sẽ trả về giá trị -1
, đồng nghĩa với việc nó đã di chuyển tới cuối file. Lúc này ta sẽ dừng việc đọc byte cho dãy bytes của file lại.
HEAD | byte-1 | byte-2 | byte-3 | TAIL |
---|---|---|---|---|
* |
Bằng lý thuyết đó, ta sử dụng vòng lặp để đọc từng byte trong dãy bytes cho đến khi nào đọc tới cuối file, tức là tới khi read()
trả về giá trị -1
Lệnh (char) b
được thực hiện để ký tự tuơng ứng của byte code đó trong bảng mã ASCII
VÍ DỤ
Với file /home/nhs3108/Desktop/test.txt
có nội dung sau
Name : Nguyen Hong Son Gender : Male Marial Status : Single
Và kết quả nhận được tương ứng là
-------------------------------------------------
Name : Nguyen Hong Son
Gender : Male
Marial Status : Single
-------------------------------------------------
Tuy nhiên, việc đọc từng byte như ví dụ trên sẽ rất chậm. Bạn sẽ thấy rõ ràng độ chậm của nó khi bạn đọc 1 file có dữ liệu kha khá (vài MB). Chính vì thế, InputStream
cung cấp thên phương thức read(byte[] bytes, int offset, int length)
để hỗ trợ cho việc đọc file, với
- mảng
bytes
là bộ nhớ đệm được sử dụng để lưu lại các bytes đọc được - Giá trị
offset
là vị trí đầu tiên của mảngbytes
được ghi dữ liệu. - Giá trị
length
là số bytes tối đa được đọc được từ file rồi ghi lên mảngbytes
. Giá trị của length làm sao nằm trong khoảng[0, bytes.length - offset]
. Ví dụ bộ nhớ đệm có size = 4, offet = 2, thì bạn chỉ có thể set giá trị của length trong khoảng [0-2].
Mình lấy ví dụ, mảng bytes
của mình có size là 4 bytes = new bytes[4]
. offset
= 1, length
= 2. Khi đọc được 2 bytes b1
và b2
từ file và thực hiện ghi lên bộ nhớ đệm, mảng bytes sẽ có dạng
- Số bytes đối đa được ghi lên bộ nhớ đệm có bằng giá trị của
length
hay không phụ thuộc vào số bytes còn lại từ vị trí con trỏ tới cuối file.Số bytes đọc được = remainingBytes.length > length ? length : remainingBytes.length
. - Giá trị trả về là tổng số bytes đọc được vào bộ nhớ đệm. Trả về
-1
nếu không còn dữ liệu để đọc do đã đọc tới cuối file.
Phương thức read(byte[] bytes)
thực hiện lệnh gọi phương thức read(byte[] bytes, int offset, int length)
với offset
= 0 và length
= bytes.length
Giờ thì hãy tự mình trải nghiệm và so sánh tốc độ giữa việc đọc từng byte với sử dụng buffer (size tùy bạn chọn) nhé.
Câu hỏi đặt ra là: Khi nào nên đọc từng byte? Khi nào sử dụng bộ nhớ đệm? Nếu sử dụng bộ nhớ đệm thì size của bộ nhớ đệm bao nhiêu là hợp lý?
.
Các bạn hãy tự mình tìm câu trả lời nhé. Keyword mà bạn có thể sử dụng là How do I decide how many bytes to read from an inputstream?
2. FileOutputStream
Cũng giống như lớp FileInputStream
, lớp FileOutputStream
được sử dụng để làm việc với file. Khác là ở chỗ, trong khi lớp FileInputStream
được sử dụng để thu nhận các dữ liệu thì FileOutputStream
được sử dụng để ghi dữ liệu lên file.
Lớp FileOutputStream
cung cấp 5 hàm khởi tạo, chi tiết tại đây
FileOutputStream(File file)
FileOutputStream(File file, boolean append)
FileOutputStream(FileDescriptor fdObj)
FileOutputStream(String name)
FileOutputStream(String name, boolean append)
CHÚ Ý
- Một chú ý nho nhỏ ở đây, giá trị boolean của
append
. Làtrue
thì các bytes sẽ được ghi vào cuối của file (hiểu nôm na là dán thêm vào file). Làfalse
thì ngược lại, khi thực hiện ghi, dữ liệu tồn tại trước đó của file sẽ bị xóa sạch để ghi mới.false
cũng là giá trị mặc định củaappend
- Lớp có 3 phuơng thức ghi bao gồm:
--
write(byte[] bytes)
: Ghi toàn bộ cácbytes
, tuơng đuơng với việc bạn gọiwrite(byte[] bytes, int offset, int length)
với offset = 0 và length = bytes.length --write(byte[] bytes, int offset, int length)
: Ghilength
các bytes nằm trong mảngbytes
, bắt đầu từ vị tríoffset
(của mảngbytes
). --write(int byte)
: Ghi một byte duy nhất.
Hãy cùng xem qua ví dụ nho nhỏ dưới đây của mình để xem cách sử dụng FileOutputStream
để ghi dữ liệu ra file nhé.
import java.io.FileOutputStream;
import java.io.IOException;
/**
* Created by nhs3108 on 11/07/2017.
*/
public class FileOutputStreamExample {
public static void main(String[] args) {
String absoluteFilePath = "/home/nhs3108/Desktop/test_output.txt";
String content = "Welcome to Nguyen Hong Son's tutorials!";
boolean append = false;
try {
System.out.println("----------------Writing...---------------");
FileOutputStreamExample.writeContent(absoluteFilePath, content, append);
System.out.println("-------------------DONE!-------------------");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void writeContent(String absoluteFilePath, String content, boolean append) throws IOException {
FileOutputStream fileOutputStream = new FileOutputStream(absoluteFilePath, append);
fileOutputStream.write(content.getBytes());
fileOutputStream.close();
}
}
All rights reserved