如何有效地读取文本文件每一行的第一个字符?
How to efficiently read the first character from each line of a text file?
我只想读取文本文件每一行的第一个字符,忽略其余字符。
这是一个示例文件:
x <- c(
"Afklgjsdf;bosfu09[45y94hn9igf",
"Basfgsdbsfgn",
"Cajvw58723895yubjsdw409t809t80",
"Djakfl09w50968509",
"E3434t"
)
writeLines(x, "test.txt")
我可以通过阅读所有带有 readLines
and using substring
的内容来获取第一个字符来解决问题:
lines <- readLines("test.txt")
substring(lines, 1, 1)
## [1] "A" "B" "C" "D" "E"
虽然这看起来效率不高。有没有办法说服 R 只读取第一个字符,而不必丢弃它们?
我怀疑应该有一些使用scan
, but I can't find it. An alternative might be low level file manipulation (maybe with seek
的咒语)。
由于性能只与较大的文件相关,这里有一个更大的测试文件用于基准测试:
set.seed(2015)
nch <- sample(1:100, 1e4, replace = TRUE)
x2 <- vapply(
nch,
function(nch)
{
paste0(
sample(letters, nch, replace = TRUE),
collapse = ""
)
},
character(1)
)
writeLines(x2, "bigtest.txt")
更新:看来你无法避免扫描整个文件。最好的速度增益似乎是使用更快的替代 readLines
( and ), or to treat the file as binary ().
01/04/2015 编辑以将更好的解决方案置于顶部。
更新 2 在打开的连接上将 scan()
方法更改为 运行 而不是在每次迭代时打开和关闭允许逐行读取行并消除循环。时机改善了很多。
## scan() on open connection
conn <- file("bigtest.txt", "rt")
substr(scan(conn, what = "", sep = "\n", quiet = TRUE), 1, 1)
close(conn)
我还发现了stringi包中的stri_read_lines()
函数,它的帮助文件说目前是实验性的,但速度非常快。
## stringi::stri_read_lines()
library(stringi)
stri_sub(stri_read_lines("bigtest.txt"), 1, 1)
下面是这两种方法的时间安排。
## timings
library(microbenchmark)
microbenchmark(
scan = {
conn <- file("bigtest.txt", "rt")
substr(scan(conn, what = "", sep = "\n", quiet = TRUE), 1, 1)
close(conn)
},
stringi = {
stri_sub(stri_read_lines("bigtest.txt"), 1, 1)
}
)
# Unit: milliseconds
# expr min lq mean median uq max neval
# scan 50.00170 50.10403 50.55055 50.18245 50.56112 54.64646 100
# stringi 13.67069 13.74270 14.20861 13.77733 13.86348 18.31421 100
原始[较慢]答案:
您可以尝试 read.fwf()
(固定宽度文件),将宽度设置为单个 1 以捕获每行的第一个字符。
read.fwf("test.txt", 1, stringsAsFactors = FALSE)[[1L]]
# [1] "A" "B" "C" "D" "E"
当然没有经过全面测试,但适用于测试文件,并且是无需读取整个文件即可获取子字符串的好函数。
Update 1 : read.fwf()
效率不高,内部调用了scan()
和read.table()
。我们可以跳过中间人,直接尝试scan()
。
lines <- count.fields("test.txt") ## length is num of lines in file
skip <- seq_along(lines) - 1 ## set up the 'skip' arg for scan()
read <- function(n) {
ch <- scan("test.txt", what = "", nlines = 1L, skip = n, quiet=TRUE)
substr(ch, 1, 1)
}
vapply(skip, read, character(1L))
# [1] "A" "B" "C" "D" "E"
version$platform
# [1] "x86_64-pc-linux-gnu"
如果您 allow/have 可以使用 Unix 命令行工具,您可以使用
scan(pipe("cut -c 1 test.txt"), what="", quiet=TRUE)
显然便携性较差,但可能非常快。
将@RichieCotton 的基准测试代码与 OP 建议的 "bigtest.txt" 文件一起使用:
expr min lq mean median uq
RC readLines 14.797830 17.083849 19.261917 18.103020 20.007341
RS read.fwf 125.113935 133.259220 148.122596 138.024203 150.528754
BB scan pipe cut 6.277267 7.027964 7.686314 7.337207 8.004137
RC readChar 1163.126377 1219.982117 1324.576432 1278.417578 1368.321464
RS scan 13.927765 14.752597 16.634288 15.274470 16.992124
每个答案的基准,在 Windows.
下
library(microbenchmark)
microbenchmark(
"RC readLines" = {
lines <- readLines("test.txt")
substring(lines, 1, 1)
},
"RS read.fwf" = read.fwf("test.txt", 1, stringsAsFactors = FALSE)$V1,
"BB scan pipe cut" = scan(pipe("cut -c 1 test.txt"),what=character()),
"RC readChar" = {
con <- file("test.txt", "r")
x <- readChar(con, 1)
while(length(ch <- readChar(con, 1)) > 0)
{
if(ch == "\n")
{
x <- c(x, readChar(con, 1))
}
}
close(con)
}
)
## Unit: microseconds
## expr min lq mean median uq
## RC readLines 561.598 712.876 830.6969 753.929 884.8865
## RS read.fwf 5079.010 6429.225 6772.2883 6837.697 7153.3905
## BB scan pipe cut 308195.548 309941.510 313476.6015 310304.412 310772.0005
## RC readChar 1238.963 1549.320 1929.4165 1612.952 1740.8300
## max neval
## 2156.896 100
## 8421.090 100
## 510185.114 100
## 26437.370 100
在更大的数据集上:
## Unit: milliseconds
## expr min lq mean median uq max neval
## RC readLines 52.212563 84.496008 96.48517 103.319789 104.124623 158.086020 20
## RS read.fwf 391.371514 660.029853 703.51134 766.867222 777.795180 799.670185 20
## BB scan pipe cut 283.442150 482.062337 516.70913 562.416766 564.680194 567.089973 20
## RC readChar 2819.343753 4338.041708 4500.98579 4743.174825 4921.148501 5089.594928 20
## RS scan 2.088749 3.643816 4.16159 4.651449 4.731706 5.375819 20
data.table::fread()
似乎击败了迄今为止提出的所有解决方案,并且具有 运行 在 Windows 和 *NIX 机器上速度相当快的优点:
library(data.table)
substring(fread("bigtest.txt", sep="\n", header=FALSE)[[1]], 1, 1)
这是 microbenchmark 在 Linux 盒子上的计时(实际上是双启动笔记本电脑,以 Ubuntu 启动):
Unit: milliseconds
expr min lq mean median uq max neval
RC readLines 15.830318 16.617075 18.294723 17.116666 18.959381 27.54451 100
JOB fread 5.532777 6.013432 7.225067 6.292191 7.727054 12.79815 100
RS read.fwf 111.099578 113.803053 118.844635 116.501270 123.987873 141.14975 100
BB scan pipe cut 6.583634 8.290366 9.925221 10.115399 11.013237 15.63060 100
RC readChar 1347.017408 1407.878731 1453.580001 1450.693865 1491.764668 1583.92091 100
这里是同一台笔记本电脑作为 Windows 机器启动的时间(使用 Rtools 提供的命令行工具 cut
):
Unit: milliseconds
expr min lq mean median uq max neval cld
RC readLines 26.653266 27.493167 33.13860 28.057552 33.208309 61.72567 100 b
JOB fread 4.964205 5.343063 6.71591 5.538246 6.027024 13.54647 100 a
RS read.fwf 213.951792 217.749833 229.31050 220.793649 237.400166 287.03953 100 c
BB scan pipe cut 180.963117 263.469528 278.04720 276.138088 280.227259 387.87889 100 d
RC readChar 1505.263964 1572.132785 1646.88564 1622.410703 1688.809031 2149.10773 100 e
找出文件大小,将其作为单个二进制 blob 读入,找到感兴趣字符的偏移量(不要计算文件末尾的最后一个 '\n'!),以及强制转换为最终形式
f0 <- function() {
sz <- file.info("bigtest.txt")$size
what <- charToRaw("\n")
x = readBin("bigtest.txt", raw(), sz)
idx = which(x == what)
rawToChar(x[c(1L, idx[-length(idx)] + 1L)], multiple=TRUE)
}
data.table 解决方案(我认为是迄今为止最快的 -- 需要将第一行作为数据的一部分!)
library(data.table)
f1 <- function()
substring(fread("bigtest.txt", header=FALSE)[[1]], 1, 1)
相比之下
> identical(f0(), f1())
[1] TRUE
> library(microbenchmark)
> microbenchmark(f0(), f1())
Unit: milliseconds
expr min lq mean median uq max neval
f0() 5.144873 5.515219 5.571327 5.547899 5.623171 5.897335 100
f1() 9.153364 9.470571 9.994560 10.162012 10.350990 11.047261 100
仍然很浪费,因为整个文件在大部分被丢弃之前被读入内存。
我发现以微秒或毫秒的顺序对基准操作提供的信息量不大。但我明白在某些情况下这是无法避免的。在这些情况下,我仍然发现有必要测试不同(增加大小)的数据以粗略衡量该方法的扩展性..
这是我在 @MartinMorgan 的测试中使用 f0()
和 f1()
在 1e4、1e5 和 1e6 行上进行的 运行 结果:
1e4
# Unit: milliseconds
# expr min lq mean median uq max neval
# f0() 4.226333 7.738857 15.47984 8.398608 8.972871 89.87805 100
# f1() 8.854873 9.204724 10.48078 9.471424 10.143601 84.33003 100
1e5
# Unit: milliseconds
# expr min lq mean median uq max neval
# f0() 71.66205 176.57649 174.9545 184.0191 187.7107 307.0470 100
# f1() 95.60237 98.82307 104.3605 100.8267 107.9830 205.8728 100
1e6
# Unit: seconds
# expr min lq mean median uq max neval
# f0() 1.443471 1.537343 1.561025 1.553624 1.558947 1.729900 10
# f1() 1.089555 1.092633 1.101437 1.095997 1.102649 1.140505 10
identical(f0(), f1())
在所有测试中返回 TRUE。
更新:
1e7
我还在 1e7 行上 运行。
f1()
(data.table) 运行 在 9.7 秒内,而 f0()
运行 第一次在 7.8 秒内,9.4 和 6.6 秒第二次.
但是,f1()
在读取整个 0.479GB 文件时内存没有明显变化,而 f0()
导致 2.4GB 的峰值。
另一个观察:
set.seed(2015)
x2 <- vapply(
1:1e5,
function(i)
{
paste0(
sample(letters, 100L, replace = TRUE),
collapse = "_"
)
},
character(1)
)
# 10 million rows, with 200 characters each
writeLines(unlist(lapply(1:100, function(x) x2)), "bigtest.txt")
## readBin() results in a 2 billion row vector
system.time(f0()) ## explodes on memory
因为 readBin()
步骤产生了一个 20 亿长度的向量(读取文件需要 ~1.9GB),而 which(x == what)
步骤需要 ~4.5+GB(总共 = ~6.5GB)那时我停止了这个过程。
fread()
在这种情况下大约需要 23 秒。
HTH
我只想读取文本文件每一行的第一个字符,忽略其余字符。
这是一个示例文件:
x <- c(
"Afklgjsdf;bosfu09[45y94hn9igf",
"Basfgsdbsfgn",
"Cajvw58723895yubjsdw409t809t80",
"Djakfl09w50968509",
"E3434t"
)
writeLines(x, "test.txt")
我可以通过阅读所有带有 readLines
and using substring
的内容来获取第一个字符来解决问题:
lines <- readLines("test.txt")
substring(lines, 1, 1)
## [1] "A" "B" "C" "D" "E"
虽然这看起来效率不高。有没有办法说服 R 只读取第一个字符,而不必丢弃它们?
我怀疑应该有一些使用scan
, but I can't find it. An alternative might be low level file manipulation (maybe with seek
的咒语)。
由于性能只与较大的文件相关,这里有一个更大的测试文件用于基准测试:
set.seed(2015)
nch <- sample(1:100, 1e4, replace = TRUE)
x2 <- vapply(
nch,
function(nch)
{
paste0(
sample(letters, nch, replace = TRUE),
collapse = ""
)
},
character(1)
)
writeLines(x2, "bigtest.txt")
更新:看来你无法避免扫描整个文件。最好的速度增益似乎是使用更快的替代 readLines
(
01/04/2015 编辑以将更好的解决方案置于顶部。
更新 2 在打开的连接上将 scan()
方法更改为 运行 而不是在每次迭代时打开和关闭允许逐行读取行并消除循环。时机改善了很多。
## scan() on open connection
conn <- file("bigtest.txt", "rt")
substr(scan(conn, what = "", sep = "\n", quiet = TRUE), 1, 1)
close(conn)
我还发现了stringi包中的stri_read_lines()
函数,它的帮助文件说目前是实验性的,但速度非常快。
## stringi::stri_read_lines()
library(stringi)
stri_sub(stri_read_lines("bigtest.txt"), 1, 1)
下面是这两种方法的时间安排。
## timings
library(microbenchmark)
microbenchmark(
scan = {
conn <- file("bigtest.txt", "rt")
substr(scan(conn, what = "", sep = "\n", quiet = TRUE), 1, 1)
close(conn)
},
stringi = {
stri_sub(stri_read_lines("bigtest.txt"), 1, 1)
}
)
# Unit: milliseconds
# expr min lq mean median uq max neval
# scan 50.00170 50.10403 50.55055 50.18245 50.56112 54.64646 100
# stringi 13.67069 13.74270 14.20861 13.77733 13.86348 18.31421 100
原始[较慢]答案:
您可以尝试 read.fwf()
(固定宽度文件),将宽度设置为单个 1 以捕获每行的第一个字符。
read.fwf("test.txt", 1, stringsAsFactors = FALSE)[[1L]]
# [1] "A" "B" "C" "D" "E"
当然没有经过全面测试,但适用于测试文件,并且是无需读取整个文件即可获取子字符串的好函数。
Update 1 : read.fwf()
效率不高,内部调用了scan()
和read.table()
。我们可以跳过中间人,直接尝试scan()
。
lines <- count.fields("test.txt") ## length is num of lines in file
skip <- seq_along(lines) - 1 ## set up the 'skip' arg for scan()
read <- function(n) {
ch <- scan("test.txt", what = "", nlines = 1L, skip = n, quiet=TRUE)
substr(ch, 1, 1)
}
vapply(skip, read, character(1L))
# [1] "A" "B" "C" "D" "E"
version$platform
# [1] "x86_64-pc-linux-gnu"
如果您 allow/have 可以使用 Unix 命令行工具,您可以使用
scan(pipe("cut -c 1 test.txt"), what="", quiet=TRUE)
显然便携性较差,但可能非常快。
将@RichieCotton 的基准测试代码与 OP 建议的 "bigtest.txt" 文件一起使用:
expr min lq mean median uq
RC readLines 14.797830 17.083849 19.261917 18.103020 20.007341
RS read.fwf 125.113935 133.259220 148.122596 138.024203 150.528754
BB scan pipe cut 6.277267 7.027964 7.686314 7.337207 8.004137
RC readChar 1163.126377 1219.982117 1324.576432 1278.417578 1368.321464
RS scan 13.927765 14.752597 16.634288 15.274470 16.992124
每个答案的基准,在 Windows.
下library(microbenchmark)
microbenchmark(
"RC readLines" = {
lines <- readLines("test.txt")
substring(lines, 1, 1)
},
"RS read.fwf" = read.fwf("test.txt", 1, stringsAsFactors = FALSE)$V1,
"BB scan pipe cut" = scan(pipe("cut -c 1 test.txt"),what=character()),
"RC readChar" = {
con <- file("test.txt", "r")
x <- readChar(con, 1)
while(length(ch <- readChar(con, 1)) > 0)
{
if(ch == "\n")
{
x <- c(x, readChar(con, 1))
}
}
close(con)
}
)
## Unit: microseconds
## expr min lq mean median uq
## RC readLines 561.598 712.876 830.6969 753.929 884.8865
## RS read.fwf 5079.010 6429.225 6772.2883 6837.697 7153.3905
## BB scan pipe cut 308195.548 309941.510 313476.6015 310304.412 310772.0005
## RC readChar 1238.963 1549.320 1929.4165 1612.952 1740.8300
## max neval
## 2156.896 100
## 8421.090 100
## 510185.114 100
## 26437.370 100
在更大的数据集上:
## Unit: milliseconds
## expr min lq mean median uq max neval
## RC readLines 52.212563 84.496008 96.48517 103.319789 104.124623 158.086020 20
## RS read.fwf 391.371514 660.029853 703.51134 766.867222 777.795180 799.670185 20
## BB scan pipe cut 283.442150 482.062337 516.70913 562.416766 564.680194 567.089973 20
## RC readChar 2819.343753 4338.041708 4500.98579 4743.174825 4921.148501 5089.594928 20
## RS scan 2.088749 3.643816 4.16159 4.651449 4.731706 5.375819 20
data.table::fread()
似乎击败了迄今为止提出的所有解决方案,并且具有 运行 在 Windows 和 *NIX 机器上速度相当快的优点:
library(data.table)
substring(fread("bigtest.txt", sep="\n", header=FALSE)[[1]], 1, 1)
这是 microbenchmark 在 Linux 盒子上的计时(实际上是双启动笔记本电脑,以 Ubuntu 启动):
Unit: milliseconds
expr min lq mean median uq max neval
RC readLines 15.830318 16.617075 18.294723 17.116666 18.959381 27.54451 100
JOB fread 5.532777 6.013432 7.225067 6.292191 7.727054 12.79815 100
RS read.fwf 111.099578 113.803053 118.844635 116.501270 123.987873 141.14975 100
BB scan pipe cut 6.583634 8.290366 9.925221 10.115399 11.013237 15.63060 100
RC readChar 1347.017408 1407.878731 1453.580001 1450.693865 1491.764668 1583.92091 100
这里是同一台笔记本电脑作为 Windows 机器启动的时间(使用 Rtools 提供的命令行工具 cut
):
Unit: milliseconds
expr min lq mean median uq max neval cld
RC readLines 26.653266 27.493167 33.13860 28.057552 33.208309 61.72567 100 b
JOB fread 4.964205 5.343063 6.71591 5.538246 6.027024 13.54647 100 a
RS read.fwf 213.951792 217.749833 229.31050 220.793649 237.400166 287.03953 100 c
BB scan pipe cut 180.963117 263.469528 278.04720 276.138088 280.227259 387.87889 100 d
RC readChar 1505.263964 1572.132785 1646.88564 1622.410703 1688.809031 2149.10773 100 e
找出文件大小,将其作为单个二进制 blob 读入,找到感兴趣字符的偏移量(不要计算文件末尾的最后一个 '\n'!),以及强制转换为最终形式
f0 <- function() {
sz <- file.info("bigtest.txt")$size
what <- charToRaw("\n")
x = readBin("bigtest.txt", raw(), sz)
idx = which(x == what)
rawToChar(x[c(1L, idx[-length(idx)] + 1L)], multiple=TRUE)
}
data.table 解决方案(我认为是迄今为止最快的 -- 需要将第一行作为数据的一部分!)
library(data.table)
f1 <- function()
substring(fread("bigtest.txt", header=FALSE)[[1]], 1, 1)
相比之下
> identical(f0(), f1())
[1] TRUE
> library(microbenchmark)
> microbenchmark(f0(), f1())
Unit: milliseconds
expr min lq mean median uq max neval
f0() 5.144873 5.515219 5.571327 5.547899 5.623171 5.897335 100
f1() 9.153364 9.470571 9.994560 10.162012 10.350990 11.047261 100
仍然很浪费,因为整个文件在大部分被丢弃之前被读入内存。
我发现以微秒或毫秒的顺序对基准操作提供的信息量不大。但我明白在某些情况下这是无法避免的。在这些情况下,我仍然发现有必要测试不同(增加大小)的数据以粗略衡量该方法的扩展性..
这是我在 @MartinMorgan 的测试中使用 f0()
和 f1()
在 1e4、1e5 和 1e6 行上进行的 运行 结果:
1e4
# Unit: milliseconds
# expr min lq mean median uq max neval
# f0() 4.226333 7.738857 15.47984 8.398608 8.972871 89.87805 100
# f1() 8.854873 9.204724 10.48078 9.471424 10.143601 84.33003 100
1e5
# Unit: milliseconds
# expr min lq mean median uq max neval
# f0() 71.66205 176.57649 174.9545 184.0191 187.7107 307.0470 100
# f1() 95.60237 98.82307 104.3605 100.8267 107.9830 205.8728 100
1e6
# Unit: seconds
# expr min lq mean median uq max neval
# f0() 1.443471 1.537343 1.561025 1.553624 1.558947 1.729900 10
# f1() 1.089555 1.092633 1.101437 1.095997 1.102649 1.140505 10
identical(f0(), f1())
在所有测试中返回 TRUE。
更新:
1e7
我还在 1e7 行上 运行。
f1()
(data.table) 运行 在 9.7 秒内,而 f0()
运行 第一次在 7.8 秒内,9.4 和 6.6 秒第二次.
但是,f1()
在读取整个 0.479GB 文件时内存没有明显变化,而 f0()
导致 2.4GB 的峰值。
另一个观察:
set.seed(2015)
x2 <- vapply(
1:1e5,
function(i)
{
paste0(
sample(letters, 100L, replace = TRUE),
collapse = "_"
)
},
character(1)
)
# 10 million rows, with 200 characters each
writeLines(unlist(lapply(1:100, function(x) x2)), "bigtest.txt")
## readBin() results in a 2 billion row vector
system.time(f0()) ## explodes on memory
因为 readBin()
步骤产生了一个 20 亿长度的向量(读取文件需要 ~1.9GB),而 which(x == what)
步骤需要 ~4.5+GB(总共 = ~6.5GB)那时我停止了这个过程。
fread()
在这种情况下大约需要 23 秒。
HTH