JDK的一个Bug监听文件变更的初步实现思路

目录
  • 背景
  • 初步实现思路
  • JDK的Bug登场
  • 更新解决方案
  • 小结

背景

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

看似比较简单的一个功能,但如果在某些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是这样描述的:

JDK-8177809

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);
   // 睡眠100ms
   Thread.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("版本已变化,进行业务处理");
   }
   // 睡眠100ms
   Thread.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?

到此这篇关于JDK的一个Bug监听文件变更要小心了的文章就介绍到这了,更多相关JDK监听文件内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java代理模式(jdk proxy)

    目录 什么是代理 举个栗子 什么是代理模式 实现代理的方式 静态代理 根据以上过程,分析静态代理的优缺点 动态代理 CGLIB代理 JDK代理 InvocationHandler接口 Method类 Proxy类 jdk动态代理的实现步骤 总结 什么是代理 举个栗子 比如有一家美国的大学,可以对全世界招生.但是对于家长来说,家长不能直接自己去找学校,家长没有能力去直接访问学校,或者说,美国学校不接受个人来访,那么此时就需要一个留学中介来帮助这家美国学校招 生,中介就是学校的代理.中介和学校要做的

  • Java反射(JDK)与动态代理(CGLIB)详解

    目录 一.反射 二.动态代理 1.JDK代理 2.CGLIB代理 3.JDK代理与CGLIB代理对比 总结 一.反射 概念:在运行状态中,对于任意的一个类,都能够知道这个类的所有字段和方法,对任意一个对象都能够通过反射机制调用一个类的任意方法 实现方法:JVM在第一次加载某个类时会生成一个Class对象,里面记录了这个类的信息 链接:类加载机制(留坑) 二.动态代理 动态代理的作用:在不改变原代码的基础上增加新的功能,如日志.权限检验等 反射在动态代理中的应用:由于知道原类的字段.方法等信息,才

  • 聊聊maven与jdk版本对应关系

    目录 maven与jdk版本对应关系 解决方式(windows) maven和java的jdk版本不同 怎么改成一样的呢? maven与jdk版本对应关系 很多搬砖的小伙伴在使用maven项目打包的时候,都会遇到下面的编译问题: Caused by: java.lang.UnsupportedClassVersionError: org/apache/maven/plugin/compiler/CompilerMojo : Unsupported major.minor version 51.0

  • JDK的一个Bug监听文件变更的初步实现思路

    目录 背景 初步实现思路 JDK的Bug登场 更新解决方案 小结 背景 在某些业务场景下,我们需要自己实现文件内容变更监听的功能,比如:监听某个文件是否发生变更,当变更时重新加载文件的内容. 看似比较简单的一个功能,但如果在某些JDK版本下,可能会出现意想不到的Bug. 本篇文章就带大家简单实现一个对应的功能,并分析一下对应的Bug和优缺点. 初步实现思路 监听文件变动并读取文件,简单的思路如下: 单起一个线程,定时获取文件最后更新的时间戳(单位:毫秒): 对比上一次的时间戳,如果不一致,则说明

  • Java实现监听文件变化的三种方案详解

    目录 背景 方案一:定时任务 + File#lastModified 方案二:WatchService 方案三:Apache Commons-IO 小结 背景 在研究规则引擎时,如果规则以文件的形式存储,那么就需要监听指定的目录或文件来感知规则是否变化,进而进行加载.当然,在其他业务场景下,比如想实现配置文件的动态加载.日志文件的监听.FTP文件变动监听等都会遇到类似的场景. 本文给大家提供三种解决方案,并分析其中的利弊,建议收藏,以备不时之需. 方案一:定时任务 + File#lastModi

  • node.js监听文件变化的实现方法

    前言 随着前端技术的飞速发展,前端开发也从原始的刀耕火种,向着工程化效率化的方向发展.在各种开发框架之外,打包编译等技术也是层出不穷,开发体验也是越来越好.例如HMR,让我们的更新可以即时可见,告别了手动F5的情况.其实现就是监听文件变化自动调用构建过程.下面就关注下如何实现node监听文件变化. 场景 假定要监听index.js,每当内容更改重新编译. 我们就用简单的console来标识执行编译.下面就是实现该功能. node原生API fs.watchFile 翻下node的文档就会看到一个

  • Java利用WatchService监听文件变化示例

    在实现配置中心的多种方案中,有基于JDK7+的WatchService方法,其在单机应用中还是挺有实践的意义的. 代码如下: package com.longge.mytest; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardWatchEventKind

  • jQuery监听文件上传实现进度条效果的方法

    原理: 给XMLHttpRequest对象的upload属性绑定onprogress方法监听上传过程 var xhr=new XMLHttpRequest(); xhr.upload.onprogress=function(e){} 因为jQuery默认使用的XMLHttpRequest对象是内部生成的无法直接给jq的xhr绑定onprogress方法 所以只要给jQuery重新生成一个绑定了onprogress的XMLHttpRequest对象即可实现 首先封装一个方法 传入一个监听函数 返回

  • Linux inotify监听文件状态的操作方法

    Inotify 是一个 Linux特性,它监控文件系统操作,比如读取.写入和创建.Inotify 反应灵敏,用法非常简单,并且比 cron 任务的繁忙轮询高效得多.学习如何将 inotify 集成到您的应用程序中,并发现一组可用来进一步自动化系统治理的命令行工具. 通俗来说,inotify可以监控文件的状态并且对变化的状态做出一些操作. 安装 yum install inotify-tools -y inotifywait命令可以用来收集有关文件访问信息 inotifywatch命令用于收集关于

  • Java NIO.2 使用Path接口来监听文件、文件夹变化

    Java7对NIO进行了大的改进,新增了许多功能: •对文件系统的访问提供了全面的支持 •提供了基于异步Channel的IO 这些新增的IO功能简称为 NIO.2,依然在java.nio包下. 早期的Java只提供了File类来操作文件.文件夹本身,功能有限,性能也不高. NIO.2为解决这种缺陷,提供了Path接口,并提供了Paths.Files2个工具类,这2个工具类包含的方法都是静态方法,Files类提供了大量的静态方法来操作文件.文件夹. Path接口.Paths工具类使用示例: //获

  • Java 实现实时监听文件夹是否有新文件增加并上传服务器功能

    本文中主要陈述一种实时监听文件夹中是否有文件增加的功能,可用于实际文件上传功能的开发.     主要实现方式: (1)利用Timer的定时循环执行代码的功能: (2)利用WatchService实时监听文件夹是否有新文件增加,通过阻塞式IO流实现文件上传服务器. 代码如下: private static String path = "E:\\Kankan"; public static void getFile() throws FileNotFoundException, IOExc

  • 处理Oracle 监听文件listener.log问题

       如果连接时候变得较慢 查看Oracle日志记录,可能是因为此文件太大,超过2G, 需要定期清理,(如果多用户,记得用root,可能没权限) 查看listener.log? find / -name listener.log 经查看,竟然高达2G得日志数据,由于一些老旧的OS不支持2GB以上的文件,故当listener.log文件 超过2GB时,会出现无法处理新的连接,新的操作系统虽然不会出现这个问题,但我们依然需要对其 进行定期处理清理. Listener log日志文件处理 [oracl

  • Win Oracle 监听文件配置参考代码实例

    这篇文章主要介绍了Win Oracle 监听文件配置参考代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 Win lister.ora配置参考 # listener.ora Network Configuration File: C:\app\Administrator\product\11.2.0\dbhome_1\NETWORK\ADMIN\listener.ora # Generated by Oracle configuratio

随机推荐