[Java/HexStringReader] 核心源码精讲:java.io.StringReader类(JDK1.1-)

概述:java.io.StringReader

  • java.io包的StringReader类可用于从字符串读取数据(以字符为单位)。
  • 它继承了抽象类java.io.Reader
  • 自 JDK 1.1 起便支持

注意:在StringReader中,指定的字符串充当源,从其中分别读取字符。

创建StringReader对象

  • 为了创建一个StringReader,我们必须首先导入java.io.StringReader包。

导入包后,就可以创建字符串读取器了。

//创建 StringReader
StringReader input = new StringReader(String data);

在这里,我们创建了一个StringReader,它从指定的名为data的字符串中读取字符。

StringReader的方法

StringReader类为Reader类中的不同方法提供了实现。

read(...)方法

  • read(...)
  • read() - 从字符串读取器读取单个字符
  • read(char[] array) - 从阅读器读取字符并将其存储在指定的数组中
  • read(char[] array, int start, int length) - 从阅读器读取等于length字符的数量,并从start位置开始存储在指定的数组中

示例:Java StringReader

import java.io.StringReader;

public class Main {
  public static void main(String[] args) {
    String data = "This is the text read from StringReader.";

    //创建一个字符数组
    char[] array = new char[100];

    try {
      //创建一个StringReader
      StringReader input = new StringReader(data);

      //使用read方法
      input.read(array);
      System.out.println("从字符串读取数据:");
      System.out.println(array);

      input.close();
    } catch(Exception e) {
      e.getStackTrace();
    }
    }
}

out

从字符串读取数据:
This is the text read from StringReader.

skip(...)方法

  • skip()方法

要丢弃和跳过指定数量的字符,可以使用skip()方法。
例如

import java.io.StringReader;

public class Main {
  public static void main(String[] args) {

    String data = "This is the text read from StringReader";
    System.out.println("原始数据: " + data);

    //创建一个字符数组
    char[] array = new char[100];

    try {
      //创建 StringReader
      StringReader input = new StringReader(data);

      //使用 skip() 方法
      input.skip(5);

      //使用 read 方法
      input.read(array);
      System.out.println("跳过5个字符后的数据:");
      System.out.println(array);

      input.close();
    }

    catch(Exception e) {
      e.getStackTrace();
    }
  }
}

out

原始数据: This is the text read from the StringReader
跳过5个字符后的数据:
is the text read from the StringReader

在上面的示例中,我们使用skip()方法从字符串读取器中跳过5个字符。因此,字符'T'、'h'、'i'、's'和' '从原始字符串读取器中被跳过。

close()方法

  • 要关闭字符串阅读器,我们可以使用该close()方法。

调用close()方法后,我们将无法使用读取器从字符串读取数据。

其他方法

方法 描述
ready() 检查字符串读取器是否准备好被读取
mark() 标记读取器中已读取数据的位置
reset() 重置标记,返回到阅读器中设置标记的位置

最佳实践

基于StringReader自定义的HexStringReader

基础依赖: BytesUtils

DataTypeEnum

package com.xxx.sdk.utils.bytes;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 数据类型枚举类
 * @updateTime 2025.7.15 11:19
 */
public enum DataTypeEnum {
    //字符串
    STRING_TYPE("STRING_TYPE", "S", String.class, "string type"),
    //整型
    LONG_TYPE("LONG_TYPE", "L", Long.class, "long type"),
    //IEEE 754 单精度浮点型
    FLOAT_TYPE("FLOAT_TYPE", "F", Float.class, "single float type"),
    //IEEE 754 双精度浮点型
    DOUBLE_TYPE("DOUBLE_TYPE", "D", Double.class, "double float type"),

    //整型数组
    //INT_ARRAY_TYPE("INT_ARRAY_TYPE", "IntArray", Integer.class, "int array type"),

    //原始类型 (Origin Raw Data Type, 原来是什么,就保留成什么,不需要解析)
    RAW_TYPE("RAW_TYPE", "OR", null, "Origin Raw Data Type");

    //public static final String STRING_TYPE = "S";
    //public static final String LONG_TYPE = "L";
    //public static final String FLOAT_TYPE = "F";
    //public static final String DOUBLE_TYPE = "D";
    ////public static final String INT_ARRAY_TYPE = "IntArray";
    //public static final String RAW_DATA_TYPE = "OR";

    private String name;

    private String code;

    private Class javaClass;

    private String description;

    public final static String CODE_PARAM = "code";
    public final static String NAME_PARAM = "name";

    DataTypeEnum(String name, String code, Class javaClass, String description) {
        this.name = name;
        this.code = code;
        this.javaClass = javaClass;
        this.description = description;
    }

    public static DataTypeEnum findByCode(String code) {
        for (DataTypeEnum type : values()) {
            if (type.getCode().equals(code)) {
                return type;
            }
        }
        return null;
    }

