5

Tôi đang thử nghiệm với trường hợp cạnh mà chúng ta đang thấy trong quá trình sản xuất. Chúng tôi có một mô hình kinh doanh nơi khách hàng tạo tệp văn bản và sau đó FTP chúng vào máy chủ của chúng tôi. Chúng tôi nhập các tệp đó và xử lý chúng trên chương trình phụ trợ Java của chúng tôi (chạy trên các máy CentOS). Hầu hết (95% +) khách hàng của chúng tôi biết để tạo ra các tệp này trong UTF-8, đó là những gì chúng tôi muốn. Tuy nhiên chúng tôi có một vài khách hàng cứng đầu (nhưng các tài khoản lớn) tạo ra các tệp này trên máy Windows với bộ ký tự CP1252. Không có vấn đề mặc dù, chúng tôi đã cấu hình libs bên thứ 3 của chúng tôi (đó là những gì làm hầu hết các "chế biến" làm việc cho chúng tôi) để xử lý đầu vào trong bất kỳ ký tự thiết lập thông qua một số doo ma thuật doo.Java không thể xem tệp trên hệ thống tệp có chứa các ký tự không hợp lệ

Thỉnh thoảng, chúng tôi thấy một tập tin xuất hiện có ký tự UTF-8 bất hợp pháp (CP1252) trong tên của nó. Khi phần mềm của chúng tôi cố gắng đọc những tập tin từ máy chủ FTP các phương pháp thông thường của file nghẹn đọc và ném một FileNotFoundException:

File f = getFileFromFTPServer(); 
FileReader fReader = new FileReader(f); 

String line = fReader.readLine(); 
// ...etc. 

Các trường hợp ngoại lệ giống như thế này:

java.io.FileNotFoundException: /path/to/file/some-text-blah?blah.xml (No such file or directory) at java.io.FileInputStream.open(Native Method) at 
java.io.FileInputStream.(FileInputStream.java:120) at java.io.FileReader.(FileReader.java:55) at com.myorg.backend.app.InputFileProcessor.run(InputFileProcessor.java:60) at 
java.lang.Thread.run(Thread.java:662) 

Vì vậy, những gì tôi nghĩ đang xảy ra là bởi vì các tập tin tên chính nó chứa ký tự không hợp pháp, chúng tôi thậm chí không bao giờ thậm chí có thể đọc nó ở nơi đầu tiên. Nếu chúng ta có thể, thì bất kể nội dung của tập tin, phần mềm của chúng ta sẽ có thể xử lý nó một cách chính xác. Vì vậy, đây thực sự là một vấn đề với việc đọc các tên tập tin với các ký tự UTF-8 bất hợp pháp trong chúng. Là một trường hợp thử nghiệm, tôi đã tạo một "ứng dụng Java" rất đơn giản để triển khai trên một trong các máy chủ của chúng tôi và kiểm tra một số thứ (mã nguồn được cung cấp bên dưới). Sau đó tôi đăng nhập vào máy Windows và tạo một tệp thử nghiệm và đặt tên là test£.txt. Chú ý ký tự sau "test" trong tên tập tin. Đây là Alt-0163. Tôi FTP này đến máy chủ của chúng tôi, và khi tôi chạy ls -ltr trên thư mục mẹ của nó, tôi đã ngạc nhiên khi thấy nó được liệt kê là test?.txt.

Trước khi tôi đi thêm nữa, đây là Java "ứng dụng" tôi đã viết để thử nghiệm/tái tạo vấn đề này:

public Driver { 
    public static void main(String[] args) { 
     Driver d = new Driver(); 
     d.run(args[0]);  // I know this is bad, but its fine for our purposes here 
    } 

    private void run(String fileName) { 
     InputStreamReader isr = null; 
     BufferedReader buffReader = null; 
     FileInputStream fis = null; 
     String firstLineOfFile = "default"; 

     System.out.println("Processing " + fileName); 

     try { 
      System.out.println("Attempting UTF-8..."); 

      fis = new FileInputStream(fileName); 
      isr = new InputStreamReader(fis, Charset.forName("UTF-8")); 
      buffReader = new BufferedReader(isr); 

      firstLineOfFile = buffReader.readLine(); 

      System.out.println("UTF-8 worked and first line of file is : " + firstLineOfFile); 
     } 
     catch(IOException io1) { 
      // UTF-8 failed; try CP1252. 
      try { 
       System.out.println("UTF-8 failed. Attempting Windows-1252...(" + io1.getMessage() + ")"); 

       fis = new FileInputStream(fileName); 
       // I've also tried variations "WINDOWS-1252", "Windows-1252", "CP1252", "Cp1252", "cp1252" 
       isr = new InputStreamReader(fis, Charset.forName("windows-1252")); 
       buffReader = new BufferedReader(isr); 

       firstLineOfFile = buffReader.readLine(); 

       System.out.println("Windows-1252 worked and first line of file is : " + firstLineOfFile); 
      } 
      catch(IOException io2) { 
       // Both UTF-8 and CP1252 failed... 
       System.out.println("Both UTF-8 and Windows-1252 failed. Could not read file. (" + io2.getMessage() + ")"); 
      } 
     } 
    } 
} 

Khi tôi chạy từ nhà ga (java -cp . com/Driver t*), tôi nhận được kết quả như sau:

Processing test�.txt 
Attempting UTF-8... 
UTF-8 failed. Attempting Windows-1252...(test�.txt (No such file or directory)) 
Both UTF-8 and Windows-1252 failed. Could not read file.(test�.txt (No such file or directory)) 

test�.txt?!?! Tôi đã làm một số nghiên cứu và thấy rằng "�" là ký tự thay thế Unicode \uFFFD. Vì vậy, tôi đoán điều đang xảy ra là máy chủ CentOS FTP không biết cách xử lý Alt-0163 (£) và do đó nó thay thế bằng \uFFFD (�). Nhưng tôi không hiểu tại sao ls -ltr hiển thị một tệp có tên là test?.txt ...

Trong mọi trường hợp, dường như giải pháp là thêm một số logic tìm kiếm sự tồn tại của ký tự này trong tên tệp và nếu tìm thấy , đổi tên tệp thành một thứ khác (như có thể thực hiện một chuỗi replaceAll("\uFFFD", "_") hoặc một cái gì đó tương tự) mà hệ thống có thể đọc và xử lý.

Vấn đề là Java thậm chí không xem tệp này trên hệ thống tệp. CentOS biết tập tin là có (test?.txt), nhưng khi tập tin đó được truyền vào Java, Java diễn giải nó là test�.txt và vì một số lý do No such file or directory ...

Tôi làm cách nào để có được Java để xem tệp này để tôi có thể thực hiện File::renameTo(String) trên đó? Xin lỗi cho cốt truyện ở đây nhưng tôi cảm thấy nó có liên quan vì mọi chi tiết đều có trong kịch bản này. Cảm ơn trước!

+0

để bạn không thể liệt kê các tệp trong thư mục, sau đó xem có "ký tự lẻ" trong tên của chúng và đổi tên chúng thành "dấu thời gian + random.something" với tệp.renameTo? –

+0

@MarkusMikkolainen - bạn đang nói về việc này bằng tay? Nếu bạn không nói ngôn ngữ/kịch bản nào? – IAmYourFaja

+0

Tôi khuyên bạn nên sử dụng đối tượng Tệp thay vì truyền tên tệp. mà có thể sẽ ngăn chặn bất kỳ tham nhũng tên tập tin. –

Trả lời

5

Chào mừng bạn đến với thế giới tuyệt vời của mã hóa văn bản. Bạn có một số cấp độ của vấn đề và bạn cần phải sắp xếp từng người trong số họ ra một cách riêng lẻ.

Đầu tiên, tên tệp trên đĩa là gì? Nó có chứa các trình tự thoát UTF-8 hợp lệ hay không?

