- 文档
- 知识库
- 教程
- OpenHarmony
【免费下载链接】CangjieCorpus
本仓库为仓颉编程语言语料库,聚焦 仓颉编程语言 核心知识体系,整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源,经结构化处理与专业标注,构建为支持 RAG(检索增强生成)技术 的高质量知识基座,适配智能编码助手、学习辅助工具等应用开发需求。 欢迎开发者基于本语料库构建仓颉语言智能应用,推动鸿蒙开发生态智能化升级!
本篇技术指南围绕仓颉编程语言(Cangjie)的I/O 节点流展开,系统讲解标准流(Console封装的标准输入/输出/错误流)与文件流(std.fs包的File类型)的概念、API 用法与底层实现原理。读者学完后,将掌握如何在命令行程序中读取键盘输入、向屏幕输出内容,以及如何通过常规文件操作、流式读写和 try-with-resource 语法高效地处理文件数据。文中所有结论均有本仓库内的官方文档与 API 源码佐证,可直接对照验证。
什么是节点流
仓颉编程语言将程序与外部载体交互的操作抽象为I/O(Input 对应输入,Output 对应输出),所有 I/O 机制都基于数据流(Stream)进行,流本质上是字节数据的有序序列,就像承载数据的管道(参见 I/O 流概述)。
按照职责差异,流可分成两类:
- 节点流(Node Stream):直接提供数据源,其构造方式通常依赖某种直接的外部资源,如文件、网络等;
- 处理流(Process Stream):只能代理其他数据流进行处理,构造方式通常依赖其他流,如缓冲流、字符串流(详见姊妹篇 I/O 处理流)。
仓颉编程语言中常见的节点流包括:
| 节点流 | 数据源 |
|---|---|
| 标准流(StdIn、StdOut、StdErr) | 键盘、终端屏幕 |
| 文件流(File) | 文件系统中的文件 |
| 网络流(Socket) | 网络连接 |
本章重点介绍标准流和文件流两类最常用的节点流。
标准流:程序与外部交互的标准接口
标准流包含三个部分:
- 标准输入流(stdin):程序从外部读取数据的通道,默认来源于键盘输入,例如命令行界面中输入的文本;
- 标准输出流(stdout):程序输出运行结果的通道,默认输出到屏幕;
- 标准错误流(stderr):程序输出错误信息的通道,默认也输出到屏幕。
在 Unix/Linux 系统中,标准输入、标准输出和标准错误分别对应文件描述符 0、1 和 2(参见 std.console 包概览)。在仓颉编程语言中,可以通过Console类型分别访问它们。
导入 console 包
使用Console类型需要先导入console包:
import std.console.*Console对三个标准流都进行了易用性封装,提供了更方便的基于String的扩展操作,并且针对很多常见类型(整数、浮点数、布尔值、Rune、String等)都提供了丰富的write重载来优化性能。其中最重要的是,Console提供了并发安全的保证,可以在任意线程中安全地通过Console提供的接口来读写内容。
说明:在 API 文档中
std.console包被标记为deprecated,未来版本将废弃,届时可使用env包中的相应函数替代(详见 console 包类定义)。本文示例基于当前仓库实际 API 编写。
读取标准输入:stdIn
当需要从标准输入流中获取数据时,可以通过Console.stdIn来读取,例如通过readln函数获取命令行的输入:
import std.console.* main() { let txt = Console.stdIn.readln() println(txt ?? "") }运行上面的代码,在命令行上输入一些文字,然后换行结束,即可在屏幕上看到输入的内容。
从源码看,Console.stdIn的类型是ConsoleReader(其声明见 console 包类定义),该类实现了InputStream接口,提供了多种读取方式:
read(): ?Rune:从标准输入中读取下一个字符,输入不符合 UTF-8 编码时抛出IllegalArgumentException;read(arr: Array<Byte>): Int64:将读取的字节放入数组并返回字节长度,注意可能将 UTF-8 码点从中截断,导致转字符串结果不正确或抛异常;readln(): ?String:读取一行字符串,结果不包含末尾换行符,读取失败返回None;readToEnd(): ?String:读取所有字符,直到 EOF(Linux 键入Ctrl+D、Windows 键入Ctrl+Z加回车);readUntil(predicate: (Rune) -> Bool): ?String与readUntil(ch: Rune): ?String:按条件或按终止字符读取。
ConsoleReader只有一个实例,所有方法共享同一个缓存区。读操作是同步的,内部设有缓存区保存控制台输入内容,当到达控制台输入流结尾时读取函数返回None。readln的返回值是?String可空类型,因此上面的示例使用?? ""提供缺省值。
写入标准输出与标准错误:stdOut / stdErr
输出流分为标准输出流和标准错误流,默认情况下它们都会输出到屏幕。当需要写入数据时,可以通过Console.stdOut/Console.stdErr写入,例如使用write/writeln函数向命令行打印内容:
import std.console.* main() { for (i in 0..1000) { Console.stdOut.writeln("hello, world!") } Console.stdOut.flush() }使用stdOut与直接使用print函数相比有两个显著差别:
- 并发安全:
ConsoleWriter保证线程安全,每次write调用写到控制台的结果是完整的,不同 write 调用之间的结果不会混合(见 console 包类定义); - 缓存与性能:
stdOut使用了缓存技术,在输入内容较多时拥有更好的性能表现。
需要注意:写完数据后需要对stdOut调用flush,才能保证内容被真正写到标准流中。这是因为ConsoleWriter的输出带有缓冲策略,数据先进入缓冲区,只有缓冲区被填满或主动调用flush时才真实写入,这与OutputStream接口中flush的设计一致(参见 I/O 接口定义)。
ConsoleWriter实现了OutputStream接口,并提供了丰富的重载:write/writeln支持Array<Byte>、Bool、Float16/Float32/Float64、Int8/Int16/Int32/Int64、UInt8/.../UInt64、Rune、String,以及泛型版本write<T>(v: T) where T <: ToString,这意味着任何实现了ToString接口的自定义类型都可以直接写入标准流。
参考 Console 官方示例,一个完整的交互程序可以这样写:
import std.console.* main() { Console.stdOut.write("请输入信息1:") var c = Console.stdIn.readln() var r = c.getOrThrow() Console.stdOut.writeln("输入的信息1为:" + r) Console.stdOut.write("请输入信息2:") c = Console.stdIn.readln() r = c.getOrThrow() Console.stdOut.writeln("输入的信息2为:" + r) }运行结果(用户依次输入两行内容):
请输入信息1:你好,请问今天星期几? 输入的信息1为:你好,请问今天星期几? 请输入信息2:你好,请问今天几号? 输入的信息2为:你好,请问今天几号?文件流:基于 fs 包的统一文件系统接口
仓颉编程语言提供了fs包来支持通用文件系统任务。不同操作系统对文件系统提供的接口有所不同,仓颉抽象出共通功能,通过统一接口屏蔽操作系统差异来简化使用(当前支持 Linux、macOS、Windows 和 HarmonyOS 平台,见 std.fs 包概览)。
常规操作任务包括:创建文件/目录、读写文件、重命名或移动文件/目录、删除文件/目录、复制文件/目录、获取文件/目录元数据、检查文件/目录是否存在。使用文件系统相关功能需要导入fs包:
import std.fs.*File类型在仓颉编程语言中同时提供了常规文件操作和文件流两类功能。对于Path和Directory的使用,可以查阅 fs 包 API。
常规文件操作:静态函数一族
对于常规文件操作,可以使用一系列静态函数完成快捷操作。
检查文件是否存在:使用exists函数,返回true表示文件存在,反之不存在。
import std.fs.* main() { let exist = exists("./tempFile.txt") println("exist: ${exist}") }移动、拷贝和删除文件:File同样提供了对应的静态函数copy、rename、remove(源码中它们实际上是fs包顶层函数,见 fs 包函数定义):
import std.fs.* main() { copy("./tempFile.txt", to: "./tempFile2.txt", overwrite: false) rename("./tempFile2.txt", to: "./tempFile3.txt", overwrite: false) remove("./tempFile3.txt") }结合源码,这三个函数的关键语义是:
copy(sourcePath, to!, overwrite! = false):复制文件或目录。目标位置存在且overwrite为true时,要求源与目标类型一致(如源为目录则目标也须为目录);overwrite为false且目标已存在时抛出FSException;rename(sourcePath, to!, overwrite! = false):重命名或移动。overwrite为true时会删除目标位置的原有文件或目录,若目标是目录会递归删除其中所有内容,需谨慎使用;remove(path, recursive! = false):删除文件或目录,删除文件夹时可选择是否递归。
此外还有removeIfExists(存在则删除并返回true)、canonicalize(规范化路径)等函数可查阅 fs 包函数定义。
一次性读写整个文件:如果需要直接将文件的所有数据读出来,或一次性将数据写入文件,可以使用File提供的静态函数readFrom、writeTo:
import std.fs.* main() { let bytes = File.readFrom("./tempFile.txt") // 一次性读取了所有的数据 File.writeTo("./otherFile.txt", bytes) // 把数据一次性写入另一个文件中 }从源码(fs 包类定义)看:
File.readFrom(path):以字节数组形式返回文件全部内容,文件不存在或不可读时抛出FSException;File.writeTo(path, buffer):以覆盖方式写入,文件存在时会先截断为零字节大小,文件不存在则创建;- 配套的
File.appendTo(path, buffer):以追加方式写入,文件不存在则创建,适合日志累加等场景。
在数据量较少的情况下,这类函数既简单易用又能提供较好的性能表现,无需手动处理数据流;但若要处理大文件或需要精细控制,则应使用下面的文件流操作。
文件流操作:File 即数据流
除了常规文件操作,File类型本身也被设计为一种数据流类型,实现了IOStream接口。当创建了一个File实例,就可以把它当成数据流来使用。
File 类定义(见 fs 包类定义):
public class File <: Resource & IOStream & Seekable { ... }这一声明揭示了三个关键能力:
Resource:实现close()与isClosed(),可配合 try-with-resource 语法自动释放资源(见 core 包 Resource 接口);IOStream:同时具备InputStream.read与OutputStream.write/flush能力(见 I/O 接口定义);Seekable:提供length、position、remainLength属性与seek函数,支持移动读写光标,配合SeekPosition枚举(Begin、Current、End)实现定位读取(见 I/O 枚举定义)。
两种构造方式
File提供了两种构造方式:
- 静态函数
create:直接创建新文件实例。注意create创建的文件是只写的,不能对实例进行读操作,否则会抛出运行时异常;且当文件已存在或上级目录不存在时抛出FSException; - 构造函数传入打开模式:通过
File(path, mode)构造新实例。
File 构造示例:
// 创建 internal import std.fs.* internal import std.io.* main() { let file = File.create("./tempFile.txt") file.write("hello, world!".toArray()) // 打开 let file2 = File("./tempFile.txt", Read) let bytes = readToEnd(file2) // 读取所有数据 println(bytes) }这里使用了std.io包提供的readToEnd(from)函数:读取入参中所有未被读取的数据,以字节数组返回(见 io 包函数定义)。io包还提供了readString(校验 UTF-8)、readStringUnchecked(不校验)、copy(from, to!)等流工具函数。
OpenMode 打开模式
当需要更精细的打开模式时,可以使用构造函数传入一个OpenMode值。OpenMode是一个enum类型,提供了丰富的文件打开模式(见 fs 包枚举定义):
public enum OpenMode <: ToString & Equatable<OpenMode> { | Read | Write | Append | ReadWrite }| 模式 | 语义 |
|---|---|
Read | 只读方式打开。文件不存在时抛出FSException |
Write | 只写方式打开。文件存在时截断为零字节大小,文件不存在则创建 |
Append | 追加写入方式打开。文件不存在则创建,写入位置始终在文件末尾 |
ReadWrite | 可读可写方式打开。文件不存在则创建,但不会将文件截断为零字节 |
File 打开模式使用示例:
// 使用指定选项打开模式 let file = File("./tempFile.txt", Write)资源释放与 try-with-resource
打开File实例会占用宝贵的系统资源(文件句柄),使用完后需要及时关闭File以释放资源。File实现了Resource接口,在大多数时候都可以使用try-with-resource语法来简化使用:
try (file2 = File("./tempFile.txt", Read)) { ... // 结束使用后自动释放文件 }该语法会在代码块结束时自动调用close()释放文件,即使块中抛出异常也会保证资源被关闭,避免资源泄露。API 文档特别提醒:创建的File对象会默认打开对应文件,使用结束后需要及时调用close关闭文件(见 fs 包类定义)。
流式读写综合示例
参考 fs 包 File 官方示例,可以综合看到四种OpenMode与SeekPosition、setLength的配合用法:
import std.fs.* import std.io.* main() { let filePath: Path = Path("./tempFile.txt") if (exists(filePath)) { remove(filePath) } /* 以只写模式创建新文件,写入三遍 "123456789\n" 并关闭 */ var file: File = File(filePath, Write) let bytes: Array<Byte> = "123456789\n".toArray() for (_ in 0..3) { file.write(bytes) } file.close() /* 以追加模式打开,写入 "abcdefghi\n" 并关闭 */ file = File(filePath, Append) file.write("abcdefghi\n".toArray()) file.close() /* 以只读模式打开,用 seek 定位后读取数据 */ file = File(filePath, Read) let bytesBuf: Array<Byte> = Array<Byte>(10, repeat: 0) file.seek(SeekPosition.Begin(10)) // 跳到文件头第 10 字节之后 file.read(bytesBuf) println("Data of the 10th byte after the 10th byte: ${String.fromUtf8(bytesBuf)}") file.seek(SeekPosition.End(-10)) // 跳到文件末尾前 10 字节 file.read(bytesBuf) println("Data of the last 10 bytes: ${String.fromUtf8(bytesBuf)}") file.close() /* 以读写模式打开,截断并重写文件 */ file = File(filePath, ReadWrite) file.setLength(0) // 截断文件大小为 0 file.write("The file was truncated to an empty file!".toArray()) file.seek(SeekPosition.Begin(0)) let allBytes: Array<Byte> = readToEnd(file) file.close() println("Data written newly: ${String.fromUtf8(allBytes)}") remove(filePath) }运行结果:
Data of the 10th byte after the 10th byte: 123456789 Data of the last 10 bytes: abcdefghi Data written newly: The file was truncated to an empty file!该示例展示了节点流在仓颉中的完整能力:用Write创建并写文件、用Append追加、用Read配合SeekPosition定位读取、用ReadWrite配合setLength截断重写,最后统一close并用remove清理。String.fromUtf8用于将字节数组还原为字符串显示。
小结与最佳实践
回到节点流的概念本质:节点流直接对接外部资源,是程序与外界数据交互的第一层通道。本文覆盖的两类节点流各有适用场景:
- 标准流:面向命令行交互,
Console封装了并发安全与缓存优化,stdIn提供readln等便捷读取接口,stdOut/stdErr提供丰富的write/writeln重载,写入后记得flush; - 文件流:面向持久化数据,
fs包统一屏蔽了跨平台差异。小数据量用File.readFrom/writeTo/appendTo静态函数一次搞定;大数据量或需要精细控制时,用File(path, OpenMode)构造流式实例,配合read/write/seek/setLength操作,并通过 try-with-resource 保证资源释放。
在实际项目中,通常还要将节点流与处理流(如BufferedInputStream、StringReader等)组合使用,以进一步减少磁盘 I/O 次数、提升字符串处理性能,相关用法可继续阅读 I/O 处理流。
- 文档
- 知识库
- 教程
- OpenHarmony
【免费下载链接】CangjieCorpus
本仓库为仓颉编程语言语料库,聚焦 仓颉编程语言 核心知识体系,整合官方开发指南、API 文档、典型代码示例及语法规范等多维度资源,经结构化处理与专业标注,构建为支持 RAG(检索增强生成)技术 的高质量知识基座,适配智能编码助手、学习辅助工具等应用开发需求。 欢迎开发者基于本语料库构建仓颉语言智能应用,推动鸿蒙开发生态智能化升级!
相关推荐
仓颉编程语言 I/O 节点流实战指南:标准流与文件流
仓颉编程语言 I/O 节点流实战指南:标准流与文件流 节点流(Node Stream)是仓颉编程语言中直接连接外部数据源的流类型,包括标准流(标准输入/输出/错
文档编程语言仓颉编程语言 I/O 节点流实战:标准流与文件流的完整用法
仓颉编程语言 I/O 节点流实战:标准流与文件流的完整用法 节点流(Node Stream)是仓颉 I/O 体系中直接对接外部数据源的流类型,涵盖标准流( Co
文档编程语言仓颉编程语言 I/O 流全解析:Stream 抽象、节点流与处理流实战指南
仓颉编程语言 I/O 流全解析:Stream 抽象、节点流与处理流实战指南 仓颉编程语言将所有与外部载体交互的操作抽象为基于数据流的 I/O 模型:输入输出统一
文档编程语言
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考