news 2026/2/9 8:13:57

文档处理能力分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
文档处理能力分析

1. 工程结构概览

Spring AI 提供了完整的文档处理能力,包括文档读取、文本分块和预处理。这些能力是 RAG 应用的基础。

document-readers/ # 文档读取器

├── pdf-reader/ # PDF 读取器

│ ├── PagePdfDocumentReader.java # 按页读取

│ └── ParagraphPdfDocumentReader.java # 按段落读取

├── markdown-reader/ # Markdown 读取器

│ └── MarkdownDocumentReader.java

├── tika-reader/ # 通用文档读取器(Tika)

│ └── TikaDocumentReader.java

└── jsoup-reader/ # HTML 读取器

└── JsoupDocumentReader.java

spring-ai-commons/ # 核心处理能力

├── document/

│ └── Document.java # 文档对象

└── transformer/

└── splitter/ # 文本分块

├── TextSplitter.java

├── TokenTextSplitter.java

└── CharacterTextSplitter.java

2. 技术体系与模块关系

文档处理流程:读取 → 分块 → 嵌入 → 存储

image.png

3. 关键场景示例代码

3.1 PDF 文档读取

PDF 读取支持按页和按段落两种方式:

// 按页读取

Resource pdfResource = new ClassPathResource("document.pdf");

PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(pdfResource);

List<Document> documents = pdfReader.get();

// 按段落读取(更智能)

ParagraphPdfDocumentReader paragraphReader =

new ParagraphPdfDocumentReader(pdfResource, config);

List<Document> documents = paragraphReader.get();

3.2 Markdown 文档读取

Markdown 读取器可以按标题、段落或水平线分组:

MarkdownDocumentReader markdownReader =

new MarkdownDocumentReader("classpath:docs/*.md", config);

List<Document> documents = markdownReader.get();

3.3 Tika 通用读取

Tika 可以读取多种格式(PDF、Word、PPT 等):

TikaDocumentReader tikaReader =

new TikaDocumentReader("classpath:document.docx");

List<Document> documents = tikaReader.get();

3.4 文档分块

将长文档分割成适合嵌入的小块:

// Token 分块(推荐)

TokenTextSplitter splitter = TokenTextSplitter.builder()

.chunkSize(800) // 目标 token 数

.minChunkSizeChars(350) // 最小字符数

.build();

List<Document> chunks = splitter.split(documents);

// 字符分块

CharacterTextSplitter charSplitter = new CharacterTextSplitter(1000, 200);

List<Document> chunks = charSplitter.split(documents);

3.5 完整流程

文档处理的完整流程:

// 1. 读取文档

TikaDocumentReader reader = new TikaDocumentReader("document.pdf");

List<Document> documents = reader.get();

// 2. 分块

TokenTextSplitter splitter = new TokenTextSplitter();

List<Document> chunks = splitter.split(documents);

// 3. 嵌入并存储

vectorStore.add(chunks);

4. 核心实现图

4.1 文档处理流程

image.png

5. 入口类与关键类关系

image.png

6. 关键实现逻辑分析

6.1 PDF 读取实现

PDF 读取有两种方式:

方式一:按页读取

public class PagePdfDocumentReader implements DocumentReader {

@Override

public List<Document> get() {

List<Document> documents = new ArrayList<>();

int pageCount = document.getNumberOfPages();

for (int i = 0; i < pageCount; i++) {

String pageText = extractTextFromPage(i);

Document doc = new Document(pageText);

doc.getMetadata().put("page", i);

documents.add(doc);

}

return documents;

}

}

方式二:按段落读取(更智能)

public class ParagraphPdfDocumentReader implements DocumentReader {

@Override

public List<Document> get() {

// 1. 提取段落

List<Paragraph> paragraphs = paragraphManager.flatten();

// 2. 将相邻段落合并为文档

List<Document> documents = new ArrayList<>();

for (int i = 0; i < paragraphs.size(); i++) {

Paragraph from = paragraphs.get(i);

Paragraph to = (i + 1 < paragraphs.size())

? paragraphs.get(i + 1)

: from;

String text = getTextBetweenParagraphs(from, to);

Document doc = new Document(text);

addMetadata(from, to, doc);

documents.add(doc);

}

return documents;

}

}