    public static DataTypeEnum findByName(String name) {
        for (DataTypeEnum type : values()) {
            if (type.getName().equals(name)) {
                return type;
            }
        }
        return null;
    }

    public static List<Map<String, DataTypeEnum>> toList() {
        List<Map<String, DataTypeEnum>> list = new ArrayList();//Lists.newArrayList()其实和new ArrayList()几乎一模
        for (DataTypeEnum item : DataTypeEnum.values()) {
            Map<String, DataTypeEnum> map = new HashMap<String, DataTypeEnum>();
            map.put(DataTypeEnum.CODE_PARAM, item);
            list.add(map);
        }
        return list;
    }

    public String getName() {
        return name;
    }

    public String getCode() {
        return code;
    }

    public Class getJavaClass() {
        return javaClass;
    }

    public String getDescription() {
        return description;
    }
}

ISourceReader

package com.xxx.sdk.utils.bytes;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;

/**
 * 解析字节数据的抽象阅读器接口
 * @updateTime 2025.7.15 11:19
 * @param <T>
 */
public interface IBytesReader<T> {
    public final static Charset CHARSET_DEFAULT = StandardCharsets.UTF_8; //Charset.forName("UTF-8");

    /**
     * 解析字节数据
     * @note
     *  1. 原方法名: getParserStr
     *  2. 现方法名: parseBytes | change at 2025.4.9. 09:45 AM by johnny
     * @param readBytesLength  待读取的数据所占用字节数
     * @param dataType 数据类型 (S:string、L:long、OR:不处理)
     * @param comment  备注说明 便于定位问题
     * @return
     * @throws IOException
     */
    Object parseBytes(int readBytesLength, DataTypeEnum dataType, Charset charset, String comment) throws IOException;

    public static Object parseBytes(ISourceReader reader, String readBytesLength, String dataType, String comment) throws IOException {
        return parseBytes(reader, readBytesLength, dataType, reader.getCharset(), comment);
    }

    public static Object parseBytes(ISourceReader reader, String readBytesLength, String dataType, Charset charset, String comment) throws IOException {
        Integer readBytesLengthInt = Integer.parseInt(readBytesLength);
        DataTypeEnum dataTypeEnum = DataTypeEnum.findByCode(dataType);
        Object result = reader.parseBytes(readBytesLengthInt, dataTypeEnum, charset, comment);
        return result;
    }

    int next();

    Boolean hasNext();

    public T getSource();

    Charset getCharset();

    void setCharset(Charset charset);

    T getLatestReadData();
}

HexStringReader

package com.xxx.sdk.utils.bytes;

import lombok.NonNull;
import lombok.SneakyThrows;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.Unsafe;

import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Field;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.Objects;

/**
 * 基于 StringReader(字符(串)流阅读器) 的 十六进制字符串读取器
 * @updateTime 2025.7.15 10:47
 * @note 原名: SourceStringReader
 * @note 所谓16进制字符串(HexString),形如: "0123A1B",而非 "0x0123A1B"
 */
public class HexStringReader extends StringReader implements IBytesReader<String> {
    private final static Logger log = LoggerFactory.getLogger(HexStringReader.class);

    public static final String SPLIT = IBytesReader.SPLIT;

    private String string;

    /**
     * 已读取的字符数量
     */
    private int hasReadSize = 0;

    /**
     * 字符集
     * @note 解析 String 类型的字节数据时使用
     */
    private Charset charset = CHARSET_DEFAULT;

    /**
     * 最近一次操作读取到的字节数据
     */
    private String latestReadData = null;

    public HexStringReader(@NonNull String string, Charset charset) {
        super((String) Objects.requireNonNull(string));
        this.string = string;
        this.charset = charset==null?CHARSET_DEFAULT:charset;
    }

    public HexStringReader(@NonNull String string) {
        this(string, CHARSET_DEFAULT);
    }

    @NonNull
    @Override
    public String getSource() {
        return this.string;
    }

    public void close() {
        super.close();
        string = null;
    }

    @Override
    public int read() throws IOException {
        int readSize = super.read();
        //hasReadSize += readSize;
        return readSize;
    }

    @Override
    public int read(CharBuffer var1) throws IOException {
        int readSize = super.read(var1);
        //hasReadSize += readSize;
        return readSize;
    }

    @Override
    public int read(char[] var1) throws IOException {
        int readSize = super.read(var1);
        //hasReadSize += readSize;
        return readSize;
    }

    @Override
    public int read(char[] var1, int var2, int var3) throws IOException {
        int readSize = super.read(var1, var2, var3);
        hasReadSize += readSize;
        return readSize;
    }

