Java: Đọc, ghi và tạo tập tin

Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực

Bài viết này thảo luận về các chi tiết của việc đọc, ghi, tạo, và mở tập tin. Ta có rất nhiều các phương thức I/O tập tin để lựa chọn. Để hiểu rõ hơn về API, sơ đồ sau đây sắp xếp các phương thức I/O tập tin theo hướng từ đơn giản đến phức tạp.

Dòng vẽ với tập tin I / O phương pháp sắp xếp từ ít phức tạp (bên trái) đến phức tạp nhất (ở bên phải).
Các phương thức I/O tập tin sắp xếp từ đơn giản đến phức tạp

Ở phía bên trái của sơ đồ là các phương thức tiện ích readAllBytes, readAllLines, và các phương thức write được thiết kế đơn giản, dễ hiểu. Phía bên phải là những phương thức được sử dụng để lặp qua một luồng hoặc các dòng văn bản, chẳng hạn như newBufferedReader, newBufferedWriter, sau đó là newInputStream và newOutputStream. Những phương thức này tương thích với gói java.io. Hướng tiếp về phía bên phải là những phương thức để giao dịch với ByteChannels, SeekableByteChannels, và ByteBuffers, chẳng hạn như phương thức newByteChannel. Cuối cùng là những phương thức sử dụng FileChannel cho các ứng dụng tiên tiến cần khóa tập tin hoặc ánh xạ bộ nhớ I/O.

Lưu ý:  Các phương thức tạo ra một tập tin mới này cho phép bạn chỉ định một tập tùy chọn các thuộc tính khởi tạo cho các tập tin. Ví dụ, trên một hệ thống tập tin hỗ trợ tập chuẩn POSIX (như UNIX), bạn có thể chỉ định một chủ sở hữu tập tin, nhóm chủ sở hữu hoặc quyền tập tin tại thời điểm các tập tin được tạo ra.

Chủ đề của bài viết:

  • Tham số OpenOptions
  • Các phương pháp thường được sử dụng cho tập tin nhỏ
  • Các phương thức I/O đệm cho các tập tin văn bản
  • Phương thức Unbuffered Streams và Interoperable với API java.io
  • Phương cho Channel và ByteBuffers
  • Phương thức tạo tập tin thường xuyên và tạm thời

Tham số OpenOptions

Một số phương thức trong phần này có một tùy chọn là tham số OpenOptions. Tham số này là tùy chọn và API sẽ cung cấp cho bạn biết các hành vi mặc định dành cho phương thức khi nó không được xác định.

Những liệt kê StandardOpenOptions sau đây được hỗ trợ:

  • WRITE - Mở tập tin để ghi.
  • APPEND - Đưa thêm dữ liệu mới vào cuối của tập tin. Tùy chọn này được sử dụng với tùy chọn WRITE hoặc CREATE.
  • TRUNCATE_EXISTING - Cắt tập tin thành 0 byte. Tùy chọn này được sử dụng với tùy chọn WRITE.
  • CREATE_NEW - Tạo ra một tập tin mới và ném một ngoại lệ nếu các tập tin đã tồn tại.
  • CREATE - Mở tập tin nếu nó tồn tại hoặc tạo ra một tập tin mới nếu nó không tồn tại.
  • DELETE_ON_CLOSE - Xóa tập tin khi đóng luồng. Tùy chọn này rất hữu ích cho các tập tin tạm thời.
  • SPARSE - Gợi ý rằng một tập tin mới được tạo ra sẽ được phân tách. Tùy chọn nâng cao này được dùng trên một số hệ thống tập tin, chẳng hạn như NTFS, nơi các tập tin lớn với "khoảng trống" dữ liệu có thể được lưu trữ một cách hiệu quả hơn, nơi những khoảng trống không tiêu thụ không gian đĩa.
  • SYNC - Giữ các tập tin (cả về nội dung và siêu dữ liệu) đồng bộ với các thiết bị lưu trữ bên dưới.
  • DSYNC - Giữ các nội dung tập tin đồng bộ hóa với các thiết bị lưu trữ bên dưới.

Các phương thức thường được sử dụng cho tập tin nhỏ

Đọc tất cả Bytes hoặc Lines từ một tập tin

