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
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
如何冲泡挂耳包咖啡?技巧是什么
2021/03/04 冲泡冲煮
用Zend Encode编写开发PHP程序
2010/02/21 PHP
php 用checkbox一次性删除多条记录的方法
2010/02/23 PHP
PHP隐形一句话后门,和ThinkPHP框架加密码程序(base64_decode)
2011/11/02 PHP
PHP中is_file不能替代file_exists的理由
2014/03/04 PHP
PHP访问Google Search API的方法
2015/03/05 PHP
PHP实现搜索相似图片
2015/09/22 PHP
php基于闭包实现函数的自调用(递归)实例分析
2016/11/11 PHP
phpstorm 正则匹配删除空行、注释行(替换注释行为空行)
2018/01/21 PHP
下载网站打开页面后间隔多少时间才显示下载链接地址的代码
2010/04/25 Javascript
JS小功能(button选择颜色)简单实例
2013/11/29 Javascript
在javascript中执行任意html代码的方法示例解读
2013/12/25 Javascript
Angular+Bootstrap+Spring Boot实现分页功能实例代码
2017/07/21 Javascript
JavaScript中的连续赋值问题实例分析
2019/07/12 Javascript
layui下拉框获取下拉值(select)的例子
2019/09/10 Javascript
ES6使用 Array.includes 处理多重条件用法实例分析
2020/03/02 Javascript
JS常见错误(Error)及处理方案详解
2020/07/02 Javascript
Python 变量类型及命名规则介绍
2013/06/08 Python
在Python3中初学者应会的一些基本的提升效率的小技巧
2015/03/31 Python
Python中实现三目运算的方法
2015/06/21 Python
Django 添加静态文件的两种实现方法(必看篇)
2017/07/14 Python
python GUI实例学习
2017/11/21 Python
Python OpenCV读取png图像转成jpg图像存储的方法
2018/10/28 Python
python实现猜拳游戏项目
2020/11/30 Python
英国蜡烛、蜡烛配件和家居香氛购买网站:Yankee Candle
2018/12/12 全球购物
美国室内和室外装饰花盆购物网站:ePlanters
2019/03/22 全球购物
365 Tickets英国:全球景点门票
2019/07/06 全球购物
致全体运动员广播稿
2014/02/01 职场文书
大学生先进事迹材料
2014/02/16 职场文书
人力资源部门的主要职能
2014/02/22 职场文书
《神奇的克隆》教学反思
2014/04/10 职场文书
亚运会口号
2014/06/20 职场文书
实践论读书笔记
2015/06/29 职场文书
导游词之南京中山陵
2019/11/27 职场文书
MySQL优化及索引解析
2022/03/17 MySQL
mysql数据库如何转移到oracle
2022/12/24 MySQL