14 | 文件IO:实现高效正确的文件读写并非易事
该思维导图由 AI 生成,仅供参考
文件读写需要确保字符编码一致
- 深入了解
- 翻译
- 解释
- 总结
文件IO操作中的常见问题和解决方法是本文的重点内容。首先,文章强调了字符编码一致性的重要性,并提出了使用InputStreamReader和FileInputStream指定字符集来解决乱码问题。其次,介绍了使用Files类的readAllLines方法可能导致OOM问题,而使用File类的lines方法可以实现按需的流式读取。文章还通过实例分析了使用缓冲Buffer的重要性,并对比了不同使用方式的文件读写性能。此外,提到了静态方法的调用并不代表资源释放,需要注意使用try-with-resources方式来确保流的close方法可以释放资源。总之,本文为读者提供了有益的指导,帮助他们快速了解文件IO操作中的常见问题和解决方法,为实现高效、正确的文件读写提供了指导。文章还提到了一些Java的File类和Files类提供的文件复制、重命名、删除等操作的原子性问题,以及对文件操作的一些思考与讨论。文章内容丰富,涵盖了文件IO操作中的多个方面,对读者进行了全面的指导和启发。
《Java 业务开发常见错误 100 例》,新⼈⾸单¥59
全部留言(24)
- 最新
- 精选
- Darren今天算是打开了一片新的天地,因为日常的开发设计文件的不太多,竟然不知道有Files这样的牛逼操作,之前对于JDK相关的NIO关注的也不多,真的是打开了一闪窗。 先说下FileChannel 的 transfreTo 方法,这个方法出现在眼前很多次,因为之前看Kafka为什么吞吐量达的原因的时候,提到了2点:批处理思想和零拷贝; 批处理思想:就是对于Kafka内部很多地方来说,不是消息来了就发送,而是有攒一波发送一次,这样对于吞吐量有极大的提升,对于需要实时处理的情况,Kafka就不是很适合的原因; 零拷贝:Kafka快的另外一个原因是零拷贝,避免了内存态到内核态以及网络的拷贝,直接读取文件,发送到网络出去,零拷贝的含义不是没有拷贝,而是没有用户态到核心态的拷贝。 而在提到零拷贝的实现时,Java中说的就是FileChannel 的 transfreTo 方法。 然后回答下问题: 第一个问题: Files的相关方法文档描述: When not using the try-with-resources construct, then directory stream's close method should be invoked after iteration is completed so as to free any resources held for the open directory. 所以是需要手动关闭的。 第二个问题: 没有原子操作,因此是线程不安全的。个人理解,其实即使加上了原子操作,也是鸡肋,不实用的很,原因是:File 类和 Files的相关操作,其实都是调用操作系统的文件系统操作,这个文件除了JVM操作外,可能别的也在操作,因此还不如不保证,完全基于操作系统的文件系统去保证相关操作的正确性。
作者回复: 👍🏻👍🏻👍🏻👍🏻
2020-04-131083 - 👽这篇专栏内容不算多,但是感觉可扩展的地方很多。 我认为:读写字符流乱码,其实本质上在于环境不一致的问题。其实跟日志路径之类的问题思路一致。服务器保存日志,如果配置绝对路径,C盘下的log文件夹。等部署到服务器上就会出错。除了针对不同的环境使用不同的配置,还可以尝试使用相对路径,亦或者将路径以存数据库的方式持久化。其实本质上,跟编码格式的处理方式一样,尽量屏蔽不同环境之间的差异。 保证文件流释放,同样也可以延伸。不仅仅是文件流,任何涉及资源占用问题的时候,都需要考虑资源是否可以保证被释放。try-whit-resources来解决IO流,其实同样也应用于各种需要释放资源的场景。 关于缓冲区,个人理解,一个典型的应用就是数据库的分页查询。如果将所有数据一次查出,不但消耗资源,甚至有可能内存不够。如果一次只查一个,如果需要查询的是几十条数据,频繁进行数据库访问,性能也较差。所以,采取了折中的方案,分页查询,一次仅查出一部分数据。既不会内存溢出,也保证了响应速度。
作者回复: 不错
2020-04-1317 - 梦倚栏杆第一个问题:都间接实现了autoCloseable接口,所以都可以使用try-with-resources进行释放。 第二个非原子性,没有锁,也没有异常后的回滚。需要调用方进行事务控制
作者回复: 不错
2020-04-1213 - Geek_3b1096谢谢老师详细解说文件操作
作者回复: 不客气,觉得好可以点赞转发
2020-04-155 - 汝林外史BufferedInputStream的二级缓冲什么时候能用到呢?既然需要自己定义一个缓冲,比如2K,那么肯定也是控制一次读取2K,应该不会有读取超过2K的时候吧?
作者回复: 你可能会疑惑了,既然这样使用 BufferedInputStream 和 BufferedOutputStream 有什么意义呢?其实,这里我是为了演示所以示例三使用了固定大小的缓冲区,但在实际代码中每次需要读取的字节数很可能不是固定的,有的时候读取几个字节,有的时候读取几百字节,这个时候有一个固定大小较大的缓冲,也就是使用 BufferedInputStream 和 BufferedOutputStream 做为后备的稳定的二次缓冲,就非常有意义了
2020-04-1395 - Michael特别喜欢老师的这种工匠精神,对读者的每一个问题都精心回复
作者回复: 感谢认可
2020-04-193 - Mondaygbk与utf8区别(本文总结所得): 1、gbk只适合中文编码方式,utf8全世界的编码方式 2、对于中文汉字,gbk使用2个字节,utf8使用3个字节;对于英文字母都是1个字节。这种变长编码方式,怎么区分汉字和英文呢?
作者回复: 针对你的问题可以看一下http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html 其中『UTF-8 的编码规则很简单,只有二条』一节就明白了
2020-04-1933 - pedro第二个问题,不是原子的,所以需要注意,如果 io 异常,可能会出现复制后的文件不完整,文件未删除成功等问题
作者回复: 是,其实我在思考题中也会补充更多正文无法详细阐述的坑
2020-04-113 - 珅珅君你好,关于BufferedInputStream有一点疑问,如果我构造BufferedInputStream的时候设置缓冲流的大小是1kb,这里缓冲区叫A,但是调用read(byte[])的时候额外用的是8kb缓冲,这里缓冲区叫B,那么读取文件的时候,这两种缓冲的大小的工作流程是什么样的。先B后A还是?
作者回复: 先A后B(可能多次)
2020-07-272 - Demon.LeeLongAdder longAdder = new LongAdder(); IntStream.rangeClosed(1, 1000000).forEach(i -> { try { try (Stream<String> lines = Files.lines(Paths.get("demo.txt"))) { lines.forEach(line -> longAdder.increment()); } } catch (IOException e) { e.printStackTrace(); } }); log.info("total : {}", longAdder.longValue()); ------------------------------------------------------ 我一开始还奇怪为啥要两个try,catch放里面不就行了么,想了一下才明白,是为了捕获里面释放资源的异常,相当于捕获finally中的异常。 两道题都去翻了源码,第一题我觉得也是需要主动释放,Path也算一种fd吧,不太确定;第二题没有看到锁什么的,不是原子性的,不过创建或删除文件,重复处理,操作系统层会报错,但写内容到文件中就需要注意了。
作者回复: 直接放到catch里是可以的,这里我因为修改了wrong所以写成这样了。对于释放资源产生的异常,同样可以在catch中捕获,可以看一下try-with-resources语法糖会翻译成怎么样的代码就理解了。
2020-04-111