Ruby处理CSV数据方法详解


Posted in Ruby onApril 18, 2022

CSV格式的数据默认是以逗号分隔各个字段的一条一条记录,默认用换行符分隔每一条记录。此外,有的CSV有标题行,有的没有。还有其他一些格式, 它们都有默认值,但都可以在读、写CSV数据时修改默认设置。后文大多数时候故意忽略这些设置,因为绝大多数读写操作都使用同样的参数**options进行格式设置。例如,在读取csv文件中的数据时想要忽略标题行,可以在参数中设置headers: true 

可设置的项及其默认值包括:

col_sep: ",",                #=> 字段分隔符
row_sep: :auto,              #=> 记录分隔符
quote_char: '"',             #=> 包围字段的符号
field_size_limit: nil,       #=> 限制字段的字符数量
converters: nil,             #=> 
unconverted_fields: nil,
headers: false,              #=> 读取时忽略标题行,具体参考官方手册
return_headers: false,
write_headers: nil,
header_converters: nil,
skip_blanks: false,          #=> 忽略空行
force_quotes: false,         #=> 设置为true时,所有字段都将使用被包围
skip_lines: nil,             #=> 指定一个正则(str也会转换为正则),
                             #=> 匹配的行将被当作注释行而忽略
liberal_parsing: false,
internal_encoding: nil,
external_encoding: nil,
encoding: nil,
nil_value: nil,             #=> 使用此处设置的值替换所有nil字段
empty_value: "",            #=> 使用此处设置的值替换所有空字符串字段
quote_empty: true,          #=> 设置为false时,空字符串字段将转换为空字段
write_converters: nil,
write_nil_value: nil,      #=> 将以此处的值替换nil字段写入文件
write_empty_value: "",
strip: false

CSV类方法处理CSV数据

以CSV格式写入文件

要向文件中写入CSV格式的数据:

require 'csv'

writer = CSV.open('/tmp/file.csv', 'w')
writer << ["junmajinlong", 29, 170, true]
writer << ["junma", 24, 176, false]
writer << ["jinlong", 25, 172, nil]
writer << ["majinlong", 23, 173, false]
writer.close

写入完成后,查看:

junmajinlong,29,170,true
junma,24,176,false
jinlong,25,172,
majinlong,23,173,false

注意其中的nil对应的写入内容为空。

可以直接在语句块中写入,这样的话可以自动关闭CSV.open()打开的IO流:

require 'csv'

CSV.open('/tmp/file.csv', 'w') do |writer|
  writer << ["junmajinlong", 29, 170, true]
  writer << ["junma", 24, 176, false]
  writer << ["jinlong", 25, 172, nil]
  writer << ["majinlong", 23, 173, false]
end

CSV.open()打开的是一个封装后的IO流对象,它除了可以使用CSV单独为其提供的一些方法(比如这里的<<)外,还可以使用很多IO流对象的方法,比如seek()、tell()、flush()、eof?()、fsync()等等。

这里使用的<<方法是单独为其提供的,它涉及两个执行过程:

  • 将数组中各元素全部转换成字符串类型并使用逗号连接
  • 按行写入到csv打开的文件中

转换为CSV格式的字符串

如果只是想执行第一个过程,即将数据转换成CSV格式的字符串而不写入,可使用类方法generate_line()

p CSV.generate_line ["junmajinlong", 29, 170, true]
p CSV.generate_line ["jun ma", 24, 176, false]
p CSV.generate_line ["jinlong", 25, 172, nil]
p CSV.generate_line ["jin, long", 23, 173, false]
=begin
"junmajinlong,29,170,true\n"
"jun ma,24,176,false\n"
"jinlong,25,172,\n"
"\"jin, long\",23,173,false\n"
=end

从CSV格式的文件中读数据

如果想要读取CSV文件,可使用类方法read()或别名readlines():

pp CSV.readlines('/tmp/file.csv')
=begin
[["junmajinlong", "29", "170", "true"],
 ["junma", "24", "176", "false"],
 ["jinlong", "25", "172", nil],
 ["majinlong", "23", "173", "false"]]
=end

注意:

  • 读取CSV文件内容时,每行保存为一个数组,每个字段是这个数组中的一个元素
  • 读取CSV文件内容时,除了不存在的字段转换为nil外,其它所有的数据都转换成了字符串类型。所以有时候可能需要去转换读取时的数据类型。关于类型转换,见后文

