Câu hỏi Làm cách nào để tạo chuỗi Java từ nội dung của tệp?


Tôi đã sử dụng thành ngữ dưới đây một thời gian. Và nó có vẻ là trải rộng nhất, ít nhất là trên các trang web tôi đã truy cập.

Có một cách tốt hơn / khác nhau để đọc một tập tin vào một chuỗi trong Java?

private String readFile(String file) throws IOException {
    BufferedReader reader = new BufferedReader(new FileReader (file));
    String         line = null;
    StringBuilder  stringBuilder = new StringBuilder();
    String         ls = System.getProperty("line.separator");

    try {
        while((line = reader.readLine()) != null) {
            stringBuilder.append(line);
            stringBuilder.append(ls);
        }

        return stringBuilder.toString();
    } finally {
        reader.close();
    }
}

1214
2017-11-28 18:32


gốc


Bất cứ ai có thể giải thích cho tôi một cách rất đơn giản những gì với NIO? Mỗi lần tôi đọc về nó, tôi bị lạc trong lần đề cập thứ n của kênh :( - OscarRyz
hãy nhớ rằng nó không đảm bảo rằng dấu phân tách dòng trong tệp không cần thiết giống như dấu tách dòng của hệ thống. - Henrik Paul
Bạn có thể vui lòng chèn một thử thích hợp cuối cùng mà đóng người đọc? Ai đó thực sự có thể sử dụng ví dụ này và giới thiệu một lỗi trong mã của anh ta. - Hans-Peter Störr
Mã trên có lỗi bổ sung thêm dòng char mới ở dòng cuối cùng. Nó phải là một cái gì đó như sau nếu (line = reader.readLine ())! = Null) {stringBuilder.append (line); } while (line = reader.readLine ())! = null) {stringBuilder.append (ls); stringBuilder.append (dòng); } - Deep
Giới thiệu Java 7 byte[] Files.readAllBytes(file); Đối với những người, những người đề xuất giải pháp 'một dòng' Máy quét: Không yo cần phải đóng nó? - Val


Các câu trả lời:


Đọc tất cả văn bản từ một tệp

Đây là một thành ngữ nhỏ gọn, mạnh mẽ dành cho Java 7, được gói gọn trong một phương thức tiện ích:

static String readFile(String path, Charset encoding) 
  throws IOException 
{
  byte[] encoded = Files.readAllBytes(Paths.get(path));
  return new String(encoded, encoding);
}

Đọc dòng văn bản từ một tệp

Java 7 đã thêm phương pháp tiện lợi để đọc một tệp dưới dạng dòng văn bản, đại diện như một List<String>. Cách tiếp cận này là "mất mát" bởi vì các dấu tách dòng được tách ra từ cuối mỗi dòng.

List<String> lines = Files.readAllLines(Paths.get(path), encoding);

Trong Java 8, BufferedReader đã thêm một phương pháp mới, lines() để tạo ra Stream<String>. Nếu một IOException gặp phải trong khi đọc tệp, nó được bao bọc trong một UncheckedIOException, kể từ đó Stream không chấp nhận lambdas mà ném ngoại lệ kiểm tra.

try (BufferedReader r = Files.newBufferedReader(path, encoding)) {
  r.lines().forEach(System.out::println);
}

Cũng có một Files.lines() phương pháp làm điều gì đó rất giống nhau, trả lại Stream<String> trực tiếp. Nhưng tôi không thích nó. Các Stream cần một close() gọi điện; đây là tài liệu kém trên API và tôi nghi ngờ nhiều người thậm chí không nhận thấy Stream có một close() phương pháp. Vì vậy, mã của bạn sẽ trông rất giống nhau, như thế này:

try (Stream<String> lines = Files.lines(path, encoding)) {
  lines.forEach(System.out::println);
}

Sự khác biệt là bạn có một Stream được gán cho một biến và tôi cố gắng tránh điều đó như một thực tế để tôi không vô tình cố gắng gọi luồng hai lần.

Sử dụng bộ nhớ