    @Override
    public long skip(long var1) throws IOException {
        long readSize = super.skip(var1);
        hasReadSize += readSize;
        return readSize;
    }


//    public static String parseBytes(SourceStringReader reader, String readByteSize, String dataType) throws IOException {
//        StringBuilder s = new StringBuilder();
//        if (Integer.parseInt(readByteSize) * 2L > reader.getSource().length()) {
//            //报文长度异常
//            logger.error("message length exception!data={},len={}", reader.getSource(), readByteSize);
//            throw new IOException();
//        }
//
//        int charSize = Integer.parseInt(readByteSize) * 2;
//        char[] data = new char[ charSize ];
//        reader.read(data);
//        String s1 = new String(data);
//        //System.out.println(s1);
//
//        switch (dataType) {
//            //Long 类型解析
//            case LONG_TYPE:
//                s.append(Long.parseLong(s1, 16));
//                break;
//            //String 类型解析
//            case STRING_TYPE:
//                s.append(BytesUtils.hexStringToTextString(s1));
//                break;
//            //IntArray 类型解析
//            //case INT_ARRAY_TYPE:
//                //s.append( BytesUtils.hexStringToIntegerArrayString( s1 , BytesUtils.INTEGER_ARRAY_STRING_SEPARATOR ) );
//                //break;
//            //原始类型,不需要解析
//            case RAW_DATA_TYPE:
//                s.append(s1);
//                break;
//            default:
//                break;
//        }
//        String str = s.toString();
//        s.setLength(0);
//        return str;
//    }

//    public static String parseBytes(SourceStringReader reader, String length, String dataType) throws IOException {
//        return parseBytes( reader, length, dataType, null);
//    }

    public static String parseBytes(HexStringReader reader, String readBytesLength, String dataType, String comment) throws IOException {
        return parseBytes(reader, readBytesLength, dataType, reader.getCharset(), comment);
    }

    public static String parseBytes(HexStringReader reader, String readBytesLength, String dataType, Charset charset, String comment) throws IOException {
        Integer readBytesLengthInt = Integer.parseInt(readBytesLength);
        DataTypeEnum dataTypeEnum = DataTypeEnum.findByCode(dataType);
        String result = reader.parseBytes(readBytesLengthInt, dataTypeEnum, charset, comment);
        return result;
    }


    @Override
    public String parseBytes(int readBytesLength, DataTypeEnum dataType, Charset charset, String comment) throws IOException {
        comment = isEmpty(comment)?"":comment;

        //step1 判断长度
        if (readBytesLength * 2L > this.getSource().length()) {
            log.error("{} | raw data's bytes length is abnormal!readBytesLength={},dataType={}, data={}", comment, readBytesLength,dataType, this.getSource() );//报文长度异常
            throw new IOException();
        }

        //step2 从 reader 读取数据,存入 data:char []
        char[] rawData = new char[readBytesLength * 2];
        this.read(rawData);

        //step3 data:char [] 转 String
        // [demo] length = 2, dataType = "L", hexString = "0014", next = 4( StringReader 已读取 4 个 char,待读取第 1+4 个(即 下标为 4) 的 char )
        // [demo] length = 4, dataType = "L", hexString = "64E85504", next = xx
        String rawDataHex = new String(rawData);
        this.latestReadData = rawDataHex;
        if(log.isDebugEnabled()){//原则上不建议打开 | 注: next() 是基于反射机制获取的
            log.debug("{}| rawDataHex:{}, dataType:{}, next:{}", comment, rawDataHex, dataType, this.next());
        }
        //step4 按照不同类型解析数据
        String result = null;
        try {
            result = hexStringConvert( rawDataHex, dataType, charset );
        } catch (Exception exception) {//先捕获异常,打印具体的日志信息(便于事后定位);再上抛异常
            log.error("{}| Fail to convert the hex string({}) to data type({})!next:{}, exception.message:{}", comment,  rawDataHex, dataType, this.next(), exception.getMessage() , this.next(), exception);
            if( log.isDebugEnabled() ) {//仅调试模式下,打印该内容
                log.error( "{}| hex-string:{} | data-type:{} | next:{} | source-string:{}", comment, rawDataHex, dataType, this.next(), this.getSource());
            }
            throw new RuntimeException(exception);
        }
        return result;
    }

    @Deprecated
    private static String hexStringConvert(String hexString , String dataType, Charset charset){
        DataTypeEnum dataTypeEnum = DataTypeEnum.findByCode( dataType );
        return hexStringConvert( hexString, dataType, charset );
    }