Nếu bạn có một tập tin nhỏ và bạn muốn đọc toàn bộ nội dung của nó trong một lần truyền, bạn có thể sử dụng phương thức readAllBytes(Path) hoặc readAllLines(Path, Charset). Những phương thức này thực hiện hầu hết các công việc cho bạn, chẳng hạn như mở và đóng luồng, nhưng không có ý định để xử lý các tập tin lớn. Đoạn mã sau đây cho thấy cách sử dụng phương thức readAllBytes:

Path file = ...;
byte[] fileArray;
fileArray = Files.readAllBytes(file);

Viết tất cả các Byte hoặc hoặc các dòng ra tập tin

Bạn có thể sử dụng một trong những phương thức để ghi byte hoặc dòng vào một tập tin.

  • write(Path, byte[], OpenOption...)
  • write(Path, Iterable< extends CharSequence>, Charset, OpenOption...)

Đoạn mã sau đây cho thấy cách dùng phương thức write().

Path file = ...;
byte[] buf = ...;
Files.write(file, buf);

Các phương thức I/O đệm cho các tập tin văn bản

Gói java.nio.file hỗ trợ kênh I/O để di chuyển dữ liệu trong bộ đệm, bỏ qua một số lớp có thể hạn chế luồng I/O.

Đọc một tập tin bằng cách sử dụng luồng I/O đệm

Phương thức newBufferedReader(Path, Charset) mở một tập tin để đọc, trả lại một BufferedReader có thể được sử dụng để đọc văn bản từ một tập tin một cách hiệu quả.

Đoạn mã sau đây cho thấy cách sử dụng phương thức newBufferedReader để đọc từ một tập tin. Các tập tin được mã hóa thành dạng "US-ASCII".

Charset charset = Charset.forName("US-ASCII");
try (BufferedReader reader = Files.newBufferedReader(file, charset)) {
    String line = null;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException x) {
    System.err.format("IOException: %s%n", x);
}

Ghi một tập tin bằng cách sử dụng luồng I/O đệm

Bạn có thể sử dụng phương thức newBufferedWriter(Path, Charset, OpenOption ...) để ghi vào một tập tin bằng cách sử dụng BufferedWriter.

Đoạn mã sau đây cho thấy cách tạo ra một tập tin mã hóa dạng "US-ASCII" sử dụng phương thức này:

Charset charset = Charset.forName("US-ASCII");
String s = ...;
try (BufferedWriter writer = Files.newBufferedWriter(file, charset)) {
    writer.write(s, 0, s.length());
} catch (IOException x) {
    System.err.format("IOException: %s%n", x);
}

Phương thức Unbuffered Streams và Interoperable với API java.io

Đọc một tập tin bằng cách sử dụng luồng I/O

Để mở một tập tin để đọc, bạn có thể sử dụng phương thức newInputStream(Path, OpenOption ...). Phương thức này trả về một dòng đầu vào không có bộ đệm để đọc byte từ tập tin.

Path file = ...;
try (InputStream in = Files.newInputStream(file);
    BufferedReader reader =
      new BufferedReader(new InputStreamReader(in))) {
    String line = null;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException x) {
    System.err.println(x);
}

Tạo và ghi một tập tin bằng cách sử dụng luồng I/O

Bạn có thể tạo một tập tin, thêm vào một tập tin, hoặc ghi vào một tập tin bằng cách sử dụng phương thức newOutputStream(Path, OpenOption ...). Phương thức này sẽ mở ra hoặc tạo ra một tập tin để ghi các byte và trả về một dòng đầu ra không có bộ đệm.

Phương thức này có một tham số tùy chọn là OpenOption. Nếu tùy chọn này không được dùng, và tập tin không tồn tại, thì một tập tin mới sẽ được tạo ra. Nếu tập tin tồn tại thì nó sẽ được cắt ngắn. Tùy chọn này tương đương với cách gọi phương thức với tùy chọn CREATE và TRUNCATE_EXISTING.

Ví dụ sau đây sẽ mở ra một file log. Nếu tập tin không tồn tại, nó sẽ được tạo. Nếu tập tin tồn tại, nó được mở ra để thêm dữ liệu.

import static java.nio.file.StandardOpenOption.*;
import java.nio.file.*;
import java.io.*;