Phương thức đầu tiên, bảo tồn các ngắt dòng, có thể tạm thời yêu cầu bộ nhớ nhiều lần kích thước của tệp, bởi vì trong một thời gian ngắn nội dung tệp thô (một mảng byte) và các ký tự được giải mã (mỗi ký tự là 16 bit ngay cả khi được mã hóa 8 bit trong tệp) nằm trong bộ nhớ cùng một lúc. Nó là an toàn nhất để áp dụng cho các tập tin mà bạn biết là nhỏ so với bộ nhớ có sẵn.

Phương pháp thứ hai, các dòng đọc, thường là bộ nhớ hiệu quả hơn, vì bộ đệm byte đầu vào để giải mã không cần chứa toàn bộ tệp. Tuy nhiên, nó vẫn không thích hợp cho các tệp rất lớn so với bộ nhớ có sẵn.

Để đọc các tệp lớn, bạn cần một thiết kế khác cho chương trình của bạn, một chương trình đọc một đoạn văn bản từ một luồng, xử lý nó và sau đó chuyển sang phần tiếp theo, sử dụng lại cùng một khối bộ nhớ có kích thước cố định. Ở đây, "lớn" phụ thuộc vào thông số kỹ thuật của máy tính. Ngày nay, ngưỡng này có thể là nhiều gigabyte RAM. Phương pháp thứ ba, sử dụng một Stream<String> là một cách để làm điều này, nếu đầu vào của bạn "hồ sơ" xảy ra được các dòng cá nhân. (Sử dụng readLine() phương pháp của BufferedReader là thủ tục tương đương với phương pháp này.)

Mã hóa ký tự

Một điều bị thiếu trong mẫu trong bài gốc là mã hóa ký tự. Có một số trường hợp đặc biệt, nơi nền tảng mặc định là những gì bạn muốn, nhưng chúng rất hiếm, và bạn sẽ có thể biện minh cho sự lựa chọn của bạn.

Các StandardCharsets lớp xác định một số hằng số cho các mã hóa được yêu cầu của tất cả các thời gian chạy Java:

String content = readFile("test.txt", StandardCharsets.UTF_8);

Mặc định nền tảng có sẵn từ các Charsetlớp học chinh no:

String content = readFile("test.txt", Charset.defaultCharset());

Lưu ý: Câu trả lời này phần lớn thay thế phiên bản Java 6 của tôi. Tiện ích của Java 7 đơn giản hóa một cách an toàn mã và câu trả lời cũ, sử dụng bộ đệm byte được ánh xạ, ngăn chặn tệp được đọc bị xóa cho đến khi bộ đệm được ánh xạ được thu thập rác. Bạn có thể xem phiên bản cũ qua liên kết "đã chỉnh sửa" trên câu trả lời này.


1256
2017-11-28 18:56



Về mặt kỹ thuật, nó là O (n) trong thời gian và không gian. Chất lượng, do yêu cầu bất biến của Strings, nó khá khó khăn về bộ nhớ; tạm thời có hai bản sao của dữ liệu char trong bộ nhớ, cộng với căn phòng cho các byte được mã hóa. Giả sử một số mã hóa byte đơn, nó sẽ (tạm thời) yêu cầu 5 byte bộ nhớ cho mỗi ký tự trong tệp. Kể từ khi câu hỏi yêu cầu cụ thể cho một String, đó là những gì tôi hiển thị, nhưng nếu bạn có thể làm việc với CharBuffer trả về bởi "giải mã", yêu cầu bộ nhớ là ít hơn nhiều. Theo thời gian, tôi không nghĩ bạn sẽ tìm thấy gì nhanh hơn trong các thư viện Java lõi. - erickson
Có thể typo? NIO có một lớp Charset (không phải CharSet) được gọi là java.nio.charset.Charset. Đây có phải là điều CharSet nên có? - Jonathan Wright
Lưu ý: sau khi thực hiện một chút mã, tôi phát hiện ra rằng bạn không thể xóa tệp một cách đáng tin cậy ngay sau khi đọc nó bằng phương pháp này, điều này có thể không phải là vấn đề trong một số trường hợp, nhưng không phải của tôi. Có thể nó liên quan đến vấn đề này: bugs.sun.com/bugdatabase/view_bug.do?bug_id=4715154 ? Cuối cùng tôi đã đi với đề xuất của Jon Skeet mà không bị lỗi này. Dù sao, tôi chỉ muốn cung cấp thông tin, cho những người khác, chỉ trong trường hợp ... - Sébastien Nussbaumer
@ Sébastien Nussbaumer: Tôi cũng gặp phải vấn đề này. Tuyệt vời là lỗi đã được đánh dấu "Sẽ không khắc phục". Điều này về cơ bản có nghĩa là FileChannel#map nói chung là không sử dụng được. - Joonas Pulakka
@ Sébastien Nussbaumer: Lỗi này đã bị xóa khỏi Cơ sở dữ liệu Oracle / Sun Bug: "Lỗi này không khả dụng". Google đã lưu trữ trang web tại webcache.googleusercontent.com/search?q=cache:bugs.sun.com/… - bobndrew


