ETL Pipeline
提取、转换和加载 (ETL) 框架是检索增强生成 (RAG) 用例中数据处理的支柱。
ETL 管道协调从原始数据源到结构化向量存储的流程,确保数据采用 AI 模型检索的最佳格式。
RAG 用例是文本,通过从数据主体中检索相关信息来增强生成模型的功能,从而提高生成输出的质量和相关性。
API 概述
ETL 管道创建、转换和存储Document实例。
该类Document包含文本、元数据和可选的附加媒体类型,如图像、音频和视频。
ETL 管道有三个主要组件,
- DocumentReader 实现 Supplier<List<Document>>
- DocumentTransformer 实现 Function<List<Document>, List<Document>>
- DocumentWriter 实现 Consumer<List<Document>>
课程Document内容是借助 PDF、文本文件和其他文档类型创建的DocumentReader。
要构建一个简单的 ETL 管道,您可以将每种类型的实例链接在一起。
假设我们有以下这三种 ETL 类型的实例
- PagePdfDocumentReader 实现 DocumentReader
- TokenTextSplitter 实现 DocumentTransformer
- VectorStore 实现 DocumentWriter
要执行将数据基本加载到矢量数据库中以供检索增强生成模式使用的操作,请使用以下 Java 函数样式语法代码。
vectorStore.accept(tokenTextSplitter.apply(pdfReader.get()));
或者,您可以使用更自然地表达域的方法名称
vectorStore.write(tokenTextSplitter.split(pdfReader.read()));
ETL 接口
ETL 管道由以下接口和实现组成。
文档阅读器
提供来自不同来源的文档来源。
public interface DocumentReader extends Supplier<List<Document>> {
default List<Document> read() {
return get();
}
}
文档转换器
作为处理工作流程的一部分,转换一批文档。
public interface DocumentTransformer extends Function<List<Document>, List<Document>> {
default List<Document> transform(List<Document> transform) {
return apply(transform);
}
}
文档编写器
管理 ETL 过程的最后阶段,准备要存储的文档。
public interface DocumentWriter extends Consumer<List<Document>> {
default void write(List<Document> documents) {
accept(documents);
}
}
ETL 类图
下面的类图说明了 ETL 接口和实现。
文档阅读器
JSON
处理JsonReaderJSON 文档,将其转换为对象列表Document。
例子
@Component
class MyJsonReader {
private final Resource resource;
MyJsonReader(@Value("classpath:bikes.json") Resource resource) {
this.resource = resource;
}
List<Document> loadJsonAsDocuments() {
JsonReader jsonReader = new JsonReader(this.resource, "description", "content");
return jsonReader.get();
}
}
构造函数选项
提供JsonReader了几个构造函数选项:
- JsonReader(Resource resource)
- JsonReader(Resource resource, String… jsonKeysToUse)
- JsonReader(Resource resource, JsonMetadataGenerator jsonMetadataGenerator, String… jsonKeysToUse)
参数
- resourceResource:指向 JSON 文件的Spring对象。
- jsonKeysToUse:JSON 中的键数组,应用于结果Document对象中的文本内容。
- jsonMetadataGeneratorJsonMetadataGenerator:为每个创建元数据的可选选项Document。
行为
流程JsonReaderJSON内容如下:
- 它可以处理 JSON 数组和单个 JSON 对象。
- 对于每个 JSON 对象(无论是数组还是单个对象):
- 它根据指定的提取内容jsonKeysToUse。
- 如果没有指定键,则使用整个 JSON 对象作为内容。
- 它使用提供的元数据JsonMetadataGenerator(如果未提供则为空)生成元数据。
- Document它使用提取的内容和元数据创建一个对象。
使用 JSON 指针
现在JsonReader支持使用 JSON 指针检索 JSON 文档的特定部分。此功能可让您轻松地从复杂的 JSON 结构中提取嵌套数据。
方法 get(String pointer)
public List<Document> get(String pointer)
此方法允许您使用 JSON 指针来检索 JSON 文档的特定部分。
参数
- pointer:JSON 指针字符串(如 RFC 6901 中定义),用于在 JSON 结构中定位所需元素。
返回值
返回List<Document>
包含从指针定位的 JSON 元素解析的文档。
行为
- 该方法使用提供的 JSON 指针导航到 JSON 结构中的特定位置。
- 如果指针有效并且指向现有元素:
- 对于 JSON 对象:它返回一个包含单个文档的列表。
- 对于 JSON 数组:它返回一个文档列表,数组中的每个元素对应一个文档。
- 如果指针无效或指向不存在的元素,则会抛出IllegalArgumentException。
例子
JsonReader jsonReader = new JsonReader(resource, "description");
List<Document> documents = this.jsonReader.get("/store/books/0");
JSON 结构示例
[
{
"id": 1,
"brand": "Trek",
"description": "A high-performance mountain bike for trail riding."
},
{
"id": 2,
"brand": "Cannondale",
"description": "An aerodynamic road bike for racing enthusiasts."
}
]
在这个例子中,如果JsonReader配置为 “description”,jsonKeysToUse 它将创建Document对象,其内容是数组中每辆自行车的“描述”字段的值。
笔记
- 使用JsonReaderJackson 进行 JSON 解析。
- 它可以通过使用数组流来有效地处理大型 JSON 文件。
- 如果在中指定了多个键jsonKeysToUse,则内容将是这些键的值的串联。
- jsonKeysToUse该阅读器非常灵活,可以通过自定义和来适应各种 JSON 结构JsonMetadataGenerator。
文本
处理TextReader纯文本文档,将其转换为对象列表Document。
例子
@Component
class MyTextReader {
private final Resource resource;
MyTextReader(@Value("classpath:text-source.txt") Resource resource) {
this.resource = resource;
}
List<Document> loadText() {
TextReader textReader = new TextReader(this.resource);
textReader.getCustomMetadata().put("filename", "text-source.txt");
return textReader.read();
}
}
构造函数选项
提供TextReader了两个构造函数选项:
TextReader(String resourceUrl)
TextReader(Resource resource)
参数
resourceUrl:表示要读取的资源的URL的字符串。
resourceResource:指向文本文件的Spring对象。
配置
setCharset(Charset charset):设置读取文本文件的字符集。默认为 UTF-8。
getCustomMetadata():返回一个可变映射,您可以在其中为文档添加自定义元数据。
行为
流程TextReader文本内容如下:
它将文本文件的全部内容读入单个Document对象。
该文件的内容将成为的内容Document。
元数据自动添加到Document:
charset:读取文件所用的字符集(默认:“UTF-8”)。
source:源文本文件的文件名。
通过添加的任何自定义元数据getCustomMetadata()都包含在内Document。
笔记
将TextReader整个文件内容读入内存,因此可能不适合非常大的文件。
如果需要将文本拆分成更小的块,则可以TokenTextSplitter在阅读文档后使用文本拆分器:
List
List
读取器使用 Spring 的Resource抽象,允许它从各种来源(类路径、文件系统、URL 等)读取。
可以将自定义元数据添加到读者使用该getCustomMetadata()方法创建的所有文档中。
HTML(JSoup)
处理HTML 文档,使用 JSoup 库JsoupDocumentReader将其转换为对象列表。Document
例子
@Component
class MyHtmlReader {
private final Resource resource;
MyHtmlReader(@Value("classpath:/my-page.html") Resource resource) {
this.resource = resource;
}
List<Document> loadHtml() {
JsoupDocumentReaderConfig config = JsoupDocumentReaderConfig.builder()
.selector("article p") // Extract paragraphs within <article> tags
.charset("ISO-8859-1") // Use ISO-8859-1 encoding
.includeLinkUrls(true) // Include link URLs in metadata
.metadataTags(List.of("author", "date")) // Extract author and date meta tags
.additionalMetadata("source", "my-page.html") // Add custom metadata
.build();
JsoupDocumentReader reader = new JsoupDocumentReader(this.resource, config);
return reader.get();
}
}
允许JsoupDocumentReaderConfig您自定义的行为JsoupDocumentReader:
charset:指定HTML文档的字符编码(默认为“UTF-8”)。
selector:JSoup CSS 选择器,用于指定从哪些元素中提取文本(默认为“body”)。
separator:用于连接多个选定元素的文本的字符串(默认为“\n”)。
allElements:如果true,则从元素中提取所有文本,忽略selector(默认为false)。
groupByElement:如果,则为 匹配的每个元素true创建一个单独的(默认为)。Documentselectorfalse
includeLinkUrls:如果true,则提取绝对链接 URL 并将其添加到元数据中(默认为false)。
metadataTags:要从中提取内容的标签名称列表(默认为[“description”, “keywords”])。
additionalMetadata:允许您向所有创建的对象添加自定义元数据Document。
示例文档:my-page.html
Welcome to My Page
Main Content
This is the main content of my web page.
It contains multiple paragraphs.
External Link处理JsoupDocumentReaderHTML 内容并Document根据配置创建对象:
确定selector哪些元素用于文本提取。
如果allElements是true,则将内的所有文本提取到单个中Document。
如果groupByElement是true,则每个匹配的元素selector都会创建一个单独的Document。
如果 和allElements都不groupByElement是,则使用 连接true所有与 匹配的元素的文本。selectorseparator
文档标题、指定标签的内容以及(可选)链接 URL 被添加到Document元数据中。
用于解析相对链接的基本 URI 将从 URL 资源中提取。
阅读器保留所选元素的文本内容,但删除其中的任何 HTML 标签。
Markdown
处理MarkdownDocumentReaderMarkdown 文档,将其转换为对象列表Document。
例子
@Component
class MyMarkdownReader {
private final Resource resource;
MyMarkdownReader(@Value("classpath:code.md") Resource resource) {
this.resource = resource;
}
List<Document> loadMarkdown() {
MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder()
.withHorizontalRuleCreateDocument(true)
.withIncludeCodeBlock(false)
.withIncludeBlockquote(false)
.withAdditionalMetadata("filename", "code.md")
.build();
MarkdownDocumentReader reader = new MarkdownDocumentReader(this.resource, config);
return reader.get();
}
}
允许MarkdownDocumentReaderConfig您自定义 MarkdownDocumentReader 的行为:
horizontalRuleCreateDocument:设置为 时true,Markdown 中的水平规则将创建新的Document对象。
includeCodeBlock:设置为 时,代码块将与周围文本true包含在内。设置为 时,代码块将创建单独的对象。DocumentfalseDocument
includeBlockquote:设置为 时,块引用将与周围文本true包含在内。设置为 时,块引用将创建单独的对象。DocumentfalseDocument
additionalMetadata:允许您向所有创建的对象添加自定义元数据Document。
示例文档:code.md
This is a Java sample application:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
Markdown also provides the possibility to use inline code formatting throughout
the entire sentence.
Another possibility is to set block code without specific highlighting:
./mvnw spring-javaformat:apply
行为:MarkdownDocumentReader 处理 Markdown 内容并根据配置创建 Document 对象:
标题成为 Document 对象中的元数据。
段落成为 Document 对象的内容。
代码块可以分离到自己的 Document 对象中或包含在周围的文本中。
块引用可以分离到自己的 Document 对象中,也可以包含在周围的文本中。
水平规则可用于将内容分割成单独的 Document 对象。
阅读器保留 Document 对象内容中的内联代码、列表和文本样式等格式。
PDF页面
使用PagePdfDocumentReaderApache PdfBox 库来解析 PDF 文档
使用 Maven 或 Gradle 将依赖项添加到您的项目。
dependencies {
implementation ‘org.springframework.ai:spring-ai-pdf-document-reader’
}
例子
@Component
public class MyPagePdfDocumentReader {
List<Document> getDocsFromPdf() {
PagePdfDocumentReader pdfReader = new PagePdfDocumentReader("classpath:/sample1.pdf",
PdfDocumentReaderConfig.builder()
.withPageTopMargin(0)
.withPageExtractedTextFormatter(ExtractedTextFormatter.builder()
.withNumberOfTopTextLinesToDelete(0)
.build())
.withPagesPerDocument(1)
.build());
return pdfReader.read();
}
}
PDF段落
使用ParagraphPdfDocumentReaderPDF 目录(例如目录)信息将输入的 PDF 拆分为文本段落,并按Document段落输出单个文本。注意:并非所有 PDF 文档都包含 PDF 目录。
依赖项
使用 Maven 或 Gradle 将依赖项添加到您的项目。
dependencies {
implementation ‘org.springframework.ai:spring-ai-pdf-document-reader’
}
例子
@Component
public class MyPagePdfDocumentReader {
List<Document> getDocsFromPdfWithCatalog() {
ParagraphPdfDocumentReader pdfReader = new ParagraphPdfDocumentReader("classpath:/sample1.pdf",
PdfDocumentReaderConfig.builder()
.withPageTopMargin(0)
.withPageExtractedTextFormatter(ExtractedTextFormatter.builder()
.withNumberOfTopTextLinesToDelete(0)
.build())
.withPagesPerDocument(1)
.build());
return pdfReader.read();
}
}
Tika(DOCX、PPTX、HTML……)
使用TikaDocumentReaderApache Tika 从各种文档格式(例如 PDF、DOC/DOCX、PPT/PPTX 和 HTML)中提取文本。有关受支持格式的完整列表,请参阅 Tika 文档。
依赖项
或者你的 Gradlebuild.gradle构建文件。
dependencies {
implementation ‘org.springframework.ai:spring-ai-tika-document-reader’
}
例子
@Component
class MyTikaDocumentReader {
private final Resource resource;
MyTikaDocumentReader(@Value("classpath:/word-sample.docx")
Resource resource) {
this.resource = resource;
}
List<Document> loadText() {
TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(this.resource);
return tikaDocumentReader.read();
}
}
变形金刚
文本分割器
一个TextSplitter抽象基类,有助于划分文档以适应 AI 模型的上下文窗口。
TokenTextSplitter
这是一种使用 CL100K_BASE 编码根据标记计数将文本拆分成块的TokenTextSplitter实现。TextSplitter
用法
@Component
class MyTokenTextSplitter {
public List<Document> splitDocuments(List<Document> documents) {
TokenTextSplitter splitter = new TokenTextSplitter();
return splitter.apply(documents);
}
public List<Document> splitCustomized(List<Document> documents) {
TokenTextSplitter splitter = new TokenTextSplitter(1000, 400, 10, 5000, true);
return splitter.apply(documents);
}
}
构造函数选项
提供TokenTextSplitter了两个构造函数选项:
TokenTextSplitter():使用默认设置创建拆分器。
TokenTextSplitter(int defaultChunkSize, int minChunkSizeChars, int minChunkLengthToEmbed, int maxNumChunks, boolean keepSeparator)
参数
defaultChunkSize:标记中每个文本块的目标大小(默认值:800)。
minChunkSizeChars:每个文本块的最小字符数(默认值:350)。
minChunkLengthToEmbed:要包含的块的最小长度(默认值:5)。
maxNumChunks:从文本生成的最大块数(默认值:10000)。
keepSeparator:是否在块中保留分隔符(如换行符)(默认值:true)。
行为
流程TokenTextSplitter文本内容如下:
它使用 CL100K_BASE 编码将输入文本编码为标记。
它根据将编码文本拆分成块defaultChunkSize。
对于每个块:
它将块解码回文本。
它尝试在 之后找到合适的断点(句号、问号、感叹号或换行符)minChunkSizeChars。
如果发现断点,它会截断该点处的块。
它会修剪块并根据设置选择性地删除换行符keepSeparator。
如果结果块长于minChunkLengthToEmbed,则将其添加到输出中。
该过程持续进行,直到所有令牌都被处理或maxNumChunks达到。
如果剩余文本的长度超过,则会将其添加为最终块minChunkLengthToEmbed。
例子
Document doc1 = new Document(“This is a long piece of text that needs to be split into smaller chunks for processing.”,
Map.of(“source”, “example.txt”));
Document doc2 = new Document(“Another document with content that will be split based on token count.”,
Map.of(“source”, “example2.txt”));
TokenTextSplitter splitter = new TokenTextSplitter();
List
for (Document doc : splitDocuments) {
System.out.println(“Chunk: “ + doc.getContent());
System.out.println(“Metadata: “ + doc.getMetadata());
}
笔记
该库TokenTextSplitter使用 CL100K_BASE 编码jtokkit,与较新的 OpenAI 模型兼容。
分割器尝试通过在可能的情况下断开句子边界来创建具有语义意义的块。
原始文档的元数据被保留并复制到从该文档派生的所有块。
如果设置为(默认行为) ,则原始文档中的内容格式化程序(如果copyContentFormatter设置)也会被复制到派生块中。true
该分割器对于为具有标记限制的大型语言模型准备文本特别有用,可确保每个块都在模型的处理能力范围内。
内容格式转换器
确保所有文档的内容格式统一。
关键字元数据丰富器
这KeywordMetadataEnricher是一种DocumentTransformer使用生成式 AI 模型从文档内容中提取关键字并将其添加为元数据的方法。
用法
@Component
class MyKeywordEnricher {
private final ChatModel chatModel;
MyKeywordEnricher(ChatModel chatModel) {
this.chatModel = chatModel;
}
List<Document> enrichDocuments(List<Document> documents) {
KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
.keywordCount(5)
.build();
// Or use custom templates
KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
.keywordsTemplate(YOUR_CUSTOM_TEMPLATE)
.build();
return enricher.apply(documents);
}
}
构造函数选项
提供KeywordMetadataEnricher了两个构造函数选项:
KeywordMetadataEnricher(ChatModel chatModel, int keywordCount):使用默认模板,提取指定数量的关键词。
KeywordMetadataEnricher(ChatModel chatModel, PromptTemplate keywordsTemplate):使用自定义模板进行关键词提取。
行为
流程KeywordMetadataEnricher文件如下:
对于每个输入文档,它使用文档的内容创建一个提示。
它将这个提示发送给提供者ChatModel以生成关键字。
生成的关键字将添加到文档元数据中的“excerpt_keywords”键下。
返回丰富的文档。
定制
您可以使用默认模板,也可以通过keywordsTemplate参数自定义模板。默认模板为:
{context_str}. Give %s unique keywords for this document. Format as comma separated. Keywords:
其中{context_str}替换为文档内容,%s替换为指定的关键字数。
例子
ChatModel chatModel = // initialize your chat model
KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
.keywordCount(5)
.build();
// Or use custom templates
KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
.keywordsTemplate(new PromptTemplate(“Extract 5 important keywords from the following text and separate them with commas:\n{context_str}”))
.build();
Document doc = new Document(“This is a document about artificial intelligence and its applications in modern technology.”);
List
Document enrichedDoc = this.enrichedDocs.get(0);
String keywords = (String) this.enrichedDoc.getMetadata().get(“excerpt_keywords”);
System.out.println(“Extracted keywords: “ + keywords);
笔记
需要KeywordMetadataEnricher功能ChatModel来生成关键字。
关键字数量必须为 1 或更大。
丰富器将“excerpt_keywords”元数据字段添加到每个处理的文档中。
生成的关键字以逗号分隔的字符串形式返回。
该丰富器对于提高文档可搜索性和为文档生成标签或类别特别有用。
在Builder模式中,如果keywordsTemplate设置了该参数,则该keywordCount参数将被忽略。
摘要元数据丰富器
这SummaryMetadataEnricher是一种DocumentTransformer使用生成式 AI 模型为文档创建摘要并将其添加为元数据的工具。它可以为当前文档以及相邻文档(上一个和下一个)生成摘要。
用法
@Configuration
class EnricherConfig {
@Bean
public SummaryMetadataEnricher summaryMetadata(OpenAiChatModel aiClient) {
return new SummaryMetadataEnricher(aiClient,
List.of(SummaryType.PREVIOUS, SummaryType.CURRENT, SummaryType.NEXT));
}
}
@Component
class MySummaryEnricher {
private final SummaryMetadataEnricher enricher;
MySummaryEnricher(SummaryMetadataEnricher enricher) {
this.enricher = enricher;
}
List<Document> enrichDocuments(List<Document> documents) {
return this.enricher.apply(documents);
}
}
构造函数
提供SummaryMetadataEnricher了两个构造函数:
SummaryMetadataEnricher(ChatModel chatModel, List
SummaryMetadataEnricher(ChatModel chatModel, List
参数
chatModel:用于生成摘要的AI模型。
summaryTypesSummaryType:指示要生成哪些摘要的枚举值列表(PREVIOUS、CURRENT、NEXT)。
summaryTemplate:用于摘要生成的自定义模板(可选)。
metadataMode:指定生成摘要时如何处理文档元数据(可选)。
行为
流程SummaryMetadataEnricher文件如下:
对于每个输入文档,它使用文档的内容和指定的摘要模板创建一个提示。
它将这个提示发送给提供者ChatModel以生成摘要。
根据指定的summaryTypes,它会向每个文档添加以下元数据:
section_summary:当前文档的摘要。
prev_section_summary:先前文件的摘要(如果有且被要求)。
next_section_summary:下一份文件的摘要(如果有且被要求)。
返回丰富的文档。
定制
可以通过提供自定义 来定制摘要生成提示summaryTemplate。默认模板为:
“””
Here is the content of the section:
{context_str}
Summarize the key topics and entities of the section.
Summary:
“””
例子
ChatModel chatModel = // initialize your chat model
SummaryMetadataEnricher enricher = new SummaryMetadataEnricher(chatModel,
List.of(SummaryType.PREVIOUS, SummaryType.CURRENT, SummaryType.NEXT));
Document doc1 = new Document(“Content of document 1”);
Document doc2 = new Document(“Content of document 2”);
List
// Check the metadata of the enriched documents
for (Document doc : enrichedDocs) {
System.out.println(“Current summary: “ + doc.getMetadata().get(“section_summary”));
System.out.println(“Previous summary: “ + doc.getMetadata().get(“prev_section_summary”));
System.out.println(“Next summary: “ + doc.getMetadata().get(“next_section_summary”));
}
提供的示例演示了预期的行为:
对于两个文档的列表,两个文档都会收到section_summary。
第一份文件收到了next_section_summary但没有prev_section_summary。
第二份文件收到了prev_section_summary但没有next_section_summary。
第一个文档的与第二个文档的section_summary匹配。prev_section_summary
第一个文档的与第二个文档的next_section_summary匹配。section_summary
笔记
需要SummaryMetadataEnricher功能ChatModel来生成摘要。
丰富器可以处理任意大小的文档列表,并正确处理第一个和最后一个文档的边缘情况。
该丰富器对于创建上下文感知摘要特别有用,可以更好地理解序列中的文档关系。
该MetadataMode参数允许控制如何将现有元数据合并到摘要生成过程中。
作家
文件
这FileDocumentWriter是一种将对象DocumentWriter列表的内容写入Document文件的实现。
用法
@Component
class MyDocumentWriter {
public void writeDocuments(List<Document> documents) {
FileDocumentWriter writer = new FileDocumentWriter("output.txt", true, MetadataMode.ALL, false);
writer.accept(documents);
}
}
构造函数
提供FileDocumentWriter了三个构造函数:
FileDocumentWriter(String fileName)
FileDocumentWriter(String fileName, boolean withDocumentMarkers)
FileDocumentWriter(String fileName, boolean withDocumentMarkers, MetadataMode metadataMode, boolean append)
参数
fileName:要写入文档的文件的名称。
withDocumentMarkers:是否在输出中包含文档标记(默认值:false)。
metadataMode:指定要写入文件的文档内容(默认值:MetadataMode.NONE)。
append:如果为真,数据将写入文件末尾而不是开头(默认值:false)。
行为
流程FileDocumentWriter文件如下:
它为指定的文件名打开一个 FileWriter。
对于输入列表中的每个文档:
如果withDocumentMarkers为真,它会写入一个包含文档索引和页码的文档标记。
它根据指定的格式写入文档的内容metadataMode。
所有文档写入后,文件将关闭。
文档标记
当withDocumentMarkers设置为 true 时,编写器将以以下格式为每个文档包含标记:
Doc: [index], pages:[start_page_number,end_page_number]
元数据处理
编写器使用两个特定的元数据键:
page_number:代表文档的起始页码。
end_page_number:代表文档的结束页码。
这些用于编写文档标记。
例子
List
FileDocumentWriter writer = new FileDocumentWriter(“output.txt”, true, MetadataMode.ALL, true);
writer.accept(documents);
这会将所有文档写入“output.txt”,包括文档标记,使用所有可用的元数据,如果文件已存在则附加到该文件。
笔记
编写器使用FileWriter,因此它使用操作系统的默认字符编码编写文本文件。
如果在写入过程中发生错误,RuntimeException则会抛出一个错误,其原因为原始异常。
该metadataMode参数允许控制如何将现有元数据合并到书面内容中。
该编写器对于调试或创建文档集合的人类可读输出特别有用。
最后编辑:Jeebiz 更新时间:2025-08-08 00:47