按段落读取的优势:

保持语义完整性:段落是自然的语义单元

更好的检索效果:段落级别的文档更适合向量搜索

保留布局信息:可以保留 PDF 的布局结构

6.2 Markdown 读取实现

Markdown 读取器使用 CommonMark 解析器:

public class MarkdownDocumentReader implements DocumentReader {

@Override

public List<Document> get() {

List<Document> documents = new ArrayList<>();

for (Resource resource : markdownResources) {

// 1. 解析 Markdown

Node document = parser.parse(loadContent(resource));

// 2. 访问文档节点

DocumentVisitor visitor = new DocumentVisitor(config);

document.accept(visitor);

// 3. 收集文档

documents.addAll(visitor.getDocuments());

}

return documents;

}

}

Markdown 读取器可以按以下方式分组:

按标题分组:每个标题及其内容成为一个文档

按段落分组:每个段落成为一个文档

按水平线分组:水平线分隔的内容成为独立文档

6.3 Tika 通用读取实现

Tika 使用自动检测解析器:

public class TikaDocumentReader implements DocumentReader {

@Override

public List<Document> get() {

try (InputStream stream = resource.getInputStream()) {

// 1. 自动检测文档类型并解析

parser.parse(stream, handler, metadata, context);

// 2. 提取文本

String text = handler.toString();

// 3. 格式化文本

text = textFormatter.format(text);

// 4. 创建文档

Document doc = new Document(text);

doc.getMetadata().put(METADATA_SOURCE, resourceName());

return List.of(doc);

}

}

}

Tika 的优势:

支持多种格式:PDF、Word、PPT、Excel、HTML 等

自动检测:无需指定文档类型

提取元数据:自动提取文档的元数据

6.4 文本分块实现

文本分块是 RAG 应用的关键步骤:

public abstract class TextSplitter implements DocumentTransformer {

@Override

public List<Document> apply(List<Document> documents) {

List<Document> chunks = new ArrayList<>();

for (Document doc : documents) {

// 1. 分割文本

List<String> textChunks = splitText(doc.getText());

// 2. 为每个分块创建文档

for (int i = 0; i < textChunks.size(); i++) {

Map<String, Object> metadata = new HashMap<>(doc.getMetadata());

// 3. 添加分块元数据

metadata.put("parent_document_id", doc.getId());

metadata.put("chunk_index", i);

metadata.put("total_chunks", textChunks.size());

Document chunk = Document.builder()

.text(textChunks.get(i))

.metadata(metadata)

.score(doc.getScore())

.build();

chunks.add(chunk);

}

}

return chunks;

}

protected abstract List<String> splitText(String text);

}

6.5 Token 分块实现

Token 分块使用编码器计算 token 数:

public class TokenTextSplitter extends TextSplitter {

@Override

protected List<String> splitText(String text) {

// 1. 编码为 tokens

List<Integer> tokens = encoding.encode(text).boxed();

List<String> chunks = new ArrayList<>();

while (!tokens.isEmpty() && chunks.size() < maxNumChunks) {

// 2. 取目标大小的 tokens

List<Integer> chunk = tokens.subList(0,

Math.min(chunkSize, tokens.size()));

String chunkText = decodeTokens(chunk);

// 3. 在标点符号处截断(保持语义)

int lastPunctuation = findLastPunctuation(chunkText);

if (lastPunctuation > minChunkSizeChars) {

chunkText = chunkText.substring(0, lastPunctuation + 1);

}

// 4. 过滤太短的分块

if (chunkText.length() > minChunkLengthToEmbed) {

chunks.add(chunkText.trim());

}

// 5. 移除已处理的 tokens

tokens = tokens.subList(getEncodedTokens(chunkText).size(),

tokens.size());

}

return chunks;

}

}

Token 分块的优势:

精确控制大小:按 token 数分割,而不是字符数

保持语义:在标点符号处截断

适合嵌入模型:token 数是嵌入模型的输入单位

7. 文档分块策略

7.1 Token 分块(推荐)

适合大多数场景,特别是使用 OpenAI 等基于 token 的模型:

TokenTextSplitter splitter = TokenTextSplitter.builder()

.chunkSize(800) // 目标 token 数

.minChunkSizeChars(350) // 最小字符数(避免过小)