如果要按行读取CSV文件的内容,使用类方法foreach():

CSV.foreach('/tmp/file.csv') do |row|
  p row
end
=begin
["junmajinlong", "29", "170", "true"]
["junma", "24", "176", "false"]
["jinlong", "25", "172", nil]
["majinlong", "23", "173", "false"]
=end

从CSV格式的字符串中读数据

如果想要从字符串中读取CSV格式的数据,使用parse()和parse_line(),分别用于解析多行字符串和解析单行字符串(超出一行的自动被忽略)。

  • parse()不指定语句块时,返回包含解析每一行得到的数组,即一个数组的数组,它是一个csv table类型,有很多自己的方法
  • 指定语句块时,每一行对应的数组传递给语句块控制变量
str1=<<-eof
junmajinlong,29,170,true
jun ma,24,176,false
jinlong,25,172,
"jin, long",23,173,false
eof

# 不指定语句块时,parse返回数组
pp CSV.parse str1
=begin
[["junmajinlong", "29", "170", "true"],
 ["jun ma", "24", "176", "false"],
 ["jinlong", "25", "172", nil],
 ["jin, long", "23", "173", "false"]]
=end

# 指定语句块时,parse将每行对应的数组传递给语句块
CSV.parse(str1) {|row| p row}
=begin
["junmajinlong", "29", "170", "true"]
["jun ma", "24", "176", "false"]
["jinlong", "25", "172", nil]
["jin, long", "23", "173", "false"]
=end

str2="junmajinlong,29,170,true"
p CSV.parse_line str2
["junmajinlong", "29", "170", "true"]

CSV实例方法处理CSV数据

  • CSV.new()CSV.open()可以创建csv对象(即一行一行csv格式的数据)
  • CSV.generate()可将字符串转换成csv对象并将该对象传递给语句块
  • <<puts()add_row()可向CSV目标中(字符串格式的CSV或CSV IO流)写入行,它们是别名关系
  • gets()shift()readline()可从csv对象中读取一行数据
  • read()readlines()可以读取csv对象中的所有数据
  • each()可以从csv对象中迭代每一行
  • eof()eof?()可以判断是否读完所有数据
  • rewind()可以重置当前csv对象的偏移指针
  • line()可以获取最近一次读取的一行数据
  • lineno()可以获取当前已读取的行数
  • path()可以获取当前读取的csv文件名

CSV table

CSV.parse()、CSV.read()、CSV.table()等方法返回的都是数组的数组(二维数组),它们是CSV Table。

CSV table按照表的方式来处理csv数据,比如关注于行、关注于字段的一些操作可以采用csv table相关的方法来处理。

# Headers are part of data
data = CSV.parse(<<~ROWS, headers: true)
  Name,Department,Salary
  Bob,Engineering,1000
  Jane,Sales,2000
  John,Management,5000
ROWS

data.class      #=> CSV::Table
data.first      #=> #<CSV::Row "Name":"Bob" "Department":"Engineering" "Salary":"1000">
data.first.to_h #=> {"Name"=>"Bob", "Department"=>"Engineering", "Salary"=>"1000"}

# Headers provided by developer
data = CSV.parse('Bob,Engineering,1000', headers: %i[name department salary])
data.first      #=> #<CSV::Row name:"Bob" department:"Engineering" salary:"1000">

CSV字段类型转换

读取CSV数据时,所有的数据都会转换为字符串格式。

# Without any converters:
CSV.parse('Bob,2018-03-01,100')
#=> [["Bob", "2018-03-01", "100"]]

可以在迭代每一行的语句块中对字段做必要的类型转换。

但如果类型转换方式比较简单,可以在读取数据时指定converters属性进行转换。该属性的值要么是CSV的内置类型符号,要么是符号数组,要么是一个lambda表达式。有如下内置类型:

Integer
Float
Numeric (Float + Integer)
Date
DateTime
All

当指定了类型转换后,每个字段将针对converters的值尝试做转换,转换失败则保留字段的值不变,所以如果通过lambda自定义类型转换时也一定要保证这一点。

CSV.parse("1,2,3,4,5", converters: :numeric)
#=> [[1, 2, 3, 4, 5]]

