Кодировка UTF-8 для вывода из консоли в JavaFX TextArea

Я хочу перенаправить вывод в консоли в JavaFX TextArea, и я следую предложению здесь: JavaFX: перенаправить вывод консоли в TextArea, созданный в Конструктор сцен

Я попытался установить кодировку UTF-8 в PrintStream(), но выглядит не очень< /а>. Установка кодировки в UTF-16 немного улучшает ее, но она по-прежнему неразборчива.

В Eclipse IDE предполагаемый текстовый вывод в консоли работает нормально:

KHA khởi đầu phiên giao dịch sáng nay ở mức 23600 điểm, khối lượng giao dịch trong ngày đạt 765 cổ phiếu, tương đương khoảng 18054000 đồng.

Контроллер.java

public class Controller {
    @FXML
    private Button button;

    public Button getButton() {
        return button;
    }

    @FXML
    private TextArea textArea;

    public TextArea getTextArea() {
        return textArea;
    }

    private PrintStream printStream;

    public PrintStream getPrintStream() {
        return printStream;
    }

    public void initialize() {
        textArea.setWrapText(true);
        printStream = new PrintStream(new UITextOutput(textArea), true, StandardCharsets.UTF_8);
    } // Encoding set to UTF-8

    public class UITextOutput extends OutputStream {
        private TextArea text;

        public UITextOutput(TextArea text) {
            this.text = text;
        }

        public void appendText(String valueOf) {
            Platform.runLater(() -> text.appendText(valueOf));
        }

        public void write(int b) throws IOException {
            appendText(String.valueOf((char) b));
        }
    }
}

UI.java

