1. Giới thiệu

Trong hướng dẫn này, chúng ta sẽ khám phá các cách khác nhau để đọc từ Tệp trong Java .

Trước tiên, chúng ta sẽ tìm hiểu cách tải tệp từ đường dẫn lớp, URL hoặc từ tệp JAR bằng cách sử dụng các lớp Java tiêu chuẩn.

Thứ hai, chúng ta sẽ xem cách đọc nội dung bằng BufferedReader , Scanner , StreamTokenizer , DataInputStream , SequenceInputStream và FileChannel . Chúng tôi cũng sẽ thảo luận về cách đọc tệp được mã hóa UTF-8.

Cuối cùng, chúng ta sẽ khám phá các kỹ thuật mới để tải và đọc tệp trong Java 7 và Java 8.

2. Thiết lập

2.1 Input File

Trong hầu hết các ví dụ xuyên suốt bài viết này, chúng ta sẽ đọc một tệp văn bản có tên tệp fileTest.txt:

Hello, world!

Đối với một số ví dụ, chúng tôi sẽ sử dụng một tệp khác; trong những trường hợp này, chúng tôi sẽ đề cập rõ ràng đến tệp và nội dung của nó.

2.2 Phương thức hỗ trợ

Chúng ta sẽ sử dụng phương thức readFromInputStream là một phương thức phổ biến để chuyển đổi InputStream thành String:

private String readFromInputStream(InputStream inputStream)
  throws IOException {
    StringBuilder resultStringBuilder = new StringBuilder();
    try (BufferedReader br
      = new BufferedReader(new InputStreamReader(inputStream))) {
        String line;
        while ((line = br.readLine()) != null) {
            resultStringBuilder.append(line).append("\n");
        }
    }
  return resultStringBuilder.toString();
}

Lưu ý: Có nhiều cách khác để đạt kết quả tương tự

3. Đọc file từ đường dẫn

3.1 Sử dụng standard Java

Phần này giải thích cách đọc một tệp có sẵn trên đường dẫn lớp. Chúng ta sẽ đọc “fileTest.txt” có sẵn trong src/main/resources:

@Test
public void givenFileNameAsAbsolutePath_whenUsingClasspath_thenFileData() {
    String expectedData = "Hello, world!";
    
    Class clazz = FileOperationsTest.class;
    InputStream inputStream = clazz.getResourceAsStream("/fileTest.txt");
    String data = readFromInputStream(inputStream);

    Assert.assertThat(data, containsString(expectedData));
}

Trong đoạn mã trên, chúng tôi đã sử dụng lớp hiện tại để tải tệp bằng phương thức getResourceAsStream

Phương thức tương tự cũng có sẵn trên phiên bản ClassLoader:

ClassLoader classLoader = getClass().getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("fileTest.txt");
String data = readFromInputStream(inputStream);

Chúng ta lấy classLoader của lớp hiện tại bằng cách sử dụng getClass().getClassLoader().

Sự khác biệt chính là khi sử dụng getResourceAsStream trên phiên bản ClassLoader, đường dẫn được coi là tuyệt đối bắt đầu từ gốc của đường dẫn lớp.

Khi được sử dụng đối với Class, đường dẫn có thể liên quan đến gói hoặc đường dẫn tuyệt đối, được gợi ý bằng dấu gạch chéo ở đầu.

Tất nhiên, hãy lưu ý rằng trong thực tế, các luồng mở phải luôn được đóng, chẳng hạn như Luồng đầu vào trong ví dụ của chúng tôi:

