背景

在某些业务场景下,我们需要自己实现文件内容变更监听的功能,比如:监听某个文件是否发生变更,当变更时重新加载文件的内容。

看似比较简单的一个功能,但如果在某些JDK版本下,可能会出现意想不到的Bug。

本篇文章就带大家简单实现一个对应的功能,并分析一下对应的Bug和优缺点。

初步实现思路

监听文件变动并读取文件,简单的思路如下:

  • 单起一个线程,定时获取文件最后更新的时间戳(单位:毫秒);
  • 对比上一次的时间戳,如果不一致,则说明文件被改动,则重新进行加载;

这里写一个简单功能实现(不包含定时任务部分)的demo:

public class FileWatchDemo {/*** 上次更新时间*/public static long LAST_TIME = 0L;public static void main(String[] args) throws IOException {String fileName = "/Users/zzs/temp/1.txt";// 创建文件,仅为实例,实践中由其他程序触发文件的变更createFile(fileName);// 执行2次for (int i = 0; i < 2; i++) {long timestamp = readLastModified(fileName);if (timestamp != LAST_TIME) {System.out.println("文件已被更新:" + timestamp);LAST_TIME = timestamp;// 重新加载,文件内容} else {System.out.println("文件未更新");}}}public static void createFile(String fileName) throws IOException {File file = new File(fileName);if (!file.exists()) {boolean result = file.createNewFile();System.out.println("创建文件:" + result);}}public static long readLastModified(String fileName) {File file = new File(fileName);return file.lastModified();}
}

在上述代码中,先创建一个文件(方便测试),然后两次读取文件的修改时间,并用LAST_TIME记录上次修改时间。如果文件的最新更改时间与上一次不一致,则更新修改时间,并进行业务处理。

示例代码中for循环两次,便是为了演示变更与不变更的两种情况。执行程序,打印日志如下:

文件已被更新:1653557504000
文件未更新

执行结果符合预期。

这种解决方案很明显有两个缺点:

  • 无法实时感知文件的变动,程序轮训毕竟有一个时间差;
  • lastModified返回的时间单位是毫秒,如果同一毫秒内容出现两次改动,而定时任务查询时恰好落在两次变动之间,则后一次变动则无法被感知到。

第一个缺点,对业务的影响不大;第二个缺点的概率比较小,可以忽略不计;

JDK的Bug登场

上面的代码实现,正常情况下是没什么问题的,但如果你使用的Java版本为8或9时,则可能出现意想不到的Bug,这是由JDK本身的Bug导致的。

编号为JDK-8177809的Bug是这样描述的:

Bug地址为:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=8177809

这个Bug的基本描述就是:在Java8和9的某些版本下,lastModified方法返回时间戳并不是毫秒,而是秒,也就是说返回结果的后三位始终为0。

我们来写一个程序验证一下:

public class FileReadDemo {public static void main(String[] args) throws IOException, InterruptedException {String fileName = "/Users/zzs/temp/1.txt";// 创建文件createFile(fileName);for (int i = 0; i < 10; i++) {// 向文件内写入数据writeToFile(fileName);// 读取文件修改时间long timestamp = readLastModified(fileName);System.out.println("文件修改时间:" + timestamp);// 睡眠100msThread.sleep(100);}}public static void createFile(String fileName) throws IOException {File file = new File(fileName);if (!file.exists()) {boolean result = file.createNewFile();System.out.println("创建文件:" + result);}}public static void writeToFile(String fileName) throws IOException {FileWriter fileWriter = new FileWriter(fileName);// 写入随机数字fileWriter.write(new Random(1000).nextInt());fileWriter.close();}public static long readLastModified(String fileName) {File file = new File(fileName);return file.lastModified();}
}

在上述代码中,先创建一个文件,然后在for循环中不停的向文件写入内容,并读取修改时间。每次操作睡眠100ms。这样,同一秒就可以多次写文件和读修改时间。

执行结果如下:

文件修改时间:1653558619000
文件修改时间:1653558619000
文件修改时间:1653558619000
文件修改时间:1653558619000
文件修改时间:1653558619000
文件修改时间:1653558619000
文件修改时间:1653558620000
文件修改时间:1653558620000
文件修改时间:1653558620000
文件修改时间:1653558620000

修改了10次文件的内容,只感知到了2次。JDK的这个bug让这种实现方式的第2个缺点无限放大了,同一秒发生变更的概率可比同一毫秒发生的概率要大太多了。

PS:在官方Bug描述中提到可以通过Files.getLastModifiedTime来实现获取时间戳,但笔者验证的结果是依旧无效,可能不同版本有不同的表现吧。

更新解决方案

Java 8目前是主流版本,不可能因为JDK的该bug就换JDK吧。所以,我们要通过其他方式来实现这个业务功能,那就是新增一个用来记录文件版本(version)的文件(或其他存储方式)。这个version的值,可在写文件时按照递增生成版本号,也可以通过对文件的内容做MD5计算获得。

如果能保证版本顺序生成,使用时只需读取版本文件中的值进行比对即可,如果变更则重新加载,如果未变更则不做处理。

如果使用MD5的形式,则需考虑MD5算法的性能,以及MD5结果的碰撞(概率很小,可以忽略)。

下面以版本的形式来展示一下demo:

public class FileReadVersionDemo {public static int version = 0;public static void main(String[] args) throws IOException, InterruptedException {String fileName = "/Users/zzs/temp/1.txt";String versionName = "/Users/zzs/temp/version.txt";// 创建文件createFile(fileName);createFile(versionName);for (int i = 1; i < 10; i++) {// 向文件内写入数据writeToFile(fileName);// 同时写入版本writeToFile(versionName, i);// 监听器读取文件版本int fileVersion = Integer.parseInt(readOneLineFromFile(versionName));if (version == fileVersion) {System.out.println("版本未变更");} else {System.out.println("版本已变化,进行业务处理");}// 睡眠100msThread.sleep(100);}}public static void createFile(String fileName) throws IOException {File file = new File(fileName);if (!file.exists()) {boolean result = file.createNewFile();System.out.println("创建文件:" + result);}}public static void writeToFile(String fileName) throws IOException {writeToFile(fileName, new Random(1000).nextInt());}public static void writeToFile(String fileName, int version) throws IOException {FileWriter fileWriter = new FileWriter(fileName);fileWriter.write(version +"");fileWriter.close();}public static String readOneLineFromFile(String fileName) {File file = new File(fileName);String tempString = null;try (BufferedReader reader = new BufferedReader(new FileReader(file))) {//一次读一行,读入null时文件结束tempString = reader.readLine();} catch (IOException e) {e.printStackTrace();}return tempString;}
}

执行上述代码,打印日志如下:

版本已变化,进行业务处理
版本已变化,进行业务处理
版本已变化,进行业务处理
版本已变化,进行业务处理
版本已变化,进行业务处理
版本已变化,进行业务处理
版本已变化,进行业务处理
版本已变化,进行业务处理
版本已变化,进行业务处理

可以看到,每次文件变更都能够感知到。当然,上述代码只是示例,在使用的过程中还是需要更多地完善逻辑。

小结

本文实践了一个很常见的功能,起初采用很符合常规思路的方案来解决,结果恰好碰到了JDK的Bug,只好变更策略来实现。当然,如果业务环境中已经存在了一些基础的中间件还有更多解决方案。

而通过本篇文章我们学到了JDK Bug导致的连锁反应,同时也见证了:实践见真知。很多技术方案是否可行,还是需要经得起实践的考验才行。赶快检查一下你的代码实现,是否命中该Bug?

博主简介:《SpringBoot技术内幕》技术图书作者,酷爱钻研技术,写技术干货文章。

公众号:「程序新视界」,博主的公众号,欢迎关注~

技术交流:请联系博主微信号:zhuan2quan


程序新视界”,一个100%技术干货的公众号


JDK的一个Bug,监听文件变更要小心了相关推荐

  1. Java实现监听文件变化的三种方法,推荐第三种

    背景 在研究规则引擎时,如果规则以文件的形式存储,那么就需要监听指定的目录或文件来感知规则是否变化,进而进行加载.当然,在其他业务场景下,比如想实现配置文件的动态加载.日志文件的监听.FTP文件变动监 ...

  2. Android 中关于 FileObserver类监听文件状态的实践