.minChunkLengthToEmbed(5) // 最小嵌入长度

.maxNumChunks(10000) // 最大分块数

.keepSeparator(true) // 保留分隔符

.build();

7.2 字符分块

适合固定大小的分块需求:

CharacterTextSplitter splitter = new CharacterTextSplitter(

1000, // chunkSize

200 // chunkOverlap(重叠部分,保持上下文)

);

7.3 自定义分块

可以实现自己的分块策略:

public class CustomTextSplitter extends TextSplitter {

@Override

protected List<String> splitText(String text) {

// 自定义分块逻辑

// 例如:按句子、按段落、按章节等

return customSplit(text);

}

}

8. 外部依赖

不同读取器的依赖:

8.1 PDF Reader

PDFBox:Apache PDFBox,PDF 解析库

无其他依赖

8.2 Markdown Reader

CommonMark:Markdown 解析库

无其他依赖

8.3 Tika Reader

Apache Tika:通用文档解析库

支持 100+ 种格式

8.4 Text Splitter

tiktoken:Token 编码库(用于 TokenTextSplitter)

无其他依赖(CharacterTextSplitter)

9. 工程总结

Spring AI 的文档处理能力设计有几个亮点:

统一的 Document 抽象。所有读取器都返回 Document 对象,这让后续处理(分块、嵌入、存储)变得统一。不管是从 PDF 还是 Word 读取,出来的都是 Document,处理起来很方便。

灵活的读取策略。不同格式有不同的读取策略(按页、按段落、按标题),可以根据需求选择最合适的方式。PDF 可以按页读,也可以按段落读,看你的需求。

智能的分块机制。Token 分块不仅考虑大小,还考虑语义完整性(在标点符号处截断),这提高了检索效果。不会在句子中间截断,保持语义完整。

元数据保留。分块时会保留原始文档的元数据,并添加分块相关的元数据(parent_document_id、chunk_index 等),这有助于追踪和调试。想知道某个分块来自哪个文档?看元数据就行。

可扩展性。所有组件都通过接口定义,可以轻松实现自定义的读取器和分块器。想支持新的文档格式?实现 DocumentReader 接口就行。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/9 5:15:31

学校食堂出入库管理软件

学校食堂出入库管理软件是用于数字化管理食材从采购入库到加工出库全过程的系统&#xff0c;旨在提升效率、降低成本和保障食品安全。这类软件通常集成库存控制、成本核算、溯源追踪等功能&#xff0c;适用于中小学、高校及多校区食堂的运营需求。核心功能特点 1、自动化出入库…

作者头像 李华
网站建设 2026/2/9 7:30:44

基于MATLAB的线性判别分析(LDA)降维算法实现方案

一、核心算法 1. 类内散度矩阵计算 function Sw computeSw(X, y, classes)[nSamples, nFeatures] size(X);Sw zeros(nFeatures, nFeatures);for i 1:length(classes)idx (y classes(i));classSamples X(idx, :);mu mean(classSamples);Sw Sw (classSamples - mu) * (…

作者头像 李华
网站建设 2026/2/7 14:25:57

【Java毕设源码分享】基于springboot+vue的线上高校奖助学金系统设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/2/7 23:37:34

【Java毕设源码分享】基于springboot+vue的高校教室资源管理系统的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/2/4 19:53:25

被裁后,我却更自由了:不同求职机构的冰与火

从“被裁”到“自由”&#xff1a;一份关于留学生回国求职辅导市场的真实报告“裁员不一定是职业生涯的终点&#xff0c;它可能是一次被迫的‘价值重估’&#xff0c;而留学生内推就业的冰与火&#xff0c;恰恰源于这次重估的标准是否真实、有效。”最近&#xff0c;我们注意到…

作者头像 李华
网站建设 2026/2/7 17:44:11

从焦虑到从容:这款AI工具如何帮我高效完成5万字硕士论文

一个普通研究生的真实经历&#xff1a;如何用智能写作工具系统性地完成毕业论文&#xff0c;告别熬夜与拖延凌晨三点的实验室&#xff0c;只有我的电脑屏幕还亮着。文档右下角的字数统计停留在8732&#xff0c;而距离截稿日只剩两周。微信群突然弹出导师的消息&#xff1a;“论…

作者头像 李华