public class LogFileTest {

  public static void main(String[] args) {

    // Convert the string to a
    // byte array.
    String s = "Hello World! ";
    byte data[] = s.getBytes();
    Path p = Paths.get("./logfile.txt");

    try (OutputStream out = new BufferedOutputStream(
      Files.newOutputStream(p, CREATE, APPEND))) {
      out.write(data, 0, data.length);
    } catch (IOException x) {
      System.err.println(x);
    }
  }
}

Phương cho Channel và ByteBuffers

Đọc và ghi tập tin bằng cách sử dụng kênh I/O

Trong khi luồng I/O đọc một ký tự tại một thời thời điểm, thì kênh I/O đọc một bộ đệm tại một thời điểm. Giao diện ByteChannel cung cấp các chức năng cơ bản là read và write. SeekableByteChannel là một ByteChannel có khả năng duy trì một vị trí trong kênh và thay đổi vị trí đó. SeekableByteChannel cũng hỗ trợ cắt bỏ các tập tin liên kết với kênh và truy vấn các tập tin với kích thước của nó.

Khả năng di chuyển đến các điểm khác nhau trong tập tin và sau đó đọc từ hay ghi vào vị trí đó sẽ dẫn đến truy cập ngẫu nhiên một tập tin.

Có hai phương thức để đọc và ghi kênh I/.

  • newByteChannel(Path, OpenOption...)
  • newByteChannel(Path, Set<? extends OpenOption>, FileAttribute<?>...)
Lưu ý:  Các newByteChannel phương pháp trả về một thể hiện của một SeekableByteChannel . Với một hệ thống tập tin mặc định, bạn có thể cast kênh này byte seekable một FileChannel cung cấp quyền truy cập vào nhiều tính năng tiên tiến như lập bản đồ một khu vực của các tập tin trực tiếp vào bộ nhớ truy cập nhanh hơn, khóa một khu vực của các tập tin để các quá trình khác không thể truy cập vào nó, hoặc đọc và viết byte từ một vị trí tuyệt đối mà không ảnh hưởng đến vị trí hiện tại của kênh.

Cả hai phương thức newByteChannel trên đều cho phép bạn chỉ định một danh sách các tùy chọn OpenOption. Các tùy chọn mở tương tự được sử dụng bởi phương thức newOutputStream cũng được hỗ trợ, ngoài việc thêm một tùy chọn: READ được yêu cầu bởi vì SeekableByteChannel hỗ trợ cả đọc và ghi.

Theo quy định thì READ sẽ mở kênh để đọc, WRITE hoặc APPEND mở kênh để ghi. Nếu không có tùy chọn nào được chỉ định, thì kênh sẽ được mở ra để đọc.

Đoạn mã dưới đây đọc một tập tin và in nó vào đầu ra tiêu chuẩn:

