<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在某些業務場景下,我們需要自己實現檔案內容變更監聽的功能,比如:監聽某個檔案是否發生變更,當變更時重新載入檔案的內容。
看似比較簡單的一個功能,但如果在某些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返回的時間單位是毫秒,如果同一毫秒內容出現兩次改動,而定時任務查詢時恰好落在兩次變動之間,則後一次變動則無法被感知到。
第一個缺點,對業務的影響不大;第二個缺點的概率比較小,可以忽略不計;
上面的程式碼實現,正常情況下是沒什麼問題的,但如果你使用的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監聽檔案內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45