# With built-in converters:
ct = CSV.parse('Bob,2018-03-01,100', converters: %i[numeric date])
#=> [["Bob", #<Date: 2018-03-01>, 100]]
ct.first[1] + 1  # 日期对象,加1天
#=> #<Date: 2018-03-02 ((2458180j,0s,0n),+0s,2299161j)>

# With custom converters:
CSV.parse('Bob,2018-03-01,100', converters: [->(v) { Time.parse(v) rescue v }])
#=> [["Bob", 2018-03-01 00:00:00 +0200, "100"]]
Ruby 相关文章推荐
Ruby处理CSV数据方法详解
Apr 18 Ruby
Ruby处理YAML和json数据
Apr 18 Ruby
Ruby序列化和持久化存储 Marshal和Pstore介绍
Apr 18 Ruby
Ruby使用Mysql2连接操作MySQL
Apr 19 Ruby
Ruby GDBM操作简介及数据存储原理
Apr 19 Ruby
安装Ruby和 Rails的详细步骤
Apr 19 Ruby
Python如何将list中的string转换为int
Jul 15 Ruby
Ruby处理YAML和json数据
Apr 18 #Ruby
Ruby序列化和持久化存储 Marshal和Pstore介绍
Apr 18 #Ruby
Ruby使用Mysql2连接操作MySQL
Apr 19 #Ruby
Ruby GDBM操作简介及数据存储原理
Apr 19 #Ruby
安装Ruby和 Rails的详细步骤
Python如何将list中的string转换为int
Jul 15 #Ruby
You might like
php代码收集表单内容并写入文件的代码
2012/01/29 PHP
PHP使用GIFEncoder类生成gif动态滚动字幕
2014/07/01 PHP
codeigniter发送邮件并打印调试信息的方法
2015/03/21 PHP
php 使用redis锁限制并发访问类示例
2016/11/02 PHP
详解PHP函数 strip_tags 处理字符串缺陷bug
2017/06/11 PHP
基于JQuery的数字改变的动画效果--可用来做计数器
2010/08/11 Javascript
使用Math.floor与Math.random取随机整数的方法详解
2013/05/07 Javascript
bootstrap3 兼容IE8浏览器!
2016/05/02 Javascript
简单的分页代码js实现
2016/05/17 Javascript
基于Bootstrap的Metronic框架实现条码和二维码的生成及打印处理操作
2016/08/29 Javascript
JS碰撞运动实现方法详解
2016/12/15 Javascript
基于javascript的Form表单验证
2016/12/29 Javascript
jQuery实现的动态文字变化输出效果示例【附演示与demo源码下载】
2017/03/24 jQuery
基于JavaScript中字符串的match与replace方法(详解)
2017/12/04 Javascript
MUI 实现侧滑菜单及其主体部分上下滑动的方法
2018/01/25 Javascript
vue实现购物车功能(商品分类)
2020/04/20 Javascript
在vue中实现给每个页面顶部设置title
2020/07/29 Javascript
[02:55]含熏伴清风,风行者至宝、屠夫身心及典藏宝瓶二展示
2020/09/08 DOTA
Python中文编码那些事
2014/06/25 Python
Windows系统配置python脚本开机启动的3种方法分享
2015/03/10 Python
python中的格式化输出用法总结
2016/07/28 Python
Pycharm编辑器技巧之自动导入模块详解
2017/07/18 Python
关于django 数据库迁移(migrate)应该知道的一些事
2018/05/27 Python
使用Python抓取豆瓣影评数据的方法
2018/10/17 Python
基于python实现百度翻译功能
2019/05/09 Python
HTML5适合的情人节礼物有纪念日期功能
2021/01/25 HTML / CSS
Lookfantastic西班牙官网:英国知名美妆购物网站
2018/06/13 全球购物
Quiksilver美国官网:始于1969年的优质冲浪服和滑雪板外套
2020/04/20 全球购物
一套SQL笔试题
2016/08/14 面试题
几道数据库的概念性面试题
2014/05/30 面试题
求职者简历中的自我评价
2013/10/20 职场文书
家长会演讲稿范文
2014/01/10 职场文书
幼儿园母亲节活动方案
2014/03/10 职场文书
大学感恩节活动策划方案
2014/10/11 职场文书
文艺部部长竞选稿
2015/11/21 职场文书
(开源)微信小程序+mqtt,esp8266温湿度读取
2021/04/02 Javascript