Sự cố ở đây là bạn cần tên tệp chính xác hoặc hệ thống tệp Windows đơn giản sẽ không thể tìm thấy tệp. Ngày đầu đó, Windows có thể cố gắng chuyển đổi các ký tự không hợp lệ trong tên tệp thành Unicode \uFFFD vì vậy bất kể bạn thử làm gì, bạn sẽ không thể tải tệp (vì không có tệp nào có \uFFFD trong đó trên đĩa).

Làm cách nào có thể? Điều này xảy ra vì ánh xạ không phải là hai chiều. Khi Windows tải tên tệp từ đĩa, nó sẽ thay thế test�.txt bằng test\uFFFD.txt và cung cấp cho bạn tên đó. Khi bạn yêu cầu Windows mở test\uFFFD.txt, nó sẽ không thể tìm thấy tệp vì không có tệp nào có tên như vậy (chỉ có test�.txt). Không có cách nào để bạn tìm ra tên thật của tập tin.

Giải pháp? Bạn có thể mở lời nhắc dos và đổi tên tệp bằng mẫu ren test*.txt test.txt. Vì mẫu chỉ khớp với một tệp duy nhất, mẫu đó sẽ hoạt động. Nhưng bạn sẽ không thể thực hiện tương tự từ trình duyệt Windows Explorer vì nó cũng không thể tìm thấy tệp.

Bước tiếp theo: FTP. FTP là một giao thức cho con người - nó không thích hợp để trao đổi dữ liệu tự động. Loại bỏ FTP. Tôi không biết bạn sẽ tốn bao nhiêu tiền nhưng nó luôn đáng giá. Sử dụng SFTP, scp hoặc FTAPI.

Một trong những vấn đề có thể là FTP chuyển tên tệp dưới dạng ASCII. Không có umlauts được cho phép trong giao thức FTP ... hay đúng hơn, FTP không mong đợi bất kỳ. Nếu bạn may mắn, ứng dụng FTP của bạn sẽ từ chối chuyển tệp nhưng hầu hết chỉ đơn giản là lỗi. Nhưng khi chúng tồn tại, FTP sẽ chỉ làm ... một cái gì đó. Bất cứ điều gì có thể được. Các hiệu ứng thông thường ở đây là các tệp có Unicode trong tên được mã hóa hai lần dưới dạng UTF-8 hoặc Unicode được thay thế bằng ? (\u003f).

Hoặc ứng dụng khách FTP Java có thể sử dụng new String(bytes) để tạo một chuỗi từ tên tệp FTP sẽ hiếp dâm các byte nghèo bằng mã hóa mặc định của Hệ thống - không đẹp.

Giải pháp:

  1. Sử dụng một máy chủ FTP mà bác bỏ các file với các nhân vật bất hợp pháp trong tên của họ hoặc thay thế những nhân vật này đến một cái gì đó mà không nhầm lẫn giữa hệ thống tập tin/OS.
  2. Sử dụng hệ thống tệp xử lý đúng các tệp có tên lạ. Điều đó thường có nghĩa là để loại bỏ Windows trên máy chủ.
  3. Đảm bảo người dùng chỉ có thể tải lên một thư mục duy nhất và thư mục này chỉ có thể chứa một tệp. Bằng cách đó, bạn có thể sử dụng một kịch bản shell nhỏ và các mẫu để đổi tên nó thành một thứ mà bạn có thể đọc.
1

Đó là một lỗi trong tệp old-skool java Tệp api, có thể chỉ trên máy mac? Dù sao, java.nio api mới hoạt động tốt hơn nhiều.Tôi có một số tệp chứa các ký tự unicode không tải được bằng các lớp java.io .... Sau khi chuyển đổi tất cả mã của tôi để sử dụng java.nio.Path mọi thứ bắt đầu hoạt động. Và tôi đã thay thế tệp Apache FileUtils (có cùng vấn đề) với java.nio.Files ...

Đảm bảo đọc và ghi nội dung của tệp bằng bộ ký tự thích hợp, ví dụ: Files.readAllLines (myPath, StandardCharsets.UTF_8)

Các vấn đề liên quan