Java 標準 API 無直接支援硬體裝置插拔事件監聽。在 Windows 平台,常見替代方案是輪詢檢測磁碟機代號變化來偵測 USB 隨身碟插入/移除。本文提供一個基於輪詢的簡易實作,適用於桌面應用程式。
核心邏輯
- Windows 磁碟機代號固定為 C: 到 Z:
- 定期檢查各磁碟機
new File("X:\\").exists() 狀態
- 比對上一次狀態,發現 從不存在→存在 即判定為 USB 插入
- 同理 存在→不存在 判定為移除
完整實作程式碼
package org.load.u;
import java.io.File;
import java.util.LinkedHashMap;
import java.util.Map;
public class UsbDetector {
// 記錄各磁碟機狀態:true=存在, false=不存在
private static final Map<String, Boolean> driveStatus = new LinkedHashMap<>();
// 監測的磁碟機代號 (通常 C: 為系統碟,D: 起為資料/可移動碟)
private static final String[] DRIVES = {"C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"};
// 輪詢間隔 (毫秒)
private static final long POLL_INTERVAL_MS = 5000;
public static void main(String[] args) {
System.out.println("USB 偵測服務啟動,輪詢間隔: " + POLL_INTERVAL_MS + "ms");
initialize();
startMonitoring();
}
/** 初始化各磁碟機初始狀態 */
private static void initialize() {
for (String drive : DRIVES) {
File root = new File(drive + ":\\");
driveStatus.put(drive, root.exists());
System.out.printf("[初始] %s: -> %s%n", drive, root.exists() ? "存在" : "不存在");
}
}
/** 開始輪詢監控 */
private static void startMonitoring() {
while (true) {
for (String drive : DRIVES) {
File root = new File(drive + ":\\");
boolean currentExists = root.exists();
Boolean previousExists = driveStatus.get(drive);
// 偵測到插入:之前不存在,現在存在
if (currentExists && (previousExists == null || !previousExists)) {
onUsbInserted(drive);
}
// 偵測到移除:之前存在,現在不存在
else if (!currentExists && previousExists != null && previousExists) {
onUsbRemoved(drive);
}
// 更新狀態
if (currentExists != (previousExists != null && previousExists)) {
driveStatus.put(drive, currentExists);
}
}
try {
Thread.sleep(POLL_INTERVAL_MS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("偵測執行緒被中斷,服務停止。");
break;
}
}
}
/** USB 插入事件處理 */
private static void onUsbInserted(String drive) {
System.out.printf(">>> [偵測] USB 裝置插入: %s:\\%n", drive);
// 這裡可觸發業務邏輯:掃描檔案、自動備份、病毒掃描等
}
/** USB 移除事件處理 */
private static void onUsbRemoved(String drive) {
System.out.printf("<<< [偵測] USB 裝置移除: %s:\\%n", drive);
// 這裡可觸發清理邏輯:釋放檔案鎖、更新 UI 等
}
}
關鍵設計說明
| 設計點 | 說明 |
| LinkedHashMap | 保持插入順序,方便日誌輸出閱讀 |
| DRIVES 陣列 | 涵蓋 C-Z 所有可能代號,避免遺漏 |
| 狀態比對邏輯 | 嚴格判斷「狀態翻轉」而非單純「當前存在」 |
| null 安全檢查 | 初始化前 get() 回傳 null,需防禦性程式設計 |
| 執行緒中斷處理 | 捕捉 InterruptedException 並重設中斷旗標,優雅停止 |
限制與改進方向
| 限制 | 改進方案 |
| 僅偵測磁碟機代號出現/消失,無法區分 USB/光碟/網路磁碟 | 配合 WMI (wmic logicaldisk get caption,drivetype) 判斷 drivetype=2 (可移動碟) |
| 輪詢有延遲 (預設 5 秒) | 縮短間隔至 1-2 秒,或使用 JNI 呼叫 Windows RegisterDeviceNotification API |
| 跨平台相容性差 (Linux/macOS 掛載點不同) | 抽象介面,各平台實作不同策略 (Linux 監聽 /media, /mnt; macOS 監聽 /Volumes) |
| 無法取得裝置詳細資訊 (廠商、序號、容量) | 整合oshi-core 函式庫或 JNA 呼叫系統 API |
進階:使用 oshi-core 取得詳細裝置資訊
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>6.2.2</version>
</dependency>
import oshi.SystemInfo;
import oshi.hardware.HWDiskStore;
SystemInfo si = new SystemInfo();
for (HWDiskStore disk : si.getHardware().getDiskStores()) {
System.out.printf("裝置: %s, 型號: %s, 序號: %s, 大小: %d GB, 類型: %s, 可移動: %b%n",
disk.getName(), disk.getModel(), disk.getSerial(),
disk.getSize() / 1_000_000_000, disk.getType(), disk.isRemovable());
}
生產環境建議
- 將偵測邏輯封裝為
UsbDetectorService 介面,支援 Spring `@PostConstruct`/`@PreDestroy` 生命週期管理
- 事件通知採用
ApplicationEventPublisher 或 Guava EventBus 解耦業務邏輯
- 配置檔化輪詢間隔、監測磁碟機清單、事件處理器
- 加入日誌框架記錄偵測歷程,支援稽核追蹤
- 上一篇文章: JDBC 入門範例:資料庫連線、查詢與資源管理完整流程
- 下一篇文章: Java 隨機字串產生器:支援字母、數字、符號任意組合的靈活工具類
Copyright ©
程式碼之家 All Rights Reserved