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使用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
关于ob_get_contents(),ob_end_clean(),ob_start(),的具体用法详解
2013/06/24 PHP
PHP has encountered a Stack overflow问题解决方法
2014/11/03 PHP
jquery实现的可隐藏重现的靠边悬浮层实例代码
2013/05/27 Javascript
可自定义速度的js图片无缝滚动示例分享
2014/01/20 Javascript
jquery实现右键菜单插件
2015/03/29 Javascript
jQuery Validate初步体验(二)
2015/12/12 Javascript
javascript瀑布流布局实现方法详解
2016/02/17 Javascript
js实现数组冒泡排序、快速排序原理
2016/03/08 Javascript
JS不用正则验证输入的字符串是否为空(包含空格)的实现代码
2016/06/14 Javascript
JS常用字符串方法(推荐)
2021/01/15 Javascript
浅析Jquery操作select
2016/12/13 Javascript
canvas雪花效果核心代码分享
2017/02/19 Javascript
nodejs密码加密中生成随机数的实例代码
2017/07/17 NodeJs
JS获取子、父、兄节点方法小结
2017/08/14 Javascript
深入理解Node module模块
2018/03/26 Javascript
jQuery中内容过滤器简单用法示例
2018/03/31 jQuery
vue-router3.0版本中 router.push 不能刷新页面的问题
2018/05/10 Javascript
微信小程序支付功能 php后台对接完整代码分享
2018/06/12 Javascript
vue-router 源码实现前端路由的两种方式
2018/07/02 Javascript
vue click.stop阻止点击事件继续传播的方法
2018/09/04 Javascript
Python采集腾讯新闻实例
2014/07/10 Python
PyQt5利用QPainter绘制各种图形的实例
2017/10/19 Python
对变量赋值的理解--Pyton中让两个值互换的实现方法
2017/11/29 Python
详解python字节码
2018/02/07 Python
浅谈flask源码之请求过程
2018/07/26 Python
Numpy之文件存取的示例代码
2018/08/03 Python
Python数据可视化:顶级绘图库plotly详解
2019/12/07 Python
python列表删除和多重循环退出原理详解
2020/03/26 Python
7 For All Mankind官网:美国加州洛杉矶的高级牛仔服装品牌
2018/12/20 全球购物
材料成型专业个人求职信范文
2013/09/25 职场文书
七一表彰活动方案
2014/01/18 职场文书
英语自我评价范文
2014/01/24 职场文书
酒店管理毕业生自我鉴定
2014/03/02 职场文书
党员先锋岗事迹材料
2014/05/08 职场文书
python读取mnist数据集方法案例详解
2021/09/04 Python
Windows Server 2019 域控制器安装图文教程
2022/04/28 Servers