Kodowanie UTF-8 dla danych wyjściowych z konsoli do JavaFX TextArea

Dec 22 2020

Chcę przekierować dane wyjściowe w konsoli do JavaFX TextArea i postępuję zgodnie z sugestią tutaj: JavaFX: Przekieruj dane wyjściowe konsoli do TextArea, który jest tworzony w SceneBuilder

Próbowałem ustawić kodowanie na UTF-8 w PrintStream (), ale nie wygląda to tak dobrze . Ustawienie zestawu znaków na UTF-16 nieco go poprawia, ale nadal jest nieczytelne .

W Eclipse IDE, przypuszczalny tekst wyjściowy w Console wygląda dobrze:

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.

Controller.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>

Wciąż jestem nowy w Javie, więc nie wiem, jak dokładnie działa PrintStream lub OutputStream. Proszę wybaczyć moją ignorancję.

Każda sugestia jest mile widziana.

Odpowiedzi

1 Slaw Dec 23 2020 at 03:29

Uważam, że przyczyną problemu jest ten kod:

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

To jest konwersja każdego pojedynczego bajtu na znak. Innymi słowy, zakłada, że ​​każdy znak jest reprezentowany przez jeden bajt. To niekoniecznie prawda. Niektóre kodowania, takie jak UTF-8 , mogą używać wielu bajtów do reprezentowania pojedynczego znaku. Muszą, jeśli chcą mieć możliwość reprezentowania więcej niż 256 znaków.

Musisz poprawnie zdekodować przychodzące bajty. Zamiast próbować to zrobić samemu, lepiej byłoby znaleźć sposób na użycie czegoś takiego BufferedReader. Na szczęście jest to możliwe dzięki PipedInputStreami PipedOutputStream. Na przykład:

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;
      }
    }
  }
}

Użycie bufferpowyższego jest próbą uniknięcia zalewania wątku aplikacji JavaFX zadaniami.

Kilka innych rzeczy, które musisz wziąć pod uwagę:

  • Ponieważ używasz literału ciągu, upewnij się, że zarówno zapisujesz plik źródłowy w UTF-8, jak i kompilujesz kod z -encoding UTF-8.
  • Upewnij się, że czcionka, której używasz z, TextAreamoże reprezentować wszystkie znaki, które chcesz.
  • Jest to możliwe, trzeba także uruchomić aplikację z -Dfile.encoding=UTF-8, ale nie jestem pewien. Nie zrobiłem i nadal mi to działało.
Nickitiki Dec 22 2020 at 15:47

Spróbuj ustawić domyślne kodowanie JVM na UTF-8.

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

Aby uzyskać więcej informacji, zajrzyj do tego wątku: Ustawianie domyślnego kodowania znaków w języku Java

Jeśli nie chcesz eksportować pliku, przejdź do Preferencji Eclipse > Ogólne> Przestrzeń robocza i ustaw kodowanie pliku tekstowego na UTF-8 (lub kodowanie, które chcesz mieć).

Jest jeszcze kilka szczegółów: Jak zmienić domyślne kodowanie pliku tekstowego w Eclipse