(Java) 字符 'é' 在 JTextArea 中显示错误

问题描述

第一次发帖,我有一个问题。

我正在使用 JFrame 和 JTextArea,将控制台输出重定向到 JTextArea。在控制台中它显示正确。这是重定向代码

public PrintStream redirectOut() {
    
    OutputStream out = new OutputStream() {
        @Override
        
        public void write(int b) throws IOException {
            String k = String.valueOf((char) b);
            byte[] o = k.getBytes();
            String text = new String(o,"Cp1252");
            output.append(k);
        }
    };
    PrintStream ps = new PrintStream(out);
    System.setout(ps);
    System.setErr(ps);
    return ps;
}

如果我将字符 'é' 以相同的方式放入 TextArea,它会正确显示,但不是这样。这是为什么?有人有解决方案吗? (输出是我的 JTextArea) 谢谢!

解决方法

不太清楚您的多步转换想要实现的目标。

write(int) 方法将接收单个 byte,由于某些原因作为 int 传递。由于该字节源自构造为 PrintStreamnew PrintStream(out),因此它将以系统的默认编码进行编码。

使用相同(系统默认)编码将其转换回 String 的一种方法是

byte[] array = { (byte) b };
String text = new String(array);

但是,有一些事情要记住

  1. 系统默认编码可能不支持所有 unicode 字符。因此,上面的代码是正确的,但仍然可能导致数据丢失。因此,您不应依赖系统默认编码,而应使用能够处理所有字符的显式编码,例如UTF-8,双方。

  2. 这种方法无法处理多字节编码,因为当尝试将多字节序列的单个字节转换回字符串时,您最终会得到无效或错误的字符。可移植程序不能假设知道系统默认编码是否为多字节。当遵循第 1 点的建议时,您将始终以多字节编码结束。您需要一个可以在将多个字节转换为字符串之前累积多个字节的解决方案。这甚至会提高效率。

这是一个完整的解决方案:

public class PrintToTextArea extends ByteArrayOutputStream implements Runnable
{
  public static PrintStream create(JTextArea ta)
  {
    try
    {
      return new PrintStream(new PrintToTextArea(ta),true,"UTF-8");
    }
    catch(UnsupportedEncodingException ex)
    {
      throw new AssertionError("UTF-8 should always be supported",ex);
    }
  }

  private final JTextArea target;

  private PrintToTextArea(JTextArea ta)
  {
    super(100);
    target = ta;
  }
  @Override
  public void flush()
  {
    if(EventQueue.isDispatchThread()) run(); else EventQueue.invokeLater(this);
  }
  @Override
  public synchronized void run()
  {
    target.append(new String(buf,count,StandardCharsets.UTF_8));
    count = 0;
  }
}

它在两边使用 UTF-8,处理所有 unicode 字符,并在 ByteArrayOutputStream 的缓冲区中收集字节,创建一个新的 String 并在 flush 时附加它被调用,这会在换行时自动发生,或者在 flush 被显式调用时发生。

你可以尝试一下

public static void main(String[] args) {
  try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); }
  catch(ReflectiveOperationException | UnsupportedLookAndFeelException ex) {}

  JFrame f = new JFrame();
  JTextArea ta = new JTextArea(40,60);
  f.setContentPane(new JScrollPane(ta));
  PrintStream ps = PrintToTextArea.create(ta);
  System.setOut(ps);
  System.setErr(ps);
  f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  f.pack();
  f.setVisible(true);

  LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
  Thread.dumpStack();
  System.out.println("È");
  LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
  System.out.println("ÄÖÜ");
  LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
  System.out.println("\u263a   \ud83d\ude80");
  LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1));
}