OK, sau một lúc nghiên cứu thì mình cũng biết thế nào rồi:
API System.console không xài được với redirected terminal (VD terminal được tích hợp của IDE), nó trả null, mà nó cũng không có cho tự chỉnh Unicode gì ráo (chỉ có auto detect encoding thôi), bên trên mình nhầm lẫn cho mình xin lỗi.
Ở trên các hệ điều hành họ Unix thì bạn cứ nhập xuất bình thường không cần chỉnh thêm, nó mặc định UTF8 cả.
Ở trên Windows thì khá phức tạp:
- Input sẽ từ UTF-16LE (encoding của HĐH Windows) qua Input Encoder của terminal rồi mới qua được đến Input Decoder của Java (InputStreamReader, Scanner, v.v.). Input Encoding của CMD thì thường là Cp437; trong NetBeans (redirected terminal) thì vì một lý do nào đó, Input Encoding luôn là ISO-8859-1; trong Eclipse thì Input Encoding đi theo Project Setting, mặc định là UTF-8.
- Output thì tương tự, đi từ UTF-16LE (encoding của class String trong Java) qua Output Encoder của Java, rồi mới qua Output Decoder của terminal. Output Encoding của CMD cũng thường là Cp437, trong NetBeans hoặc Eclipse thì đều theo project setting mặc định là UTF-8.
Tóm lại trên Windows thì thường là:
- Input: UTF-16LE 🡲(1)🡲 Cp437 🡲(2)🡲 UTF-16LE
- Output: UTF-16LE 🡲(3)🡲 Cp437 🡲(4)🡲 UTF-16LE
Terminal của NetBeans thì mặc định là:
- Input: UTF-16LE 🡲(1)🡲 ISO-8859-1 🡲(2)🡲 UTF-16LE
- Output: UTF-16LE 🡲(3)🡲 UTF-8 🡲(4)🡲 UTF-16LE
Terminal của các IDE khác:
- Input: UTF-16LE 🡲(1)🡲 UTF-8 🡲(2)🡲 UTF-16LE
- Output: UTF-16LE 🡲(3)🡲 UTF-8 🡲(4)🡲 UTF-16LE
Vậy thì phải điều chỉnh Input Decoder và Encoder của Java cho đúng với Encoding trung gian kia (số (2) và số (3) ) :
Input:
BufferedReader in = new BufferedReader(new InputStreamReader(System.in, "Cp437"));
Dành cho NetBeans
BufferedReader in = new BufferedReader(new InputStreamReader(System.in, "ISO-8859-1"));
Dành cho những IDE còn lại:
BufferedReader in = new BufferedReader(new InputStreamReader(System.in, "UTF-8"));
Ouput thì: (Copy lại của bác Joe)
System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out),true,"Cp437"));
System.out.println("àáä");
Trong bất kì IDE nào thì:
System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out),true,"UTF-8"));
System.out.println("àáä");
Và giờ đến vấn đề khó nhất: làm sao để biết Input Encoder và Ouput Decoder của Console ( số (1) và số (4) ) chúng đang được để ở Encoding nào? Trên Windows, nếu đổi system locale sang Tiếng Nhật chẳng hạn, Input Encoder và Ouput Decoder cũng thay đổi theo. Hay thậm chí là encoding của chính cái redirect terminal. Và có cách nào để dùng code Java để thay đổi tạm thời 2 cái số (1) và số (4) theo ý muốn không (chỉ có Cp1258, UTF-16, UTF-8 là có Tiếng Việt)?
Rất tiếc là Java không có cách nào standard để làm điều đó cả (C# thì có). Link này https://stackoverflow.com/questions/6172972/how-to-get-console-charset có một số cách get bằng Reflection, nhưng không sửa lại được bằng code.
Nếu ko dùng chuẩn Java thì có thể dùng WinAPI như SetConsoleCP, SetConsoleOutputCP, hoặc run chcp (nhưng sẽ gây ảnh hưởng đến các app khác chạy sau nếu có trong cùng cửa số console):
new ProcessBuilder("cmd.exe", "/c", "chcp", "65001")
.inheritIO().start().waitFor();
(Nếu chỉnh CMD input encoding sang UTF-8 thì còn dính bug: https://social.msdn.microsoft.com/Forums/vstudio/en-US/6db367e1-6b39-4c91-bd08-e3779ae5fc23/problems-with-readingwriting-utf8-characters-to-console?forum=vcgeneral, tới Windows 10 mà Microsoft vẫn chưa thèm sửa, may mà không ảnh hưởng gì tới redirected terminal)
Vậy nếu trên Windows thì cách chuẩn nhất là xài chính API của Windows, và thao tác trực tiếp trên UTF-16LE thông qua các API W luôn mà không phải thông qua Decoder và Encoder trung gian. Cái này thì lại là 1 thứ hết sức phức tạp khác, bạn tham khảo 2 link sau:
https://illegalargumentexception.blogspot.com/2009/04/java-unicode-on-windows-command-line.html
https://illegalargumentexception.blogspot.com/2009/04/i18n-unicode-at-windows-command-prompt.html