Commons FileUtils.readFileToString:

public static String readFileToString(File file)
                       throws IOException

Đọc nội dung của một tệp thành một Chuỗi bằng cách sử dụng mã hóa mặc định   cho máy ảo. Tệp luôn bị đóng.

Thông số:

  • file - tệp cần đọc, không được rỗng

Trả về:   nội dung tệp, không bao giờ trống

Ném:    - - IOException - trong trường hợp lỗi I / O

Kể từ:   Commons IO 1.3.1

Mã được sử dụng (gián tiếp) bởi lớp đó là:

IOUtils.java Dưới Giấy phép Apache 2.0.

public static long copyLarge(InputStream input, OutputStream output)
       throws IOException {
   byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
   long count = 0;
   int n = 0;
   while (-1 != (n = input.read(buffer))) {
       output.write(buffer, 0, n);
       count += n;
   }
   return count;
}

Nó rất giống với cái được Ritche_W sử dụng.


296
2017-11-28 18:44



Đó là trong lớp org.apache.commons.io.FileUtils - Cyrille Ka
Tôi đang sử dụng FileUtils quá, nhưng tôi tự hỏi những gì là tốt hơn betwwen bằng cách sử dụng FileUtils hoặc câu trả lời nio được chấp nhận? - Guillaume
@Guillaume: Câu hỏi lớn nhất là liệu bạn có cảm thấy thoải mái khi có sự phụ thuộc vào thư viện của bên thứ 3 hay không. Nếu bạn có Commons IO hoặc Trái ổi trong dự án của bạn, sau đó sử dụng nó (chỉ để đơn giản về mã; nếu không có khả năng sẽ không có sự khác biệt đáng chú ý). - Jonik
của bạn URL trình diễn FileUtils#readFileToString được không dùng nữa - Kevin Meredith
@KevinMeredith: Nó cho thấy readFileToString(File file) không được chấp nhận. readFileToString(File file,Charset encoding) là sự thay thế. - Ian


Từ trang này một giải pháp rất nạc:

Scanner scanner = new Scanner( new File("poem.txt") );
String text = scanner.useDelimiter("\\A").next();
scanner.close(); // Put this call in a finally block

hoặc là

Scanner scanner = new Scanner( new File("poem.txt"), "UTF-8" );
String text = scanner.useDelimiter("\\A").next();
scanner.close(); // Put this call in a finally block

Nếu bạn muốn đặt bộ ký tự


159
2017-09-16 20:02



