You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

GLib(Vala)中DataInputStream异常Skip行为排查(NBS文件场景)

问题:NBS文件解析中DataInputStream skip(4)导致字符串首字符丢失,改为skip(3)才正常的原因

我正在为.NBS(Note Block Studio歌曲文件)编辑器编写辅助类,在实现将.NBS文件加载至结构体的逻辑时,遇到了DataInputStream的异常skip行为。例如,若作者名为"Wattana",读取后会显示为"attana",必须将skip字节数改为3才能正常读取。为何读取song_name前skip4字节正常,而读取song_author前skip4字节会跳过字符串首字符?以下是辅助类的部分代码:

public static SongData to_song_data (DataInputStream data_stream) {
    // Specifications for the NBS file format can be found at https://opennbs.org/nbs
    data_stream.set_byte_order (DataStreamByteOrder.LITTLE_ENDIAN);
    var song_data = new SongData();
    // Note to self: a "short" is 2 bytes(16 bits) long.
    // TODO: Add error handler.
    song_data.old_version = data_stream.read_int16 ();
    song_data.version = data_stream.read_byte ();
    song_data.vanilla_intrument_count = data_stream.read_byte ();
    song_data.length = data_stream.read_int16 ();
    song_data.layer_count = data_stream.read_int16 ();
    data_stream.skip (4); // 4 bytes(32 bits) must be skipped before reading any string from an NBS file.
    song_data.song_name = data_stream.read_line ();
    data_stream.skip (3); // Not sure why 4 bytes skip doesn't work here.
    song_data.song_author = data_stream.read_line ();
    return song_data;
}

回答

嘿,这个问题的核心其实是你对NBS文件格式中字符串字段的理解出现了偏差,再加上skip()方法的行为和你的预期不匹配导致的。让我一步步拆解:

1. NBS字符串字段的正确结构

根据NBS格式规范,每个字符串字段并不是以固定4字节偏移开头,而是遵循以下结构:

  • 一个2字节的小端序short(16位整数),表示后续UTF-8字符串的字节长度
  • 紧接着是对应长度的UTF-8编码字节数据

你代码里注释的“读取字符串前必须跳过4字节”是错误的,这也是问题的根源。

2. 为什么第一次skip(4)看似正常?

我们来计算一下读取到layer_count后的字节偏移:

  • old_version(2字节) + version(1字节) + vanilla_intrument_count(1字节) + length(2字节) + layer_count(2字节) = 总共8字节

而NBS规范中,layer_count之后直接就是song_name的2字节长度前缀。你这里skip(4)相当于跳过了song_name的长度前缀(2字节)加上song_name的前2字节。这时候你的read_line()方法能读出正确的song_name,大概率是因为这个方法的实现逻辑有问题——比如它可能是读取到某个终止符(而非按长度前缀读取),刚好巧合地跳过了被截断的部分后读到了完整内容。

3. 为什么第二次skip(4)会丢失首字符?

当你读完song_name后,文件指针已经停在song_name的末尾,紧接着就是song_author的2字节长度前缀。这时候你再skip(4),就会跳过:

  • song_author的2字节长度前缀 + song_author的前2字节

当你用read_line()读取时,自然就会丢失首字符(比如"Wattana"变成"attana",刚好丢了第一个字节)。而改成skip(3)时,只是巧合地少跳了1字节,刚好对齐到了字符串的起始位置,但这并不是正确的解决方式。

4. 正确的实现方式

你需要替换掉错误的skip()和read_line()逻辑,改为按照NBS规范读取字符串:

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

// 读取NBS字符串的工具方法
private static String readNbsString(DataInputStream dataStream) throws IOException {
    short length = dataStream.read_int16(); // 读取2字节长度前缀(小端序)
    if (length <= 0) {
        return "";
    }
    byte[] strBytes = new byte[length];
    dataStream.readFully(strBytes); // 确保读取完整的字符串字节
    return new String(strBytes, StandardCharsets.UTF_8);
}

// 修改后的to_song_data方法
public static SongData to_song_data (DataInputStream data_stream) throws IOException {
    data_stream.set_byte_order (DataStreamByteOrder.LITTLE_ENDIAN);
    var song_data = new SongData();
    
    song_data.old_version = data_stream.read_int16 ();
    song_data.version = data_stream.read_byte ();
    song_data.vanilla_intrument_count = data_stream.read_byte ();
    song_data.length = data_stream.read_int16 ();
    song_data.layer_count = data_stream.read_int16 ();
    
    // 去掉所有skip操作,直接按规范读取字符串
    song_data.song_name = readNbsString(data_stream);
    song_data.song_author = readNbsString(data_stream);
    
    return song_data;
}

额外提醒:skip()方法的坑

另外要注意,DataInputStream.skip(long n)方法不保证一定会跳过n字节,它返回的是实际跳过的字节数。如果文件剩余字节不足n,它只会跳过剩余的部分。这也是为什么有时候你的skip()行为会出现看似随机的异常——你假设它跳过了4字节,但实际可能并没有。


内容的提问来源于stack exchange,提问作者user9102859

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.09 11:07:35