从java中的标准输入读取字符串和原始字节

时间:2013-01-07 17:27:50

标签: java inputstream

我有一个程序,它将通过System.in从外部源接收信息。有两种输入模式:线路模式和原始模式。在行模式下,输入只是一系列UTF-8字符串,每个字符串都以换行符结束。在线路模式下的某个时刻,我将收到通知,即我将接收N字节的原始数据。此时输入切换到原始模式,我接收到N字节的原始二进制数据,这些数据不是有效的UTF-8字符。在此之后,它将返回到行模式。

有没有办法轻松切换读取字符串和读取原始数据?我唯一的想法是逐字节读取一个InputStream并在我去的时候转换为字符。有没有办法用多种类型的输入流包装System.in?我觉得从两个不同的包装中读取会导致问题。

(已修复)更新:

我尝试过parsifal的建议,但遇到了问题。为了模拟切换输入模式,我修改了我的测试工具。 (我意识到我的另一个进程最终也需要以这种方式输出。)我不知道问题是由发送端还是接收端引起的。当我在输出模式之间切换时,它似乎没有正确读取字节。此外,它始终显示相同的字节值。以下是一些代码摘录:

FIX:问题是显然你不能太快地从OutputStreamWriter切换到OutputStream。我在发送原始字节之前添加了1ms的sleep命令,问题解决了!

测试工具:

Process p = processList.get(pubName); //Stored list of started Processes
OutputStream o = p.getOutputStream(); //Returns OutputStream which feeds into stdin
out = new OutputStreamWriter(runPublisher.getOutputStream());

byte[] payload = new byte[25];
out.write("\nPAYLOAD\nRAW\n"); // "RAW\n" signals raw mode
out.write(String.valueOf(payload.length) + "\n");
out.flush();
Thread.sleep(1); //This fixed the problem I was having.
System.out.println(Arrays.toString(payload));
o.write(payload);
o.flush();

客户端:

InputStreamReader inReader = new InputStreamReader(System.in);

while(true){
    try{
        if((chIn = inReader.read())!= -1){
            if(chIn == (int)'\n'){
                if(rawMode){
                    if(strIn.equals("ENDRAW"))
                        rawMode = false;
                    else{
                        System.out.println(strIn);
                        //Exception on next line
                        int rawSize = Integer.parseInt(strIn);
                        payload = new byte[rawSize];
                        int t = System.in.read(payload);
                        System.out.println("Read " + t + " bytes");
                        System.out.print(Arrays.toString(payload));
                    }
                }else if(strIn.startsWith("RAW")){
                    rawMode = true;
                }else {
                    // Do other things
                }
                strIn = "";
            }else
                strIn += (char)chIn;
        }else
            break;
    }catch(IOException e){break;}
}

输出(在添加Sleep语句之前)如下所示:

测试线束:
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ]

客户端:
25个
读取9个字节
[83,72,85,84,68,79,87,78,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ]

Exception in thread "main" java.lang.NumberFormatException: For input string: "
    at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
    at java.lang.Integer.parseInt(Integer.java:470)
    at java.lang.Integer.parseInt(Integer.java:514)
    at myClass.handleCommand(myClass.java:249)

2 个答案:

答案 0 :(得分:3)

您可以使用指定“utf-8”编码的System.in包装InputStreamReader,然后逐个字符地读取。将字符累积到StringBuilder并在适当的时候发送(当您看到'\n'时,这可能是基于建筑商的测试)。

当您想要读取二进制数据时,只需从基础InputStreamSystem.in)中读取。 InputStreamReader根据需要执行转换,不会缓冲数据。

想要在堆栈中使用任何类型的缓冲流或读取器。这将消除使用readLine()方法的任何机会,至少如果您只限于JDK类。


根据您的最新更新进行修改:

我认为你在原始模式和熟食模式之间切换有点可疑。如果我要实现这一点,我将创建两个原始操作String readLine()byte[] readData(length)。第一个字符累积到换行符,第二个读取固定缓冲区。然后你的主循环看起来像这样:

InputStream in = // ...
Reader rd = new InputStreamReader(in, "USASCII");  // or whatever encoding you use

while (true) {
    String command = readLine(rd );
    if (command .equals("RAW")) {
        int length = Integer.parseInt(readLine(rd ));
        byte[] data = readData(in , length);
        if (! readLine(rd ).equals("ENDRAW")) {
            throw // an exception that indicates protocol violation
        }
    }
    else // process other commands
}

我还要将整个事物包装在一个围绕流构建的对象中,并且可能使用回调来分派数据包。

答案 1 :(得分:1)

最好的选择可能是逐字节(使用System.in.read())读入缓冲区,直到您点击UTF-8换行字节0x0A,然后将该字节缓冲区转换为字符串(使用{ {1}})。

请注意,调用InputStream的new String(byte[] bytes, "UTF-8")将返回一个值为0到255的int,您需要将其转换为一个字节。您可以在某种类型的Collection中累积字节,然后使用标准的Collection框架工具将其转换为数组以供String构造函数使用。

当你看到它要切换的指示器(可能是某种插入信号,某些特定字节)时,然后切换到原始字节读取代码。