// Defaults to READ
try (SeekableByteChannel sbc = Files.newByteChannel(file)) {
    ByteBuffer buf = ByteBuffer.allocate(10);

    // Read the bytes with the proper encoding for this platform.  If
    // you skip this step, you might see something that looks like
    // Chinese characters when you expect Latin-style characters.
    String encoding = System.getProperty("file.encoding");
    while (sbc.read(buf) > 0) {
        buf.rewind();
        System.out.print(Charset.forName(encoding).decode(buf));
        buf.flip();
    }
} catch (IOException x) {
    System.out.println("caught exception: " + x);

Ví dụ sau đây viết cho UNIX và hệ thống tập tin POSIX khác, tạo ra một tập tin đăng nhập với một tập hợp cụ thể các quyền tập tin. Đoạn mã tạo một tập tin đăng nhập hoặc đưa thêm vào file log nếu nó đã tồn tại. file log được tạo ra với quyền đọc/ghi cho chủ sở hữu và quyền chỉ đọc cho nhóm.

import static java.nio.file.StandardOpenOption.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.file.*;
import java.nio.file.attribute.*;
import java.io.*;
import java.util.*;

public class LogFilePermissionsTest {

  public static void main(String[] args) {

    // Create the set of options for appending to the file.
    Set<OpenOption> options = new HashSet<OpenOption>();
    options.add(APPEND);
    options.add(CREATE);

    // Create the custom permissions attribute.
    Set<PosixFilePermission> perms =
      PosixFilePermissions.fromString("rw-r-----");
    FileAttribute<Set<PosixFilePermission>> attr =
      PosixFilePermissions.asFileAttribute(perms);

    // Convert the string to a ByteBuffer.
    String s = "Hello World! ";
    byte data[] = s.getBytes();
    ByteBuffer bb = ByteBuffer.wrap(data);

    Path file = Paths.get("./permissions.log");

    try (SeekableByteChannel sbc =
      Files.newByteChannel(file, options, attr)) {
      sbc.write(bb);
    } catch (IOException x) {
      System.out.println("Exception thrown: " + x);
    }
  }
}

Phương thức tạo tập tin thường xuyên và tạm thời

Tạo tập tin

Bạn có thể tạo một tập tin rỗng với một tập giá trị khởi tạo cho các thuộc tính bằng cách sử dụng phương thức CreateFile(Path, FileAttribute <?>). Ví dụ, nếu vào thời điểm tạo bạn muốn có một tập tin để có một tập hợp các quyền tập tin, thì bạn sử dụng phương thức CreateFile để thực hiện. Nếu bạn không chỉ định bất kỳ các thuộc tính nào, thì tập tin sẽ được tạo ra với các thuộc tính mặc định. Nếu tập tin đã tồn tại, thì CreateFile sẽ ném một ngoại lệ.

Trong một hoạt động atomic đơn, phương thức CreateFile sẽ kiểm tra sự tồn tại của tập tin và tạo tập tin đó với các thuộc tính được chỉ định, điều này sẽ làm cho tiến trình an toàn hơn chống lại các mã độc hại.

Đoạn mã sau tạo một tập tin với các thuộc tính mặc định:

Path file = ...;
try {
    // Create the empty file with default permissions, etc.
    Files.createFile(file);
} catch (FileAlreadyExistsException x) {
    System.err.format("file named %s" +
        " already exists%n", file);
} catch (IOException x) {
    // Some other sort of failure, such as permissions.
    System.err.format("createFile error: %s%n", x);
}

Quyền tập tin POSIX có một ví dụ sử dụng CreateFile(Path, FileAttribute <?>) để tạo ra một tập tin với quyền thiết lập được tạo sẵn.

Bạn cũng có thể tạo một file mới bằng cách sử dụng phương thức newOutputStream. Nếu bạn mở một luồng đầu ra và đóng nó ngay lập tức, thì một tập tin rỗng sẽ được tạo ra.

Tạo tập tin tạm thời

Bạn có thể tạo một tập tin tạm thời bằng cách sử dụng một trong các phương thức createTempFile sau:

  • createTempFile(Path, String, String, FileAttribute <?>)
  • createTempFile(String, String, FileAttribute <?>)

Phương thức đầu tiên cho phép mã lệnh chỉ định một thư mục cho các tập tin tạm thời và phương thức thứ hai tạo ra một file mới trong thư mục tập tin-tạm thời mặc định. Cả hai phương thức đều cho phép bạn chỉ định một hậu tố cho tên tập tin và phương thức đầu tiên cho phép bạn cũng chỉ định một tiền tố. Đoạn mã dưới đây thể hiện cách dùng hai phương thức thứ hai:

try {
    Path tempFile = Files.createTempFile(null, ".myapp");
    System.out.format("The temporary file" +
        " has been created: %s%n", tempFile)
;
} catch (IOException x) {
    System.err.format("IOException: %s%n", x);
}

Kết quả của việc chạy tập tin này sẽ là như sau:

The temporary file has been created: /tmp/509668702974537184.myapp

Định dạng cụ thể của tên tập tin tạm thời là nền tảng cụ thể.

» Tiếp: Tập tin truy cập ngẫu nhiên
« Trước: Quản lý siêu dữ liệu (File và File lưu các thuộc tính)
Các khóa học qua video:
Python SQL Server PHP C# Lập trình C Java HTML5-CSS3-JavaScript
Học trên YouTube <76K/tháng. Đăng ký Hội viên
Viết nhanh hơn - Học tốt hơn
Giải phóng thời gian, khai phóng năng lực
Copied !!!