InputStream inputStream = null;
try {
    File file = new File(classLoader.getResource("fileTest.txt").getFile());
    inputStream = new FileInputStream(file);
    
    //...
}     
finally {
    if (inputStream != null) {
        try {
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3.2 Sử dụng thư viện commons-io

Một tùy chọn phổ biến khác là sử dụng lớp FileUtils của gói commons-io:

@Test
public void givenFileName_whenUsingFileUtils_thenFileData() {
    String expectedData = "Hello, world!";
        
    ClassLoader classLoader = getClass().getClassLoader();
    File file = new File(classLoader.getResource("fileTest.txt").getFile());
    String data = FileUtils.readFileToString(file, "UTF-8");
        
    assertEquals(expectedData, data.trim());
}

Ở đây chúng ta truyền đối tượng File cho phương thức readFileToString() của lớp FileUtils. Lớp tiện ích này quản lý việc tải nội dung mà không cần phải viết bất kỳ mã soạn sẵn nào để tạo một phiên bản inputStream và đọc dữ liệu.

Thư viện tương tự cũng cung cấp lớp IOUtils:

@Test
public void givenFileName_whenUsingIOUtils_thenFileData() {
    String expectedData = "Hello, world!";
        
    FileInputStream fis = new FileInputStream("src/test/resources/fileTest.txt");
    String data = IOUtils.toString(fis, "UTF-8");
        
    assertEquals(expectedData, data.trim());
}

Ở đây chúng ta truyền đối tượng FileInputStream cho phương thức toString() của lớp IOUtils. Lớp tiện ích này hoạt động giống như lớp trước để tạo một cá thể inputStream và đọc dữ liệu.

4. Sử dụng BufferedReader

Bây giờ hãy tập trung vào các cách khác nhau để phân tích nội dung của tệp.
Chúng ta sẽ bắt đầu với một cách đơn giản để đọc từ tệp bằng BufferedReader:

@Test
public void whenReadWithBufferedReader_thenCorrect()
  throws IOException {
     String expected_value = "Hello, world!";
     String file ="src/test/resources/fileTest.txt";
     
     BufferedReader reader = new BufferedReader(new FileReader(file));
     String currentLine = reader.readLine();
     reader.close();

    assertEquals(expected_value, currentLine);
}

Lưu ý rằng readLine() sẽ trả về null khi đến cuối tệp.

5. Sử dụng File Using Java NIO

Trong JDK7, gói NIO đã được cập nhật đáng kể.

Hãy xem một ví dụ sử dụng lớp Tệp và phương thức readAllLines. Phương thức readAllLines chấp nhận một Đường dẫn.

Lớp đường dẫn có thể được coi là bản nâng cấp của java.io.File với một số thao tác bổ sung được áp dụng

5.1 Đọc file cỡ nhỏ

Đoạn mã sau đây cho thấy cách đọc một tệp nhỏ bằng lớp Files:

@Test
public void whenReadSmallFileJava7_thenCorrect()
  throws IOException {
    String expected_value = "Hello, world!";

    Path path = Paths.get("src/test/resources/fileTest.txt");

    String read = Files.readAllLines(path).get(0);
    assertEquals(expected_value, read);
}

Lưu ý rằng chúng ta cũng có thể sử dụng phương thức readAllBytes() nếu cần dữ liệu nhị phân.

5.2 Đọc file cỡ lớn

Nếu chúng ta muốn đọc một tệp lớn với lớp Files, chúng ta có thể sử dụng BufferedReader.

Đoạn mã sau đọc tệp bằng lớp Files và BufferedReader:

@Test
public void whenReadLargeFileJava7_thenCorrect()
  throws IOException {
    String expected_value = "Hello, world!";

    Path path = Paths.get("src/test/resources/fileTest.txt");

    BufferedReader reader = Files.newBufferedReader(path);
    String line = reader.readLine();
    assertEquals(expected_value, line);
}

5.3 Sử dụng Files.lines()

JDK8 cung cấp phương thức lines() bên trong lớp Files. Nó trả về một phần tử Stream of String.

Hãy xem một ví dụ về cách đọc dữ liệu thành byte và giải mã nó bằng bộ ký tự UTF-8.

Đoạn mã sau đọc tệp bằng cách sử dụng Files.lines() mới:

@Test
public void givenFilePath_whenUsingFilesLines_thenFileData() {
    String expectedData = "Hello, world!";
         
    Path path = Paths.get(getClass().getClassLoader()
      .getResource("fileTest.txt").toURI());
         
    Stream<String> lines = Files.lines(path);
    String data = lines.collect(Collectors.joining("\n"));
    lines.close();
         
    Assert.assertEquals(expectedData, data.trim());
}

Khi sử dụng Stream với các kênh IO như thao tác với tệp, chúng ta cần đóng Stream một cách rõ ràng bằng phương thức close().

Như chúng ta có thể thấy, File API cung cấp một cách dễ dàng khác để đọc nội dung tệp thành String.

Trong các phần tiếp theo, chúng ta sẽ xem xét các phương pháp đọc tệp ít phổ biến khác có thể phù hợp trong một số trường hợp.

6. Sử dụng Scanner

Tiếp theo, hãy sử dụng Scanner để đọc File. Ở đây chúng tôi sẽ sử dụng khoảng trắng làm dấu phân tách các phần của dữ liệu:

@Test
public void whenReadWithScanner_thenCorrect()
  throws IOException {
    String file = "src/test/resources/fileTest.txt";
    Scanner scanner = new Scanner(new File(file));
    scanner.useDelimiter(" ");

    assertTrue(scanner.hasNext());
    assertEquals("Hello,", scanner.next());
    assertEquals("world!", scanner.next());

    scanner.close();
}

Lưu ý rằng dấu phân cách mặc định là khoảng trắng, nhưng có thể sử dụng nhiều dấu phân cách với Scanner.

Scanner rất hữu ích khi đọc nội dung từ console hoặc khi nội dung chứa các giá trị nguyên thủy, có dấu phân cách đã biết (ví dụ: danh sách các số nguyên được phân tách bằng dấu cách).

7. Sử dụng StreamTokenizer

Bây giờ, đọc tệp văn bản thành token bằng StreamTokenizer.

Tokenizer hoạt động bằng cách trước tiên tìm ra mã thông báo tiếp theo là gì, Chuỗi hoặc số. Chúng tôi thực hiện điều đó bằng cách xem xét trường tokenizer.ttype.

Sau đó, chúng tôi sẽ đọc mã thông báo thực tế dựa trên loại này:

tokenizer.nval – nếu loại là số

tokenizer.sval – nếu loại là String

Trong ví dụ này, chúng tôi sẽ sử dụng một tệp đầu vào khác chỉ chứa:

Hello 1

Đoạn mã sau đọc từ tệp cả String và số:

@Test
public void whenReadWithStreamTokenizer_thenCorrectTokens()
  throws IOException {
    String file = "src/test/resources/fileTestTokenizer.txt";
   FileReader reader = new FileReader(file);
    StreamTokenizer tokenizer = new StreamTokenizer(reader);

    // token 1
    tokenizer.nextToken();
    assertEquals(StreamTokenizer.TT_WORD, tokenizer.ttype);
    assertEquals("Hello", tokenizer.sval);

    // token 2    
    tokenizer.nextToken();
    assertEquals(StreamTokenizer.TT_NUMBER, tokenizer.ttype);
    assertEquals(1, tokenizer.nval, 0.0000001);

    // token 3
    tokenizer.nextToken();
    assertEquals(StreamTokenizer.TT_EOF, tokenizer.ttype);
    reader.close();
}

8. Sử dụng DataInputStream

Chúng ta có thể sử dụng DataInputStream để đọc các kiểu dữ liệu nhị phân hoặc nguyên thủy từ một tệp.

Thử nghiệm sau đây đọc tệp bằng DataInputStream:

@Test
public void whenReadWithDataInputStream_thenCorrect() throws IOException {
    String expectedValue = "Hello, world!";
    String file ="src/test/resources/fileTest.txt";
    String result = null;

    DataInputStream reader = new DataInputStream(new FileInputStream(file));
    int nBytesToRead = reader.available();
    if(nBytesToRead > 0) {
        byte[] bytes = new byte[nBytesToRead];
        reader.read(bytes);
        result = new String(bytes);
    }

    assertEquals(expectedValue, result);
}

9. Sử dụng FileChannel

Nếu chúng ta đang đọc một tệp lớn, FileChannel có thể nhanh hơn standard IO.

Đoạn mã sau đọc byte dữ liệu từ tệp bằng FileChannel và RandomAccessFile

@Test
public void whenReadWithFileChannel_thenCorrect()
  throws IOException {
    String expected_value = "Hello, world!";
    String file = "src/test/resources/fileTest.txt";
    RandomAccessFile reader = new RandomAccessFile(file, "r");
    FileChannel channel = reader.getChannel();

    int bufferSize = 1024;
    if (bufferSize > channel.size()) {
        bufferSize = (int) channel.size();
    }
    ByteBuffer buff = ByteBuffer.allocate(bufferSize);
    channel.read(buff);
    buff.flip();
    
    assertEquals(expected_value, new String(buff.array()));
    channel.close();
    reader.close();
}

10. Sử dụng UTF-8 Encoded File

Bây giờ hãy xem cách đọc tệp được mã hóa UTF-8 bằng BufferedReader. Trong ví dụ này, chúng ta sẽ đọc một tệp chứa các ký tự tiếng Trung:

@Test
public void whenReadUTFEncodedFile_thenCorrect()
  throws IOException {
    String expected_value = "青空";
    String file = "src/test/resources/fileTestUtf8.txt";
    BufferedReader reader = new BufferedReader
      (new InputStreamReader(new FileInputStream(file), "UTF-8"));
    String currentLine = reader.readLine();
    reader.close();

    assertEquals(expected_value, currentLine);
}

11. Sử dụng URL

Để đọc nội dung từ một URL, chúng tôi sẽ sử dụng URL “/” trong ví dụ của mình:

@Test
public void givenURLName_whenUsingURL_thenFileData() {
    String expectedData = "Baeldung";

    URL urlObject = new URL("/");
    URLConnection urlConnection = urlObject.openConnection();
    InputStream inputStream = urlConnection.getInputStream();
    String data = readFromInputStream(inputStream);

    Assert.assertThat(data, containsString(expectedData));
}

Ngoài ra còn có các cách khác để kết nối với URL. Ở đây chúng tôi đã sử dụng URL và lớp URLConnection có sẵn trong SDK tiêu chuẩn.

12. Sử dụng đọc file từ JAR

Để đọc một tệp nằm bên trong tệp JAR, chúng ta sẽ cần một JAR có tệp bên trong nó. Trong ví dụ của chúng tôi, chúng tôi sẽ đọc “LICENSE.txt” từ tệp “hamcrest-library-1.3.jar”:

@Test
public void givenFileName_whenUsingJarFile_thenFileData() {
    String expectedData = "BSD License";

    Class clazz = Matchers.class;
    InputStream inputStream = clazz.getResourceAsStream("/LICENSE.txt");
    String data = readFromInputStream(inputStream);

    Assert.assertThat(data, containsString(expectedData));
}

Ở đây chúng tôi muốn tải LICENSE.txt nằm trong thư viện Hamcrest, vì vậy chúng tôi sẽ sử dụng lớp Matcher để giúp lấy tài nguyên. Tệp tương tự cũng có thể được tải bằng classloader.

13. Kết luận

Như chúng ta có thể thấy, có nhiều khả năng để tải một tệp và đọc dữ liệu từ nó bằng cách sử dụng Java đơn giản.

Chúng tôi có thể tải tệp từ nhiều vị trí khác nhau như đường dẫn lớp, URL hoặc tệp jar.

Sau đó, chúng ta có thể sử dụng BufferedReader để đọc từng dòng, Scanner để đọc bằng các dấu phân cách khác nhau, StreamTokenizer để đọc tệp thành token, DataInputStream để đọc dữ liệu nhị phân và các kiểu dữ liệu nguyên thủy, SequenceInput Stream để liên kết nhiều tệp thành một luồng, FileChannel để đọc nhanh hơn từ các tập tin lớn, v.v.

Link thảm khảo