尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Apache Arrow Ruby(Red Arrow)开发命名约定:Reader/Writer 与 Loader/Saver 双层 API 设计解析

Apache Arrow Ruby(Red Arrow)开发命名约定:Reader/Writer 与 Loader/Saver 双层 API 设计解析 数据工程大数据序列化数据分析【免费下载链接】arrowApache Arrow is a multi-language toolbox for accelerated data interchange and in-memory processing项目地址https://gitcode.com/gh_mirrors/arrow13/arrow点击查看免费下载本文以 ruby/red-arrow/doc/text/development.md 中定义的开发命名约定为核心系统讲解 Red ArrowApache Arrow 的 Ruby 绑定中Reader/Writer与Loader/Saver两类 API 的职责划分、设计动机与源码实现。读完本文你将理解为什么有的类需要你手动打开 IO 流、有的类只需传一个路径并能依据实际场景在两类 API 之间做出正确选择同时掌握Arrow::Table.load/Table#save底层完整的格式分发与选项解析链路。一、命名约定总览两种互补的 API 风格Red Arrow 的 IO 相关类遵循一套清晰的命名约定其核心规则只有两条但贯穿了整个读写体系命名后缀构造前提定位示例类Reader需要一个已打开的 IO 流input stream底层、可组合的读取组件RecordBatchFileReader、RecordBatchStreamReaderWriter需要一个已打开的 IO 流output stream底层、可组合的写入组件RecordBatchFileWriter、RecordBatchStreamWriterLoader只需要一个路径path面向用户的便捷门面内部打开路径后用 Reader 读取Arrow::TableLoader、Arrow::CSVLoaderSaver只需要一个路径path面向用户的便捷门面内部打开路径后用 Writer 写入Arrow::TableSaver原文档对这条约定的定义非常精炼Reader 和 Writer 需要一个已打开的 IO 流Reader and Writer require an opened IO stream。Loader 和 Saver 只需要一个路径是便捷类Loader and Saver require a path. They are convenient classes。Loader 负责打开路径并借助 Reader 读取数据Loader opens the path and reads data by Reader。Saver 负责打开路径并借助 Writer 写入数据Writer opens the path and writes data by Writer此处原文笔误为 Writer结合 table-saver.rb 的实现实际指 Saver 委托 Writer 完成写入。也就是说Reader/Writer是流驱动的底层构件而Loader/Saver是路径驱动的高层门面——两者不是平行的两套实现而是分层协作上层负责路径解析与 IO 流打开下层负责真正的序列化/反序列化。二、Reader/Writer面向已打开 IO 流的底层构件2.1 类族与文件位置Red Arrow 中的核心 Reader/Writer 类包括Arrow::RecordBatchFileReader——读取 Arrow文件格式random access带 footer/元数据可随机读取任意 record batch见 record-batch-file-reader.rbArrow::RecordBatchStreamReader——读取 Arrow流格式streaming只能顺序读取见 record-batch-stream-reader.rbArrow::RecordBatchFileWriter/Arrow::RecordBatchStreamWriter——对应两种格式的写入器在 Ruby 侧没有手工包装文件而是由 GObject Introspection 在运行时从 c_glib/arrow-glib/writer.h 自动生成绑定见下文 2.3。从源码结构看record-batch-file-reader.rb 只对自动生成的类补充了Enumerable混入与each遍历逻辑class RecordBatchFileReader include Enumerable def each return to_enum(__method__) {n_record_batches} unless block_given? n_record_batches.times do |i| yield(get_record_batch(i)) end end end这体现了 Red Arrow 的整体架构绝大多数类是 C 层Apache Arrow C → Apache Arrow GLib通过 GObject Introspection 自动生成的Ruby 源文件只负责补充 Ruby 惯用的便利方法。加载全部绑定与扩展库的入口在 loader.rb它继承自GObjectIntrospection::Loader在post_load中通过require_libraries逐个加载所有手工扩展、再require_extension_library加载arrow.so。2.2 用法先开流再构造 Reader由于 Reader 需要已打开的 IO 流典型用法是先创建Arrow::MemoryMappedInputStream等流对象再传入 Reader 构造器。参考 example/read-file.rbrequire arrow Arrow::MemoryMappedInputStream.open(/tmp/file.arrow) do |input| reader Arrow::RecordBatchFileReader.new(input) fields reader.schema.fields reader.each_with_index do |record_batch, i| puts( * 40) puts(record-batch[#{i}]:) fields.each do |field| field_name field.name values record_batch.collect do |record| record[field_name] end puts( #{field_name}: #{values.inspect}) end end end读取流格式的对应写法见 example/read-stream.rb仅把RecordBatchFileReader换成RecordBatchStreamReader。二者的差异在于文件格式可随机访问get_record_batch(i)按索引取流格式只能顺序each消费。2.3 从 Ruby 普通 IO 到 Arrow 流的桥接一个值得注意的细节是Reader/Writer 需要的是Arrow 的InputStream/OutputStream抽象而不是 Ruby 的IO对象。Red Arrow 提供了多级桥接文件路径 →Arrow::MemoryMappedInputStream读、Arrow::FileOutputStream写第二个参数false表示不追加见 example/write-file.rb内存缓冲 →Arrow::BufferInputStream/Arrow::BufferOutputStreamRuby 的IO/StringIO→Gio::RubyInputStream→Arrow::GIOInputStream读Gio::RubyOutputStream→Arrow::GIOOutputStream写。管道pipe场景是 Reader 需要已打开 IO 流 的最典型例证。参考 example/write-pipe.rb 与 example/read-pipe.rb父进程把Table写入管道一端子进程从管道另一端读取。因为管道没有路径只有流所以这里必须走 Reader/Writer 而非 Loader/Saver# 写入端write-pipe.rb table Arrow::Table.new(a: [1, 2, 3], b: [a, b, c]) IO.pipe do |input, output| pid spawn(RbConfig.ruby, File.join(__dir__, read-pipe.rb), in: input) input.close output.singleton_class.__send__(:undef_method, :seek) Gio::RubyOutputStream.open(output) do |gio_output| Arrow::GIOOutputStream.open(gio_output) do |arrow_output| Arrow::RecordBatchStreamWriter.open(arrow_output, table.schema) do |writer| writer.write_table(table) end end end output.close Process.waitpid(pid) end# 读取端read-pipe.rb Gio::RubyInputStream.open($stdin) do |gio_input| Arrow::GIOInputStream.open(gio_input) do |arrow_input| reader Arrow::RecordBatchStreamReader.new(arrow_input) p reader.read_all end end这里还解释了为什么命名约定要求 Reader/Writer 只认流流抽象让同一个读取/写入组件可以无差别地服务文件、内存、管道、网络 socket 乃至任意 Ruby IO路径只是流的一种来源。三、Loader/Saver面向路径的便捷门面3.1 一句话的职责定义与入口Loader/Saver 的定位是便捷类调用方无需关心如何打开流、无需了解底层 Reader/Writer 的格式差异只需给出路径。其职责正是原文档所写——Loader 打开路径后交给 Reader 读Saver 打开路径后交给 Writer 写。最常用的两个门面 API 定义在 table.rbclass self def load(path, options{}) # 第 29 行 TableLoader.load(path, options) end end # 实例方法第 445 行附近 def save(output, options{}) # 委托给 TableSaver end也就是说Arrow::Table.load(/path/to/data.arrow)与table.save(/path/to/out.arrow)这两条 README 里最常用的用法见 ruby/red-arrow/README.md底层分别由TableLoader与TableSaver完成打开路径 → 构造流 → 委托 Reader/Writer的完整流程。3.2 TableLoader来源识别与格式分发table-loader.rb 的加载流程分三层第一层识别输入来源path / URI / Buffer / directory。load方法根据输入类型确定候选加载方法def load if input.is_a?(URI) custom_load_method_candidates [] if input.scheme custom_load_method_candidates load_from_uri_#{input.scheme} end custom_load_method_candidates load_from_uri elsif input.is_a?(String) and ::File.directory?(input) custom_load_method_candidates [load_from_directory] else custom_load_method_candidates [load_from_file] end # ...按候选方法逐一 dispatch找不到则抛出列出可用来源的 ArgumentError end基类实现了load_from_file与load_from_uri_http/https统一走load_by_readerload_from_directory则是预留的扩展钩子——从源码结构看基类并未实现该方法若传入目录路径会抛出ArgumentError并列出所有可用的load_from_*来源。第二层按格式分发load_as_*。load_by_reader读取options[:format]动态调用对应的load_as_#{format}私有方法基类支持以下格式format 选项对应方法底层 Reader备注:arrow默认load_as_arrow先尝试RecordBatchFileReader失败则回退RecordBatchStreamReader扩展名无法识别时的兜底:arrow_fileload_as_arrow_fileRecordBatchFileReader自 1.0.0 起:arrows/:arrow_streamingload_as_arrowsRecordBatchStreamReader自 7.0.0 起:orcload_as_orcORCFileReader若可用支持:field_indexes选项:csvload_as_csvCSVLoader见第四节:tsvload_as_tsvCSVLoaderdelimiter: \t:featherload_as_featherFeatherFileReader:jsonload_as_jsonJSONReader支持把选项映射到JSONReadOptions其中load_as_batch/load_as_stream是已废弃的旧格式名源码中以deprecated Use format: :arrow_file ...标注分发表在构建错误信息时会把这些废弃格式从可用列表中剔除。第三层打开输入流并委托 Reader。open_input_stream按来源类型打开流并统一交给load_rawdef open_input_stream case input when Buffer yield(BufferInputStream.new(input)) when URI input.open do |ruby_input| # :stream 格式用 Gio::RubyInputStream GIOInputStream # 其他格式先整段读入 Buffer 再用 BufferInputStream规避 GVL 问题 end else yield(MemoryMappedInputStream.new(input)) end end def load_raw(input, reader) schema reader.schema record_batches [] reader.each do |record_batch| record_batches record_batch end table Table.new(schema, record_batches) table.refer_input(input) # 让 Table 引用输入流保证底层数据存活 table endload_raw正是原文档所述 Loader opens the path and reads data by Reader 的直接代码体现先打开路径对应的流再构造 Reader 遍历 record batch最后组装成Arrow::Table。table.refer_input(input)是一个容易被忽略但很关键的内存语义Table 的数据可能直接引用输入流的底层内存尤其 MemoryMappedInputStream必须让 Table 持有流的引用防止流被 GC 回收导致数据失效。3.3 扩展名自动识别与压缩选项TableLoader/TableSaver另一个便捷之处在于自动从路径扩展名推断格式与压缩方式。fill_options利用Arrow::PathExtension解析输入/输出路径def fill_options if options[:format] and options.key?(:compression) return end case input when Buffer info {} when URI extension PathExtension.new(input.path) info extension.extract else extension PathExtension.new(input) info extension.extract end format info[:format] options options.dup if format options[:format] || format.to_sym else options[:format] || :arrow # 无法识别时默认按 Arrow 文件格式 end unless options.key?(:compression) options[:compression] info[:compression] end end因此Table.load(data.csv.gz)无需显式传参——扩展名会解析出format: :csv、compression: :gzipLoader 自动用CompressedInputStream解压后再交给CSVLoader。显式传入的:format/:compression选项优先级更高会跳过自动推断。四、Saver打开路径后委托 Writertable-saver.rb 与 Loader 完全对称TableSaver.save(table, output, options{})先识别输出目标URI 或文件路径再按format分发到save_as_*方法。save_raw是核心委托点def save_raw(writer_class) open_output_stream do |output| writer_class.open(output, table.schema) do |writer| writer.write_table(table) end end end def save_as_arrow_file save_raw(RecordBatchFileWriter) # Arrow 文件格式 end def save_as_arrows save_raw(RecordBatchStreamWriter) # Arrow 流格式 end这正是原文档 Saver opens the path and writes data by Writer 的代码化表达open_output_stream负责打开文件 →FileOutputStream.open(output, false)Buffer →BufferOutputStream并按:compression选项决定是否包一层CompressedOutputStream用Codec.new(compression)创建编解码器最后把已打开的流和 schema 交给对应 Writer。写入选项同样由fill_options从扩展名自动推断如.arrow→:arrow_file。Saver 支持的格式与 Loader 对应save_as_arrow_file默认等价于旧save_as_batch、save_as_arrows等价于旧save_as_stream、save_as_csvcsv_save先写 schema 字段名作表头再逐行写raw_records、save_as_tsvcol_sep: \t、save_as_feather通过FeatherWriteProperties把选项映射为写属性后调用table.write_as_feather。写入完成后save返回table本身便于链式调用。五、CSVLoaderLoader 约定的第二个实例arrow/csv-loader.rb 展示了 Loader 约定的另一种形态——输入既可以是路径Pathname或以.csv结尾的字符串也可以是内存中的 CSV 字符串数据def load case path_or_data when Pathname load_from_path(path_or_data.to_path) when /\A.\.csv\z/i load_from_path(path_or_data) else load_data(path_or_data) # 直接把字符串当作 CSV 数据解析 end endCSVLoader内部采用两条腿走路的策略优先尝试用Arrow::CSVReaderGLib 层高性能读取器配合CSVReadOptions解析若因选项不支持等原因失败rescue Arrow::Error::Invalid, Gio::Error则回退到 Ruby 标准库CSV逐行解析并构造Arrow::Table。它支持:headers布尔值、列名数组或字符串三种形态、:column_types、:schema、:encoding用Gio::CharsetConverter做编码转换、:delimiter内部转成:col_sep、:compression用CodecCompressedInputStream解压等选项。更值得一提的是它的列类型自动检测detect_robust_converters扫描全部行按列归纳出:boolean/:integer/:float/:time/:date_time/:date/:string中的一种类型若某列出现不一致则降级为:string并为每列生成对应的selective_converter转换器内置BOOLEAN_CONVERTER识别true/false、ISO8601_CONVERTER识别 ISO 8601 时间字符串。这保证了 CSV 加载后能得到类型正确的 Arrow 列。六、如何选择两条 API 路径的使用准则结合以上源码分析可以把原文档的命名约定转化为可操作的选择准则你的输入/输出形态推荐 API理由有文件路径希望一行搞定、自动识别格式Arrow::Table.load(path)/table.save(path)Loader/Saver 会完成开流、格式推断、压缩推断代码量最小需要从 HTTP(S) URI 或 Buffer 读取Arrow::Table.load(uri_or_buffer)TableLoader原生支持URI与Buffer来源需要控制流的生命周期、复用已打开的流直接构造RecordBatchFileReader/RecordBatchStreamReader等Reader/Writer 只依赖流抽象不关心流从哪来管道、socket、任意 Ruby IO 上的实时读写Gio::RubyInputStreamGIOInputStream Reader或对称的 Writer 链没有路径只能用流见 example/read-pipe.rb需要自定义加载来源如目录、私有存储继承TableLoader实现load_from_*/load_as_*方法分发机制按命名约定自动发现可用方法一条实用经验默认优先使用 Loader/Saver——它们不仅省去开流样板代码还能从扩展名自动推导格式与压缩方式并且对 URI、Buffer、文件三种来源统一了入口只有当你需要对流的打开方式、生命周期或底层 Reader 行为做精细控制时才下沉到 Reader/Writer 层。七、小结Red Arrow 通过Reader/Writer与Loader/Saver的命名约定构建了一个清晰的读写分层Reader/Writer是流驱动的底层构件可组合、可复用服务于文件、内存、管道等一切流形态的数据源Loader/Saver是路径驱动的高层门面内部完成打开路径 → 构造流 → 委托 Reader/Writer的完整链路并额外提供格式与压缩的自动推断能力见 table-loader.rb 与 table-saver.rb。理解这套约定不仅能让你在阅读 Red Arrow 源码从 example/ 下的读写示例到 lib 目录各组件时快速定位类的职责也能在开发中根据有路径还是有流瞬间选出正确的 API并在此基础上通过实现load_from_*/save_as_*方法扩展自定义格式这正是该命名约定作为开发指南的价值所在。赞分享数据工程大数据序列化数据分析【免费下载链接】arrowApache Arrow is a multi-language toolbox for accelerated data interchange and in-memory processing项目地址https://gitcode.com/gh_mirrors/arrow13/arrow点击查看免费下载相关推荐Red Arrow 开发指南Reader/Writer 与 Loader/Saver 命名约定及其 IO 流设计解析Red Arrow 开发指南Reader/Writer 与 Loader/Saver 命名约定及其 IO 流设计解析 Red ArrowApache Arr数据工程数据分析大数据五步让旧 Mac 再战五年OpenCore Legacy Patcher 免费安装新版 macOS 完整指南五步让旧 Mac 再战五年OpenCore Legacy Patcher 免费安装新版 macOS 完整指南 你的旧 Mac 跑起来还流畅系统却告诉你无法操作系统固件驱动开发Red Arrow基于 GObject Introspection 的 Apache Arrow Ruby 绑定入门与实战Red Arrow基于 GObject Introspection 的 Apache Arrow Ruby 绑定入门与实战 Red Arrow 是 Apach数据工程数据分析大数据创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表