    文章目录 需求背景 走进源码 实现示例 参考 需求背景 当某一个目录的文件发生变化(创建.修改.删除.移动)时,需要给一个回调事件给其他端调用. 其他场景:阅后即焚等等. 比如在 Android 的 ...

  3. hutool工具包Tailer类监听文件的bug

    使用hutool工具包Tailer类监听文件内容时,如果文件忽然被清空后在重新写入,此时无法监听到文件第一行数据 解决方法: 复写cn.hutool.core.io.file包下LineReadWat ...

  4. Hutool操作和监听文件

    目录 1 文件监听简单使用 1.1 WatchMonitor 1.2 内部应用 1.3 监听指定事件 1.4 监听全部事件 1.5 延迟处理监听事件 2 文件的读取 3 文件的写入 4 文件追加 5 ...

  5. FileSystemWatcher监听文件是否有被修改

    作用:监听文件系统更改通知,并在目录或目录中的文件更改时引发事件. 需求:监听特定文件是否修改,然后做出相应的操作. 方法: ①利用一个线程,一直去查找该指定的文件是否有被修改,如果修改则操作特定步骤 ...

  6. python监听文件更改记录_如何用机器人监听老板微信?

    随着微信社交的兴起,我们加入的群也越来越多,一个不经意就被拉入好几个群,群是大家协同交流的平台,但是微信群却越来越泛滥,不知道大家有没有统计过自己浪费在毫无营养的群中的时间? 因为群质量或者群太吵的 ...

  7. python监听文件最后修改人_Python持续监听文件变化代码实例

    在日常的工作中,有时候会有这样的需求,需要一个常驻任务,持续的监听一个目录下文件的变化,对此作出回应. pyinotify就是这样的一个python包,使用方式如下: 一旦src.txt有新的内容,程 ...

  8. 使用Node.JS监听文件夹变化

    使用Node.JS监听文件夹改变有许多应用场合,比如: 构建自动编绎工具 当源文件改变时,自动运行build过程,比如当你写CoffeeScript文件或SASS CSS文件时,保存之后可即时生成对应 ...

  9. 查看oracle监听服务器,处理Oracle 监听文件listener.log问题

    如果连接时候变得较慢 查看Oracle日志记录,可能是因为此文件太大,超过2G, 需要定期清理,(如果多用户,记得用root,可能没权限) 查看listener.log? find / -name l ...

最新文章

  1. 一个高效的内存池实现
  2. Linux根文件系统学习总结
  3. 设计模式——责任链模式
  4. php用while循环做出1到10的乘积,PHP实现笛卡尔积算法的实例讲解
  5. “科学学”视角下的科研工作者行为研究
  6. CentOS 恢复 rm -rf * 误删数据(转)
  7. Java中try catch的原则
  8. select下拉框带模糊查询_SQL 之 简单查询
  9. 中blur函数_实时渲染中的软阴影技术
  10. 剪贴板 Clipbrd 直接用法
  11. java ssh会议室管理系统(源码+文档)【源码分享】
  12. virtualbox安装androidx86进入console控制台,不能进入启动界面,卡死在detecting android-x86 found at /dev/sda1
  13. 反爬虫SSL TLS指纹识别和绕过JA3算法.md
  14. NinePatch图片
  15. 华为设备常用软件包名
  16. UML-封神之路的开始
  17. 本科课程【数字图像处理】实验5 - 空洞填充
  18. BIN,S19,M0T,SREC,HEX文件解析;FileParse(二)之源码解析
  19. UserBehavior用户行为分析
  20. 【Html+CSS】3D旋转相册

热门文章

  1. 为什么那么多人喜欢用CTA策略?
  2. 2015百度面试题--对10亿个32位整数去重和排序
  3. div层调整zindex属性无效原因分析及解决方法
  4. BQB PTS dongle不识别问题
  5. cad能自学成才吗_我在6个月内成为一名自学成才的开发人员,所以你能
  6. 实验.........
  7. 独立博客怎样申请谷歌Adsense
  8. KNIME Python Integration安装配置指南
  9. 计算机表格应用试卷,2020年7月网络教育统考《计算机应用基础》电子表格模拟题试卷操作题...
  10. Linux之系统痕迹命令