\\ Một tác phẩm vì không có "đầu khác của tập tin", vì vậy bạn đang trong thực tế đọc các mã thông báo cuối cùng ... mà cũng là người đầu tiên. Không bao giờ thử với \\ Z. Cũng lưu ý bạn có thể đọc bất cứ điều gì có thể đọc được, như Files, InputStreams, kênh ... Tôi đôi khi sử dụng mã này để đọc từ cửa sổ hiển thị nhật thực, khi tôi không chắc là mình đang đọc một tệp hay tệp khác. .yes, classpath làm tôi bối rối. - Pablo Grisafi
Là poster, tôi có thể nói rằng tôi thực sự không biết nếu và khi tập tin được đóng đúng ... Tôi không bao giờ viết mã này trong mã sản xuất, tôi chỉ sử dụng nó để kiểm tra hoặc gỡ lỗi. - Pablo Grisafi
Nó có giới hạn 1024 ký tự mà tôi nghĩ - Whimusical
Máy quét thực hiện Closeable (nó gọi đóng trên nguồn) - vì vậy trong khi thanh lịch nó không nên thực sự là một một lót. Kích thước mặc định của bộ đệm là 1024, nhưng Máy quét sẽ tăng kích thước khi cần thiết (xem Máy quét # makeSpace ()) - earcam
Điều này không thành công cho các tệp rỗng với một java.util.NoSuchElementException. - SpaceTrucker


Nếu bạn đang tìm kiếm giải pháp thay thế không liên quan đến thư viện của bên thứ ba (ví dụ: Commons I / O), bạn có thể dùng Máy quét lớp học:

private String readFile(String pathname) throws IOException {

    File file = new File(pathname);
    StringBuilder fileContents = new StringBuilder((int)file.length());
    Scanner scanner = new Scanner(file);
    String lineSeparator = System.getProperty("line.separator");

    try {
        while(scanner.hasNextLine()) {
            fileContents.append(scanner.nextLine() + lineSeparator);
        }
        return fileContents.toString();
    } finally {
        scanner.close();
    }
}

68
2017-11-28 19:00



Tôi nghĩ đây là cách tốt nhất. Kiểm tra java.sun.com/docs/books/tutorial/essential/io/scanning.html - Tarski
Các nhà xây dựng Scanner chấp nhận một String không xử lý chuỗi như tên của một tập tin để đọc, nhưng là văn bản được quét. Tôi luôn mắc lỗi đó. : - / - Alan Moore
@ Alan, bắt tốt. Tôi đã chỉnh sửa câu trả lời của Don một chút để sửa lỗi đó (tôi hy vọng). - Jonik
fileContents.append (scanner.nextLine ()). append (lineSeparator); - ban-geoengineering
Thay đổi câu lệnh khởi tạo thành Scanner scanner = new Scanner((Readable) new BufferedReader(new FileReader(file)));. Nếu không, bạn chỉ có thể nắm bắt một phần của tệp. - Wei Yang


Trái ổi có một phương pháp tương tự như phương pháp từ Commons IOUtils mà Willi aus Rohr đã đề cập:

import com.google.common.base.Charsets;
import com.google.common.io.Files;

// ...

String text = Files.toString(new File(path), Charsets.UTF_8);

EDIT của Oscar Reyes

Đây là mã cơ bản (được đơn giản hóa) trên thư viện được trích dẫn:

InputStream in = new FileInputStream(file);
byte[] b  = new byte[file.length()];
int len = b.length;
int total = 0;

while (total < len) {
  int result = in.read(b, total, len - total);
  if (result == -1) {
    break;
  }
  total += result;
}

return new String( b , Charsets.UTF_8 );

Chỉnh sửa (bởi Jonik): Ở trên không khớp với mã nguồn của các phiên bản Guava gần đây. Đối với nguồn hiện tại, hãy xem các lớp học Các tập tin, CharStreams, ByteSource và CharSource trong com.google.common.io gói.


63
2018-04-16 14:33



Mã này đã truyền từ lâu đến int, có thể bật lên một số hành vi điên rồ với các tệp lớn. Có thêm không gian và bạn đóng cửa đầu vào ở đâu? - M-T-A
@ M-T-A: Luồng Là đóng, lưu ý việc sử dụng Closer trong CharSource. Mã trong câu trả lời không phải là nguồn Guava hiện tại, thực tế. - Jonik


import java.nio.file.Files;

.......

 String readFile(String filename) {
            File f = new File(filename);
            try {
                byte[] bytes = Files.readAllBytes(f.toPath());
                return new String(bytes,"UTF-8");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return "";
    }

50
2017-10-29 08:51



Hoặc thậm chí đơn giản hơn: new String(Files.readAllBytes(FileSystems.getDefault().getPath( filename)));
hoặc là new String(Files.readAllBytes(Paths.get(filename))); :-) - assafmo
Chơi tốt, và để cứu anh chàng tiếp theo của Googling, Paths rõ ràng là 1,7+ như là FileSystems. (Dang nó!) - ruffin
Đó là một sự xấu hổ câu trả lời này không có nhiều phiếu bầu hơn. Tôi đang tìm cách nhanh nhất và đơn giản nhất để có được một tệp văn bản thành một Chuỗi. Đây là nó và nếu tôi không di chuyển xuống và xuống, tôi sẽ bỏ lỡ nó. OP nên xem xét chấp nhận câu trả lời này để chuyển nó lên đầu. - Thorn
@Thorn Câu trả lời này có xử lý lỗi khủng khiếp. Không sử dụng phương pháp này trong mã sản xuất hoặc tốt hơn: không bao giờ. - xehpuk


Nếu bạn cần xử lý chuỗi (xử lý song song) Java 8 có API luồng lớn.

String result = Files.lines(Paths.get("file.txt"))
                    .parallel() // for parallel processing 
                    .map(String::trim) // to change line   
                    .filter(line -> line.length() > 2) // to filter some lines by a predicate                        
                    .collect(Collectors.joining()); // to join lines

Các ví dụ khác có sẵn trong các mẫu JDK sample/lambda/BulkDataOperations có thể tải xuống từ Trang tải xuống Oracle Java SE 8 

Một ví dụ khác

String out = String.join("\n", Files.readAllLines(Paths.get("file.txt")));

44
2017-11-28 19:56



Liệu () xảy ra sau khi bạn đọc các dòng hoặc trước đó? - Istvan
Công việc thực sự bắt đầu kể từ khi thu thập hoạt động đầu cuối (...) được gọi. Luồng được tạo thành một cách lười biếng theo từng dòng. Không cần đọc toàn bộ tệp trong bộ nhớ trước khi xử lý (ví dụ: lọc và ánh xạ). - Andrei N
cắt trước khi chọn các dòng không trống? - Thorbjørn Ravn Andersen


Mã đó sẽ bình thường hóa ngắt dòng, có thể hoặc không thể là những gì bạn thực sự muốn làm.

Đây là một thay thế mà không làm điều đó, và đó là (IMO) đơn giản để hiểu hơn mã NIO (mặc dù nó vẫn còn sử dụng java.nio.charset.Charset):

public static String readFile(String file, String csName)
            throws IOException {
    Charset cs = Charset.forName(csName);
    return readFile(file, cs);
}

public static String readFile(String file, Charset cs)
            throws IOException {
    // No real need to close the BufferedReader/InputStreamReader
    // as they're only wrapping the stream
    FileInputStream stream = new FileInputStream(file);
    try {
        Reader reader = new BufferedReader(new InputStreamReader(stream, cs));
        StringBuilder builder = new StringBuilder();
        char[] buffer = new char[8192];
        int read;
        while ((read = reader.read(buffer, 0, buffer.length)) > 0) {
            builder.append(buffer, 0, read);
        }
        return builder.toString();
    } finally {
        // Potential issue here: if this throws an IOException,
        // it will mask any others. Normally I'd use a utility
        // method which would log exceptions and swallow them
        stream.close();
    }        
}

44
2017-10-28 07:04



Cái nào là mã "đó"? - OscarRyz
Mã trong câu hỏi. - Jon Skeet
Hãy tha thứ cho tôi để làm sống lại một bình luận cũ này, nhưng ý của bạn là truyền vào một đối tượng String có tên là "file", hay nó phải là một đối tượng File thay vào đó? - Bryan Larson