当前位置: 首页 > news >正文

Kotlin中优雅的一行行读取文本文件

1. 传统Java方式

一行一行的读取文本文件的需求是很常见的,Java中的原始方式如下:

  1. BufferedReader + try-with-resources

    File file = new File("D:\\demo.txt");
    try (BufferedReader reader = new BufferedReader(new FileReader(file))) {String line;while ((line = reader.readLine()) != null) {System.out.println(line);}
    } catch (Exception e) {e.printStackTrace();
    }
    
  2. Files.lines + try-with-resources

    File file = new File("D:\\demo.txt");
    try (Stream<String> lines = Files.lines(file.toPath())) {lines.forEach(line -> {System.out.println(line);});
    } catch (Exception e) {e.printStackTrace();
    }
    

    这里的Stream<String> lines = Files.lines(file.toPath())结果是一个Stream,这是Java8的 Stream API,它是惰性的,也就是说此时一行代码都还没有读取,当有求值操作的时候才会真正去读取,方便我们在读取之前添加各种操作,比如经典的过滤操作。而且它在读取的时候也是一行一行的读取的,并不是一下读完所有的行,所以不要以为Stream<String> lines就是已经把所有的行都读到了,其实不是的,比如可以给流设置一个查找条件,则找到所需要的行就可以提前结束,无需要读取完所有的行,示例如下:

    File file = new File("D:\\demo.txt");
    try (Stream<String> lines = Files.lines(file.toPath())) {Optional<String> result = lines.filter(line -> {System.out.println(line);return line.contains("World");}).findFirst();result.ifPresent(s -> System.out.println("找到需要的行了:" + s));
    } catch (Exception e) {e.printStackTrace();
    }
    

    运行结果如下:

    Hello
    World!
    找到需要的行了:World!
    

    从结果可以看到,只读取了两行,在第二行就找到需要的行了,然后后面的行就不会读取了,直接结束了。这里需要懂Java 8Lambda表达式和Stream API,可能很多人都还没学这块知识,那读起来是会有点懵的,但是这种方式确实比第一种方式好。

2. Kotlin 方式

  1. 与Java中BufferedReader + try-with-resources对等的方式

    val file = File("D:\\demo.txt")
    file.bufferedReader().use { reader ->var line = ""while (reader.readLine()?.also { line = it } != null) {println(line)}
    }
    

    没用习惯kotlin的人对于while中的写法可能会感觉到怪怪的。了解其原理的感觉其实还好。

  2. 优雅的一行行读取

    val file = File("D:\\demo.txt")
    file.forEachLine { println(it) }
    

    这实在是太精简了,你甚至可以合并成一行:

    File("D:\\demo.txt").forEachLine { println(it) }
    
  3. 与Java中Files.lines + try-with-resources原理相同的方式:

    val file = File("D:\\demo.txt")
    file.useLines { lines ->lines.forEach { println(it) }
    }
    

    这里返回的lines类型为 Sequence,它和Java 8中的Stream是一样的,也是惰性的,比如找到包含有World的一行,然后就不再读取:

    val file = File("D:\\demo.txt")
    file.useLines { lines ->lines.find { line ->println(line)line.contains("World")}
    }
    

3. 原理

不论是Java中的Files.lines()还是Kotlin中的file.useLines,它们返回的StreamSequence都是把文件封装为BufferedReader,然后再封装一个迭代器来实现一行行读取的,比如查看kotlin的useLines 源码,它返回的是LinesSequence对象的实现,源码如下:

private class LinesSequence(private val reader: BufferedReader) : Sequence<String> {override public fun iterator(): Iterator<String> {return object : Iterator<String> {private var nextValue: String? = nullprivate var done = falseoverride public fun hasNext(): Boolean {if (nextValue == null && !done) {nextValue = reader.readLine()if (nextValue == null) done = true}return nextValue != null}override public fun next(): String {if (!hasNext()) {throw NoSuchElementException()}val answer = nextValuenextValue = nullreturn answer!!}}}
}

可以看到,它底层也是使用BufferedReaderreadLine()进行一行一行读取的。所以,从这里我们就能了解到,它底层也是操作文件流,那我使用了一次 Sequence之后,就不能再使用第二次了,就像使用BufferedReader读取一次数据后,想要再读取一次这是不可能的,因为流是不能回头的。示例如下:

val file = File("D:\\demo.txt")
file.useLines { lines ->lines.forEach { println(it) }lines.forEach { println(it) }
}

运行结果如下:

Hello
World!
Good
morning
Nice.
Exception in thread "main" java.lang.IllegalStateException: This sequence can be consumed only once.at kotlin.sequences.ConstrainedOnceSequence.iterator(SequencesJVM.kt:23)at KotlinMainKt.main(KotlinMain.kt:14)at KotlinMainKt.main(KotlinMain.kt)

可以看到,读取一次之后,再读取就会报异常:This sequence can be consumed only once.,提示Sequence 只能被消费一次,什么叫消费呢?就是进行了求值操作或遍历。

所以,如果真的要读两遍,则要生成两个流再分别生成Sequence对象,如下:

val file = File("D:\\demo.txt")file.useLines { lines ->lines.forEach { println(it) }
}file.useLines { lines ->lines.forEach { println(it) }
}

或者,直接把所有行读出来,然后就可以重复读取,如下:

val file = File("D:\\demo.txt")
val lines: List<String> = file.readLines()
lines.forEach { println(lines) }
lines.forEach { println(lines) }

这种方式的缺点是,如果读取的是大文件,容易内存溢出。

总结:
Kotlin中的三种读取行的函数:

val file = File("D:\\demo.txt")
file.useLines { lines: Sequence<String> -> }
file.forEachLine { line: String -> }
val lines: List<String> = file.readLines()

通过查看源代码发现,useLines是基本,forEachLine 底层调用的是useLinesreadLines底层调用的是forEachLine

forEachLine实现:

public fun Reader.forEachLine(action: (String) -> Unit): Unit = useLines { it.forEach(action) }

从这里可以知道一个重要的知识点,forEachLine会遍历所有的行,且是无法停止的,示例如下:

file.forEachLine { line: String ->println(line)return@forEachLine
}

运行结果如下:

Hello
World!
Good
morning
Nice.

代码是希望打印一行就结束,但是实际还是会把每一行都打印。

所以,如果希望在指定条件下可以提前结束读取,则使用useLines

方法名选择
useLines一行行读,惰性序列,适合添加过滤条件,且可提前结束
forEachLine一行行读,不能添加过滤条件,且不能提前结束
readLines读完所有的行保存到集合,适合数据量小的情况
http://www.lryc.cn/news/580120.html

相关文章:

  • 缓存雪崩、缓存穿透、缓存预热、缓存更新、缓存降级
  • 【笔记】PyCharm 2025.2 EAP 创建 Poetry 和 Hatch 环境的踩坑实录与反馈
  • 三体融合实战:Django+讯飞星火+Colossal-AI的企业级AI系统架构
  • Android WebView 性能优化指南
  • 《Java修仙传:从凡胎到码帝》第三章:缩进之劫与函数峰试炼
  • React Ref使用
  • React中的useState 和useEffect
  • 指环王英文版魔戒再现 Part 1 Chapter 01
  • 力扣 hot100 Day34
  • [Linux]内核态与用户态详解
  • java web5(黑马)
  • Vue内置指令
  • 一、react18+项目初始化(vite)
  • 支付宝小程序关键词排名实战攻略,从0到1的突破之路
  • 八股学习(三)---MySQL
  • Spring AI Alibaba 来啦!!!
  • 【网络与系统安全】强制访问控制——BLP模型
  • Redis基础(5):Redis的Java客户端
  • 马尔可夫链:随机过程的记忆法则与演化密码
  • 【github】想fork的项目变为私有副本
  • WPF学习笔记(23)Window、Page与Frame、ViewBox
  • WPF+HelixToolkit打造炫酷自定义3D贴图立方体盒子模型
  • 简单 Python 爬虫程序设计
  • latency 对功耗的影响
  • VSCode 安装使用教程
  • vue3引入海康监控视频组件并实现非分屏需求一个页面同时预览多个监控视频;
  • 玩转n8n工作流教程(一):Windows系统本地部署n8n自动化工作流(n8n中文汉化)
  • goole chrome变更默认搜索引擎为百度
  • DotNetBrowser 2.27.14 版本发布啦!
  • Ubuntu下的Tomcat服务器部署