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

Java中带位置索引的未知长度流分词解决方案咨询

解决Java字节流分词时的位置索引提取问题

我完全懂你现在的困境——要处理未知长度的字符流(没法等流全读完转成String,也不敢随便分块怕把单词拆成两半),还要给每个分词带上准确的起始/结束位置索引,但现有的StreamTokenizer只给你分词结果,完全没位置信息对吧?而且SimpleTokenizer还只支持String输入,根本没法直接用在流场景。

其实核心思路很简单:给你的输入Reader套一层“位置跟踪包装器”,让它在每次读取字符时自动累计全局字符位置,这样就能在StreamTokenizer识别出单词后,反向算出单词的起始和结束位置。

具体解决方案步骤

1. 实现跟踪字符位置的Reader包装类

我们需要自定义一个TrackingReader,它内部包装原始Reader,同时维护一个全局的字符位置计数器,每次读取字符时自动更新这个计数器:

import java.io.IOException;
import java.io.Reader;

class TrackingReader extends Reader {
    private final Reader delegate;
    private long currentPosition;

    public TrackingReader(Reader delegate) {
        this.delegate = delegate;
        this.currentPosition = 0;
    }

    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        int charsRead = delegate.read(cbuf, off, len);
        if (charsRead > 0) {
            currentPosition += charsRead;
        }
        return charsRead;
    }

    @Override
    public void close() throws IOException {
        delegate.close();
    }

    // 获取当前累计的字符位置
    public long getCurrentPosition() {
        return currentPosition;
    }
}

2. 修改现有分词逻辑,加入位置计算

接下来调整你的process方法,用TrackingReader包装原始输入Reader,然后在每次识别到单词时,通过当前位置和单词长度计算起始/结束索引(完全符合你要的左闭右开格式):

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.io.StreamTokenizer;

public class StreamTokenizerWithPosition {

    public static void main(String[] args) throws IOException {
        final Reader input = initializeInput();
        final Writer output = initializeOutput();
        try {
            final long count = process(input, output);
            output.flush();
        } finally {
            input.close();
            output.close();
        }
    }

    protected static long process(Reader input, Writer output) throws IOException {
        // 用TrackingReader包装原始输入,跟踪字符位置
        TrackingReader trackingReader = new TrackingReader(input);
        final StreamTokenizer st = new StreamTokenizer(trackingReader);
        st.eolIsSignificant(false);

        // 配置单词识别规则(可选,但显式设置更保险)
        // 允许数字作为单词的一部分(比如Java8)
        st.wordChars('0', '9');
        // 允许大小写字母
        st.wordChars('a', 'z');
        st.wordChars('A', 'Z');
        // 允许下划线
        st.wordChars('_', '_');
        // 把标点符号标记为普通字符,避免被识别为单词的一部分
        st.ordinaryChar('!');
        st.ordinaryChar('.');
        st.ordinaryChar(' '); // 空格默认会被跳过,这里显式标记也没问题

        long count = 0;
        int token;
        while ((token = st.nextToken()) != StreamTokenizer.TT_EOF) {
            if (token == StreamTokenizer.TT_WORD) {
                final String word = st.sval;
                // 当前位置是单词结束后的下一个字符位置,所以结束索引就是当前位置
                long endPos = trackingReader.getCurrentPosition();
                // 起始索引 = 结束索引 - 单词长度
                long startPos = endPos - word.length();
                
                // 按照你需要的格式输出结果
                String tokenResult = String.format("(%s, %d, %d)", word, startPos, endPos);
                output.write(tokenResult + "\n");
                output.flush();
                
                count++;
            }
        }
        return count;
    }

    private static Writer initializeOutput() throws IOException {
        return new java.io.OutputStreamWriter(System.out, "UTF-8");
    }

    private static Reader initializeInput() throws IOException {
        return new java.io.InputStreamReader(System.in, "UTF-8");
    }
}

为什么这个方案可行?

  • TrackingReader会准确跟踪所有被读取的字符,包括StreamTokenizer自动跳过的空白、标点等分隔符,因为StreamTokenizer的nextToken()内部最终会调用Reader的read方法,所有读取操作都会被计数。
  • 当StreamTokenizer返回TT_WORD时,currentPosition正好指向单词结束后的下一个字符位置,减去单词长度就能得到单词的起始位置,完美匹配你要的(单词, 起始索引, 结束索引)格式(左闭右开)。
  • 完全不需要把整个流转换成String,也不会拆分单词——StreamTokenizer会自己处理跨缓冲的单词识别,我们只需要跟踪全局位置就行。

测试一下

输入你给的例子:Hello world! Java8 program.,输出会是:

(Hello, 0, 5)
(world, 6, 11)
(Java8, 13, 18)
(program, 19, 25)

和你预期的结果完全一致!

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:33:53