    /**  * 16进制字符串 转 Long / String / OR 的数据类型
     * @return
     */
    private static String hexStringConvert(String hexString , DataTypeEnum dataType, Charset charset){
        StringBuilder builder = new StringBuilder();
        byte [] bytes = null;
        String errorMessage = null;
        String result;

        try {
            switch (dataType) {
                //Long 类型解析
                case LONG_TYPE:
                    builder.append(Long.parseLong(hexString, 16));
                    break;
                //Float 类型解析
                case FLOAT_TYPE:
                    bytes = BytesUtils.hexStringToBytes( hexString );
                    builder.append( BytesUtils.bytesToFloat( bytes ) );//eg: "1.23"
                    break;
                //Double 类型解析
                case DOUBLE_TYPE:
                    bytes = BytesUtils.hexStringToBytes( hexString );
                    builder.append( BytesUtils.bytesToDouble( bytes ) );
                    break;
                //String类型解析
                case STRING_TYPE:
                    //builder.append(hexStringToString(hexString));
                    builder.append( BytesUtils.hexStringToTextString(hexString, charset) );
                    break;
                //IntArray 类型解析
                //case INT_ARRAY_TYPE:
                //s.append( BytesUtils.hexStringToIntegerArrayString( s1 , BytesUtils.INTEGER_ARRAY_STRING_SEPARATOR ) );
                //break;
                //原始类型,不需要解析
                case RAW_TYPE:
                    builder.append(hexString);
                    break;
                default:
                    break;
            }
            result = builder.toString();
            builder.setLength(0);//置空
        } catch (Exception exception) {
            errorMessage = String.format("Fail to convert the hex string(%s) to data type(%s)!", hexString, dataType);
            log.error(errorMessage + ", exception:", exception);
            throw new RuntimeException(errorMessage, exception);
        }

        if(log.isDebugEnabled()){
            log.debug("Convert the hex string({}) to data type({}) success!result:{}", hexString, dataType, result);
        }
        return result;
    }

    private static String hexStringConvert(String hexString , DataTypeEnum dataType){
        return hexStringConvert(hexString, dataType, CHARSET_DEFAULT);
    }


//    public String getParserStrV1(SourceStringReader reader, int a, String b) throws IOException {
//        StringBuilder s = new StringBuilder();
//        if (a * 2L > reader.getSource().length()) {
//            log.error("报文长度异常,data={},len={}", reader.getSource(), a);
//            throw new IOException();
//        }
//
//        char[] data = new char[a * 2];
//        reader.read(data);
//        String s1 = new String(data);
//
//        switch (b) {
//            //Long类型解析
//            case "L":
//                s.append(Long.parseLong(s1, 16));
//                break;
//            //String类型解析
//            case "S":
//                s.append(BytesUtils.hexStringToTextString(s1));
//                break;
//            //原始类型,不需要解析
//            case "OR":
//                s.append(s1);
//                break;
//            default:
//                break;
//        }
//        return s.toString();
//    }
//
//    public String getParserStrOR(SourceStringReader reader, long a) throws IOException {
//        if (a * 2L > reader.getSource().length()) {
//            log.error("报文长度异常,data={},len={}", reader.getSource(), a);
//            throw new IOException();
//        }
//
//        char[] data = new char[(int)a * 2];
//        reader.read(data);
//        return new String(data);
//    }


    /**
     * 获取 StringReader 的 next(游标位置/指针)
     * @description
     *  1. 利用反射原理,将 java.io.StringReader 的 private 属性 next 读取出来
     *  2. 不建议高频调用 (影响调用程序的性能)
     * @return
     */
    @SneakyThrows
    public int next(){
        int next = Integer.MIN_VALUE; //读取失败时,以此值为标志
        //反射方法1 : Java 17 中需结合 VM Option 参数 : `--add-opens java.base/java.io=ALL-UNNAMED`
        //java.lang.reflect.Field field = java.io.StringReader.class.getDeclaredField("next");
        //field.setAccessible(true);
        //next = field.getInt( this );//读取 next 的值
        ////field.set(this, Integer.MIN_VALUE);//设置字段的值

        //反射方法2: 基于 Unsafe
        Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        // 获取私有字段的偏移量
        Field nextField = StringReader.class.getDeclaredField("next");
        long offset = unsafe.objectFieldOffset(nextField);
        next = unsafe.getInt(this, offset);
        //unsafe.putInt(stringReader, offset, 10);// 设置字段值

        return next;//next : 下标从 0 开始; 即将读取的下一个 char 的下标位置
    }

    public Boolean hasNext(){
        // string.length() | eg: string = "00000001" => string.length() = 8
        return hasReadSize != string.length();
    }

    public int getHasReadSize() {
        return hasReadSize;
    }

    @Override
    public Charset getCharset() {
        return charset;
    }

    @Override
    public void setCharset(Charset charset) {
        this.charset = charset;
    }

    @Override
    public String getLatestReadData() {
        return latestReadData;
    }

    public static boolean isEmpty(final CharSequence cs) {
        return cs == null || cs.length() == 0;
    }
}

Y 推荐文献

X 参考文献

posted @ 2025-05-03 18:45  千千寰宇  阅读(41)  评论(0)    收藏  举报