创建自定义OutputStream,正确处理双字节字符

时间:2015-11-10 11:04:37

标签: java utf-8

基本上,我想将控制台输出重定向到javafx TextArea。我现在正在使用此代码。

public static void init() {
    //GUI code. TextArea variable is named textArea
    Console console = new Console(textArea);
    PrintStream ps = new PrintStream(console, true);
    System.setOut(ps);
    System.setErr(ps);
}

public static class Console extends OutputStream {

    private TextArea output;
    private PrintStream out;

    public Console(TextArea ta) {
        this.output = ta;
        out = System.out;
    }

    @Override
    public void write(int i) throws IOException {
        Platform.runLater(() ->  {
            output.appendText(String.valueOf((char) i));
            out.print(String.valueOf((char) i));

        });


    }

但是,有一个问题。 ASCII字符工作正常(因为它们都只使用一个字节)。但是,当我尝试打印需要两个字节进行编码的符号时(例如西里尔字符),它们显然无法正确打印,我得到类似的东西

  

java.io.IOException:￐ㄱ￐ㅈチ￑ツ￐ㅅ￐ㅌㅅ￐ㅍ￐テテテㅄ￐ㅀ￐ツツツチチママママツツツツツツㅈ   ￑テ￐ㅊㅀ￐ㅇ￐￐ㅍ￐ㅍヒヒㅉ￐﾿テテ￑ツ￑フ

有什么方法可以解决这个问题吗?也许通过使用不同的方法?

更新

这就是我最终想出来的。想我是否能以某种方式优化它。

public static class Console extends OutputStream {

        private TextArea output;
        private PrintStream out;
        private ArrayList<Byte> bytes = new ArrayList<>();

        public Console(TextArea ta) {
            this.output = ta;
            out = System.out;
        }

        @Override
        public void write(int i) throws IOException {
            Platform.runLater(() ->  {
                bytes.add((byte)i);

                byte[] array = new byte[bytes.size()];
                int q = 0;
                for (Byte current : bytes) {
                    array[q] = current;
                    q++;
                }
                try {
                    output.setText(new String(array, "UTF-8"));
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                }
                out.write(i);
            });


        }

    }

更新2

经过一些优化后,这就是我最终得到的代码。

//Cut all the imports
public class LogScreen {

    private static TextArea textArea = new TextArea();
    private static List<Byte> bytes = new ArrayList<>();
    //And some other unnecessary variables

    public static void show() {
        update();
        logStage.showAndWait();

    }

    public static void init() {
        //Cut window initialization
        PrintStream ps = new PrintStream(new Console(), true);
        System.setOut(ps);
        System.setErr(ps);
    }

    public static void update() {
        byte[] array = new byte[bytes.size()];
        int q = 0;
        for (Byte current : bytes) {
            array[q] = current;
            q++;
        }
        try {
            textArea.setText(new String(array, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    public static class Console extends OutputStream {
        private PrintStream out;

        public Console() {
            out = System.out;
        }

        @Override
        public void write(int i) throws IOException {
            Platform.runLater(() ->  {
                bytes.add((byte)i);
                out.write(i);
                if (logStage.isShowing()) {
                    update();
                }
            });
        }

        @Override
        public void write(byte[] i) {
            Platform.runLater(() -> {
                for (byte b : i) {
                    bytes.add(b);
                    out.write(b);
                }
                if (logStage.isShowing()) {
                    update();
                }
            });
        }
    }
}

2 个答案:

答案 0 :(得分:0)

它不能像这样工作。

来自OutputStream.write(int)

的javadoc
Writes the specified byte to this output stream. The general contract for write is that one
byte is written to the output stream. The byte to be written is the eight low-order bits
of the argument b. The 24 high-order bits of b are ignored.

对于由两个字节组成的任何字符(例如П - 0xD0,0x9F),这意味着此方法被调用两次。但是,如果使用PrintStream.print(String.valueOf((char) i)),则根据默认编码创建基于这些单字节的字符。在你的例子中导致输出乱码。

不应使用out.print(..),而应使用out.write(int)来写字节而不是字符。

以下代码段将预期输出打印到控制台。

static class Console extends OutputStream {
    private PrintStream out;

    public Console() {
        out = System.out;
    }

    @Override
    public void write(int i) throws IOException {
        out.write(i);
    }
}

public static void main(String[] args) throws Exception {
    Console console = new Console();
    PrintStream ps = new PrintStream(console, true);
    System.setOut(ps);
    System.setErr(ps);
    ps.println("Привет, мир!");
}

答案 1 :(得分:0)

哼......第一种方法看起来更精细,但我建议你覆盖write(byte[] b)public void write(byte[] b, int off, int len)方法。就在您收到一个字节数组时,您可以通过构造函数String(bytes, encoding)将其转换为String。

如何使用write(int b)方法?嗯...通常它不会被使用。但如果真的需要它,那么就需要更多的复杂功能:

您必须实现write(int)以接收单个字节并将其存储以供日后使用。并且,第二次调用它时,您有两个字节可以存储到byte[]并正确转换为String。所以:方法write(int)必须适用于两种状态。