public class UI extends Application {
    @Override
    public void start(Stage stage) {
        try {
            FXMLLoader loader = new FXMLLoader(getClass().getResource("Sample.fxml"));
            Parent root = loader.load();
            Controller control = loader.getController();

            stage.setTitle("Title");
            stage.setScene(new Scene(root));
            stage.show();

            control.getButton().setOnAction(new EventHandler<ActionEvent>() {
                public void handle(ActionEvent event) {
                    try {
                        System.setOut(control.getPrintStream());
                        System.setErr(control.getPrintStream());
                        System.out.println(
                                "KHA khởi đầu phiên giao dịch sáng nay ở mức 23600 điểm, khối lượng giao dịch trong ngày đạt 765 cổ phiếu, tương đương khoảng 18054000 đồng.");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Sample.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.layout.BorderPane?>


<BorderPane prefHeight="339.0" prefWidth="468.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/11.0.1" fx:controller="main.Controller">
   <center>
      <TextArea fx:id="textArea" prefHeight="200.0" prefWidth="200.0" BorderPane.alignment="CENTER" />
   </center>
   <right>
      <Button fx:id="button" mnemonicParsing="false" onAction="#getButton" text="Button" BorderPane.alignment="CENTER" />
   </right>
</BorderPane>

Я все еще новичок в Java, поэтому я не знаком с тем, как именно работает PrintStream или OutputStream. Пожалуйста, простите мое невежество.

Каждое предложение приветствуется.


person Ha Trung Nam Hai    schedule 22.12.2020    source источник
comment
Ну, я попытался добавить textArea.setFont(new Font("TimesRoman", 14)); в initialize() в контроллере, но все равно получаю тот же результат. Какие еще шрифты следует использовать?   -  person Ha Trung Nam Hai    schedule 22.12.2020
comment
Хм. Дело в том, что я не настолько знаком с тонкостями кодирования/декодирования символов. Но я чувствую, что String.valueOf((char) b) может быть проблемой. b — это один байт (значение от 0 до 255). Все же некоторые символы в UTF-8 представлены несколькими байтами, если я не ошибаюсь.   -  person Slaw    schedule 22.12.2020
comment
Этот код, являющийся проблемой, кажется, поддерживается тем фактом, что вызов setText(...) напрямую с вашим строковым литералом работает нормально (по крайней мере, когда исходный файл скомпилирован с -encoding UTF-8).   -  person Slaw    schedule 22.12.2020
comment
Есть ли альтернатива тому, что я мог бы использовать setText(...) для отправки вывода консоли в TextArea? Может быть, поместить вывод консоли в строку (или что-то еще?), а затем setText()?   -  person Ha Trung Nam Hai    schedule 22.12.2020
comment
Это не setText решает проблему. Когда я говорю, что код работает с setText, дело в том, что проблема не в TextArea, а в том, как вы декодируете байты в символы. Я добавил ответ с примером.   -  person Slaw    schedule 22.12.2020
comment
Я все еще новичок в XX, поэтому я не знаком с тем, как именно работает YY, что определяет один из ваших следующих пунктов TODO: проработайте учебник о YY в XX .. просто говорю :)   -  person kleopatra    schedule 23.12.2020


Ответы (2)


Я считаю, что ваша проблема вызвана этим кодом:

public void write(int b) throws IOException {
    appendText(String.valueOf((char) b));
}

Это преобразование каждого отдельного байта в символ. Другими словами, предполагается, что каждый символ представлен одним байтом. Это не обязательно так. Некоторые кодировки, например, UTF-8, могут использовать несколько байтов для представления одного символа. . Им необходимо, если они хотят иметь возможность представлять более 256 символов.

Вам нужно будет правильно декодировать входящие байты. Вместо того, чтобы пытаться сделать это самостоятельно, было бы лучше найти способ использовать что-то вроде BufferedReader. К счастью, это возможно с PipedInputStream и PipedOutputStream. Например:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;
import java.io.UncheckedIOException;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.stage.Stage;

import static java.nio.charset.StandardCharsets.UTF_8;

public class Main extends Application {

  @Override
  public void start(Stage primaryStage) {
    TextArea area = new TextArea();
    area.setWrapText(true);

    redirectStandardOut(area);

    primaryStage.setScene(new Scene(area, 800, 600));
    primaryStage.show();

    System.out.println(
        "KHA khởi đầu phiên giao dịch sáng nay ở mức 23600 điểm, khối lượng giao dịch trong ngày đạt 765 cổ phiếu, tương đương khoảng 18054000 đồng.");
  }

  private void redirectStandardOut(TextArea area) {
    try {
      PipedInputStream in = new PipedInputStream();
      System.setOut(new PrintStream(new PipedOutputStream(in), true, UTF_8));

      Thread thread = new Thread(new StreamReader(in, area));
      thread.setDaemon(true);
      thread.start();
    } catch (IOException ex) {
      throw new UncheckedIOException(ex);
    }
  }

  private static class StreamReader implements Runnable {

    private final StringBuilder buffer = new StringBuilder();
    private boolean notify = true;

    private final BufferedReader reader;
    private final TextArea textArea;

    StreamReader(InputStream input, TextArea textArea) {
      this.reader = new BufferedReader(new InputStreamReader(input, UTF_8));
      this.textArea = textArea;
    }

    @Override
    public void run() {
      try (reader) {
        int charAsInt;
        while ((charAsInt = reader.read()) != -1) {
          synchronized (buffer) {
            buffer.append((char) charAsInt);
            if (notify) {
              notify = false;
              Platform.runLater(this::appendTextToTextArea);
            }
          }
        }
      } catch (IOException ex) {
        throw new UncheckedIOException(ex);
      }
    }

    private void appendTextToTextArea() {
      synchronized (buffer) {
        textArea.appendText(buffer.toString());
        buffer.delete(0, buffer.length());
        notify = true;
      }
    }
  }
}

Использование buffer выше — это попытка избежать переполнения потока приложения JavaFX задачами.

Некоторые другие вещи, которые вы должны принять во внимание:

  • Поскольку вы используете строковый литерал, убедитесь, что вы сохраняете исходный файл с UTF-8 и компилируете код с -encoding UTF-8.
  • Убедитесь, что шрифт, который вы используете с TextArea, может отображать все символы, которые вы хотите.
  • Возможно вам также нужно запустить приложение с -Dfile.encoding=UTF-8, но я не уверен. Я этого не сделал, и это все еще работало для меня.
person Slaw    schedule 22.12.2020
comment
Это прекрасно работает, большое спасибо! - person Ha Trung Nam Hai; 23.12.2020

Попробуйте установить кодировку JVM по умолчанию на UTF-8.

java -Dfile.encoding=UTF-8 -jar YourJarfile.jar

Подробнее см. в этой теме: Установка кодировки символов Java по умолчанию

Если вы не хотите экспортировать свой файл, перейдите в Eclipse Настройки › Общие › Рабочая область и установите кодировку текстового файла на UTF-8 (или другую кодировку, которую вы хотели бы иметь).

Есть еще несколько деталей: Как изменить кодировку текстового файла по умолчанию. в затмении

person Nickitiki    schedule 22.12.2020
comment
Спасибо за предложение. Извините, но это означает, что я должен сначала упаковать свой проект в исполняемый файл .jar, не так ли? И когда jar запускается на другом компьютере, нужно ли снова устанавливать JVM? - person Ha Trung Nam Hai; 22.12.2020
comment
Если кодировка по умолчанию на других платформах отличается от вашей предпочтительной, вам необходимо установить аргументы JVM перед выполнением. - person Nickitiki; 22.12.2020
comment
Что ж, настройка Preferences в Eclipse IDE влияет только на редактор кода и консоль, но не на вывод в TextArea. - person Ha Trung Nam Hai; 22.12.2020