10分钟就理解Redis序列化协议,你也能编写redis客户端( 五 )

这里抽取出一个类LineStringDecoder用于解析单行字符串,这样在解析错误消息的时候可以做一次继承即可 。测试一下:
public static void main(String[] args) throws Exception { ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(); // +OKrn buffer.writeBytes("+OK".getBytes(RespConstants.UTF_8)); buffer.writeBytes(RespConstants.CRLF); String value = https://www.isolves.com/it/sjk/Redis/2019-10-15/RespCodec.X.decode(buffer); log.info("Decode result:{}", value);}// Decode result:OK复制代码解析错误消息
错误消息的本质也是单行字符串,所以其解码的实现可以和简单字符串的解码实现一致 。错误消息数据类型的解码器如下:
public class RespErrorDecoder extends LineStringDecoder {}复制代码测试一下:
public static void main(String[] args) throws Exception { ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(); // -ERR unknown command 'foobar'rn buffer.writeBytes("-ERR unknown command 'foobar'".getBytes(RespConstants.UTF_8)); buffer.writeBytes(RespConstants.CRLF); String value = https://www.isolves.com/it/sjk/Redis/2019-10-15/RespCodec.X.decode(buffer); log.info("Decode result:{}", value);}// Decode result:ERR unknown command 'foobar'复制代码解析整型数字
整型数字类型,本质就是需要从字节序列中还原出带符号的64bit的长整型,因为是带符号的,类型标识位:后的第一个字节需要判断是否负数字符-,因为是从左向右解析,然后每解析出一个新的位,当前的数字值要乘10 。其解码器的实现如下:
public class RespIntegerDecoder implements RespDecoder<Long> { @Override public Long decode(ByteBuf buffer) { int lineEndIndex = CodecUtils.X.findLineEndIndex(buffer); // 没有行尾,异常 if (-1 == lineEndIndex) { return null; } long result = 0L; int lineStartIndex = buffer.readerIndex(); boolean negative = false; byte firstByte = buffer.getByte(lineStartIndex); // 负数 if (RespConstants.MINUS_BYTE == firstByte) { negative = true; } else { int digit = firstByte - '0'; result = result * 10 + digit; } for (int i = lineStartIndex + 1; i < (lineEndIndex - 1); i++) { byte value = https://www.isolves.com/it/sjk/Redis/2019-10-15/buffer.getByte(i); int digit = value - '0'; result = result * 10 + digit; } if (negative) { result = -result; } // 重置读游标为rn之后的第一个字节 buffer.readerIndex(lineEndIndex + 1); return result; }}复制代码整型数字类型的解析相对复杂,一定要注意负数判断 。测试一下:
public static void main(String[] args) throws Exception { ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(); // :-1000rn buffer.writeBytes(":-1000".getBytes(RespConstants.UTF_8)); buffer.writeBytes(RespConstants.CRLF); Long value = https://www.isolves.com/it/sjk/Redis/2019-10-15/RespCodec.X.decode(buffer); log.info("Decode result:{}", value);}// Decode result:-1000复制代码解析定长字符串
定长字符串类型解析的关键是先读取类型标识符$后的第一个字节序列分块解析成64bit带符号的整数,用来确定后面需要解析的字符串内容的字节长度,然后再按照该长度读取后面的字节 。其解码器实现如下:
public class RespBulkStringDecoder implements RespDecoder<String> { @Override public String decode(ByteBuf buffer) { int lineEndIndex = CodecUtils.X.findLineEndIndex(buffer); if (-1 == lineEndIndex) { return null; } // 使用RespIntegerDecoder读取长度 Long length = (Long) DefaultRespCodec.DECODERS.get(ReplyType.INTEGER).decode(buffer); if (null == length) { return null; } // Bulk Null String if (RespConstants.NEGATIVE_ONE.equals(length)) { return null; } // Bulk Empty String if (RespConstants.ZERO.equals(length)) { return RespConstants.EMPTY_STRING; } // 真实字节内容的长度 int readLength = (int) length.longValue(); if (buffer.readableBytes() > readLength) { byte[] bytes = new byte[readLength]; buffer.readBytes(bytes); // 重置读游标为rn之后的第一个字节 buffer.readerIndex(buffer.readerIndex() + 2); return new String(bytes, RespConstants.UTF_8); } return null; }}复制代码测试一下:
public static void main(String[] args) throws Exception{ ByteBuf buffer = ByteBufAllocator.DEFAULT.buffer(); // $6rnthrowablern buffer = ByteBufAllocator.DEFAULT.buffer(); buffer.writeBytes("$9".getBytes(RespConstants.UTF_8)); buffer.writeBytes(RespConstants.CRLF); buffer.writeBytes("throwable".getBytes(RespConstants.UTF_8)); buffer.writeBytes(RespConstants.CRLF); String value = https://www.isolves.com/it/sjk/Redis/2019-10-15/RespCodec.X.decode(buffer); log.info("Decode result:{}", value);}// Decode result:throwable复制代码解析RESP数组
RESP数组类型解析的关键: