如何正确解析Java中的字节流

Posted

技术标签:

【中文标题】如何正确解析Java中的字节流【英文标题】:How to parse byte stream in Java properly 【发布时间】:2013-09-13 11:54:31 【问题描述】:

你好男孩和女孩。

我正在开发一个基于终端的客户端应用程序,它通过 TCP/IP 与服务器通信并发送和接收任意数量的原始字节。每个字节代表一个命令,我需要将其解析为代表这些命令的 Java 类,以供进一步使用。

我的问题是如何有效地解析这些字节。我不想以一堆嵌套的 if 和 switch-case 告终。

我已经准备好这些命令的数据类。我只需要弄清楚进行解析的正确方法。

以下是一些示例规格:

字节流可以在例如 整数:[1,24,2,65,26,18,3,0,239,19,0,14,0,42,65,110,110,97,32,109,121,121,106,228,42,15,20,5,149,45,87]

第一个字节是 0x01,它是仅包含一个字节的标头的开始。

第二个是长度,它是特定的字节数 命令,这里也只有一个字节。

下一个可以是第一个字节是命令的任何命令,0x02 在这种情况下,它遵循 n 个字节,这些字节包含在 命令。

等等。最后还有校验和相关的字节。

表示 set_cursor 命令的示例类:

/**
 * Sets the cursor position.
 * Syntax: 0x0E | position
 */
public class SET_CURSOR 

private final int hexCommand = 0x0e;
private int position;

public SET_CURSOR(int position) 



public int getPosition() 
    return position;


public int getHexCommnad() 
    return hexCommand;



【问题讨论】:

【参考方案1】:

当像这样解析字节流时,最好使用的设计模式是命令模式。每个不同的命令都将充当处理程序来处理流中接下来的几个字节。

interface Command

    //depending on your situation, 
    //either use InputStream if you don't know
    //how many bytes each Command will use
    // or the the commands will use an unknown number of bytes
    //or a large number of bytes that performance
    //would be affected by copying everything.
    void execute(InputStream in);

    //or you can use an array if the
    //if the number of bytes is known and small.
    void execute( byte[] data);


然后,您可以拥有一个映射,其中包含每个字节“操作码”的每个 Command 对象。

Map<Byte, Command> commands = ...

commands.put(Byte.parseByte("0x0e", 16), new SetCursorCommand() );
...

然后您可以解析消息并执行命令:

InputStream in = ... //our byte array as inputstream
byte header = (byte)in.read();
int length = in.read();
byte commandKey = (byte)in.read();   
byte[] data = new byte[length]
in.read(data);

Command command = commands.get(commandKey);
command.execute(data);

你可以在同一个字节消息中有多个命令吗?如果是这样,您可以轻松地将命令获取和解析包装在一个循环中,直到 EOF。

【讨论】:

这似乎是简单而合法的使用模式。我会用这个。【参考方案2】:

你可以试试那个https://github.com/raydac/java-binary-block-parser的JBBP库

@Bin class Parsed  byte header; byte command; byte [] data; int checksum;
Parsed parsed = JBBPParser.prepare("byte header; ubyte len; byte command; byte [len] data; int checksum;").parse(theArray).mapTo(Parsed.class);

【讨论】:

【参考方案3】:

这是一个庞大而复杂的主题。

这取决于您将读取的数据类型。

它是一个 looooong 流吗? 是不是很多小的独立结构/对象? 您的流程的结构/对象之间是否有一些引用?

我最近为专有软件编写了一个字节序列化/反序列化库。

我采用类似于访问者的方法进行类型转换,与 JAXB 的工作方式相同。

我将我的对象定义为 Java 类。在类上初始化解析器,然后将要反序列化的字节或要序列化的 Java 对象传递给它。

类型检测(基于流的第一个字节)通过简单的大小写匹配机制(1 => ClassA,15 => ClassF,...)向前完成。

编辑:它可能很复杂或代码重载(嵌入对象),但请记住,如今,java 对此进行了很好的优化,它使代码保持清晰易懂。

【讨论】:

流很短。从几个字节到几百个最大。该协议定义了 30 种不同的操作,因此数量相当少。这些对象之间没有链接,因此解析它们应该非常简单。它们可以按任意顺序排列。这种类似访问者的方法听起来很合理。你有什么实现可以让我研究这种模式吗? 我也不太关心最终性能,因为这个库(我在这里开发)用于移动环境。在 Dalvik JVM 中是准确的。当我创建几十个只包含简单原始数据的对象时,开销是否太大? 很遗憾,我无法发布源代码,因为它是封闭源代码。但我可以尝试向您解释我所做的基本原理: 1) 为您的每种命令类型创建一个 Java 类。 2)读取你的第一个字节,然后读取第二个(大小) 3)读取给定大小的整个缓冲区(一个命令) 4)使用案例,将您的类型与相应的类匹配 5)使用工厂初始化解析器这个特定的类(如果可能,使用缓存以方便重用)(这是更棘手的部分)6)反序列化! 关键部分是定义命令的java类。在我的例子中,它是一个简单类型的 POJO。解析器工厂对类进行一些反射以检测其属性并将每个不同的属性类型与专用类型解析器(int/long/String)匹配。我使用类似访问者的逻辑来启用类型嵌套(子类)【参考方案4】:

ByteBuffer可用于解析字节流-What is the use of ByteBuffer in Java?:

byte[] bytesArray = 4, 2, 6, 5, 3, 2, 1;
ByteBuffer bb = ByteBuffer.wrap(bytesArray);
int intFromBB = bb.order(ByteOrder.LITTLE_ENDIAN).getInt(); 
byte byteFromBB = bb.get(); 
short shortFromBB = bb.getShort(); 

【讨论】:

以上是关于如何正确解析Java中的字节流的主要内容,如果未能解决你的问题,请参考以下文章

Java中的字节流和字符流区别

java IO的字节流和字符流及其区别

Java中常用的字节流和字符流

java 文件读写流

芯学苑:浅析Java字节流和字符流

文件操作的字节流和字符流