当前位置:首页 > 开发 > 编程语言 > Java > 正文

java7文件夹监控

发表于: 2012-04-04   作者:cherishLC   来源:转载   浏览次数:
摘要: java7的文件夹监控真是太烂了,折腾了一天,封装的差不多了,结果还是没有jnotify好用!!! (java7在递归子文件夹目录时貌似原理上有问题,应该真的就是每个文件夹单独注册监听事件吧,所以在拷贝整个文件夹到所监听的目录时,由于还没注册好监听事件,有些文件的创建事件根本无法监听到)!!! 虽然还得加额外的dll,还是用jnotify吧~~, jnotify网址: http://jn
java7的文件夹监控真是太烂了,折腾了一天,封装的差不多了,结果还是没有jnotify好用!!! (java7在递归子文件夹目录时貌似原理上有问题,应该真的就是每个文件夹单独注册监听事件吧,所以在拷贝整个文件夹到所监听的目录时,由于还没注册好监听事件,有些文件的创建事件根本无法监听到)!!!

虽然还得加额外的dll,还是用jnotify吧~~,
jnotify网址:http://jnotify.sourceforge.net/

当然,java7可以很好地控制监控的文件夹(不一定整个目录树都得监控不是,自己筛选挺方便)
java7的文件夹监控官方教程:http://docs.oracle.com/javase/tutorial/essential/io/notification.html


以下程序改自官方教程,主要是对事件进行了整合(原本更改文件名称被分为了两个事件delete+create,诸如此类):

import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;

import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchEvent.Kind;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashMap;

/*
 * java7文件夹监控程序的封装。
 * version: 2012_04_03
 */
public final class DirWatcher {
	public static interface WatchEventHandler {
		void handleEvent(KeyState event);
	}

	/**
	 * 记录操作类型
	 * 
	 */
	static enum OperationType {
		/**
		 * @author LC 调整
		 */
		Modify {
			@Override
			public String toString() {
				return "Modify";
			}
		},
		/**
		 * @author LC 新建
		 */
		Create {
			@Override
			public String toString() {
				return "Create";
			}
		},
		/**
		 * @author LC 删除
		 */
		Delete {
			@Override
			public String toString() {
				return "Delete";
			}
		},
		/**
		 * @author LC 空事件
		 */
		Null {
			@Override
			public String toString() {
				return "Null";
			}
		},
		/**
		 * @author LC 重命名
		 */
		Rename {
			@Override
			public String toString() {
				return "Rename";
			}
		},
		/**
		 * @author LC 没用到
		 */
		Move {
			@Override
			public String toString() {
				return "Move";
			}
		}
	}

	/**
	 * 记录{@link WatchKey}及事件的状态类
	 */
	class KeyState {
		/**
		 * 事件发生的路径或者{@link WatchKey}对应的路径
		 */
		public Path path;
		/**
		 * {@link WatchKey}上一步操作的路径或者重命名事件中的重命名后的名称
		 */
		public Path exPath;
		/**
		 * 事件的类型
		 */
		public OperationType opType = OperationType.Null;
		/**
		 * {@link WatchKey}的等级或者引发事件的{@link WatchKey}的等级
		 */
		public int level;
		/**
		 * 事件发生的事件
		 */
		public long opTime = -1;

		/**
		 * @param path
		 *            事件发生的路径或者{@link WatchKey}对应的路径
		 * @param level
		 *            {@link WatchKey}的等级或者引发事件的{@link WatchKey}的等级
		 */
		public KeyState(Path path, int level) {
			this.path = path;
			this.level = level;
		}

		/**
		 * @param path
		 *            事件发生的路径或者{@link WatchKey}对应的路径
		 * @param opType
		 *            操作的类型
		 * @param level
		 *            {@link WatchKey}的等级或者引发事件的{@link WatchKey}的等级
		 */
		public KeyState(Path path, OperationType opType, int level) {
			this.path = path;
			this.level = level;
			this.opType = opType;
		}

		/**
		 * @param path
		 *            事件发生的路径或者{@link WatchKey}对应的路径
		 * @param opType
		 *            操作的类型
		 * @param level
		 *            {@link WatchKey}的等级或者引发事件的{@link WatchKey}的等级
		 * @param opTime
		 *            事件发生的时间
		 */
		public KeyState(Path path, OperationType opType, int level, long opTime) {
			super();
			this.path = path;
			this.opType = opType;
			this.level = level;
			this.opTime = opTime;
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder();
			sb.append("opType=" + opType);
			sb.append("\topTime=" + opTime);
			sb.append("\tlevel=" + level);
			if (path != null)
				sb.append("\tpath=" + path.normalize());
			if (exPath != null)
				sb.append("\texPath=" + exPath.normalize());

			return sb.toString();
		}

	}

	private Path pathToMonitor;

	private WatchService ws;
	/**
	 * 是否监控子目录树
	 */
	private boolean watchSubDir = false;
	/**
	 * 存储键值对应的路径等信息
	 */
	private HashMap<WatchKey, KeyState> keyMap = new HashMap<>();
	/**
	 * 监控的最大层级
	 */
	private int maxLevel = 100;
	/**
	 * 是否输出调试信息
	 */
	static boolean debug = true;
	/**
	 * 处理事件的函数类
	 */
	private WatchEventHandler eventHandler;

	private static final WatchEventHandler nullHandler = new WatchEventHandler() {

		@Override
		public void handleEvent(KeyState event) {
			if (!debug) {
				System.out.println(event);
				System.out.println();
			}
		}
	};

	/**
	 * 演示用
	 * 
	 * @param pathToMonitor
	 * @param watchSubDir
	 */
	public DirWatcher(Path pathToMonitor, boolean watchSubDir) {
		this.pathToMonitor = pathToMonitor;
		this.watchSubDir = watchSubDir;
		try {
			ws = FileSystems.getDefault().newWatchService();
			if (watchSubDir)
				registerAll(pathToMonitor, maxLevel);
			else
				register(pathToMonitor, 0);
		} catch (IOException e) {
			e.printStackTrace();
		}

		eventHandler = nullHandler;

	}

	public DirWatcher(Path pathToMonitor, boolean watchSubDir, int maxLevel,
			WatchEventHandler eventHandler) {
		super();
		this.pathToMonitor = pathToMonitor;
		this.watchSubDir = watchSubDir;
		this.maxLevel = maxLevel;
		this.eventHandler = eventHandler == null ? eventHandler : nullHandler;
	}

	@SuppressWarnings("unchecked")
	static <T> WatchEvent<T> cast(WatchEvent<?> event) {
		return (WatchEvent<T>) event;
	}

	/**
	 * 注册{@link WatchService}
	 * 
	 * @param pathToMonitor
	 *            需要被监控的目录
	 * @param level
	 *            相对于最上层的要监控的目录的等级,如d:\test为第0级,则d:\test\folder则为1级
	 * @throws IOException
	 */
	private void register(Path pathToMonitor, int level) throws IOException {
		WatchKey key = pathToMonitor.register(ws, ENTRY_CREATE, ENTRY_DELETE,
				ENTRY_MODIFY);
		//		System.out.println(key + "\t注册key," + dir.toFile().getAbsolutePath());
		if (debug) {
			KeyState prev = keyMap.get(key);
			if (prev == null) {
				System.out.format("-->\tregister: %s\tregisted level=%d\n",
						pathToMonitor, level);
			} else {
				if (!pathToMonitor.equals(prev.path)) {
					System.out.format(
							"-->\tupdate: %s -> %s\tregisted level=%d\n", prev,
							pathToMonitor, level);
				}
			}
		}
		KeyState prev = keyMap.get(key);
		if (prev == null)
			keyMap.put(key, new KeyState(pathToMonitor, level));
		else {
			prev.path = pathToMonitor;
			prev.level = level;
		}
	}

	/**
	 * 监控一个目录及其子目录
	 * 
	 * @param curPath
	 *            要被监控的目录
	 * @param maxLevel
	 *            监控的等级数量
	 * @throws IOException
	 */
	private void registerAll(final Path curPath, final int maxLevel)
			throws IOException {
		registerAll(curPath, 0, maxLevel);
	}

	/**
	 * 监控一个目录及其子目录
	 * 
	 * @param curPath
	 *            要被监控的目录
	 * @param curLevel
	 *            当前目录相对于最上层的要监控的目录的等级,如d:\test为第0级,则d:\test\folder则为1级
	 * @param maxLevel
	 *            监控的等级数量,curLevel<maxLevel时,才进行监控
	 * @throws IOException
	 */
	private void registerAll(final Path curPath, final int curLevel,
			final int maxLevel) throws IOException {

		Files.walkFileTree(curPath, new SimpleFileVisitor<Path>() {
			int curLv = curLevel;

			@Override
			public FileVisitResult preVisitDirectory(Path dir,
					BasicFileAttributes attrs) throws IOException {
				if (curLv < maxLevel)
					register(dir, curLv);
				++curLv;
				if (curLv < maxLevel) {
					return FileVisitResult.CONTINUE;
				} else {
					return FileVisitResult.SKIP_SUBTREE;
				}
			}

			@Override
			public FileVisitResult postVisitDirectory(Path dir, IOException exc)
					throws IOException {
				--curLv;
				return super.postVisitDirectory(dir, exc);
			}
		});
	}

	/**
	 * 开始监控,事件的处理分为两类
	 * <p>
	 * 1、对重命名或新增的文件夹的监控,由Oracle的示例完成。
	 * <p>
	 * 2、文件夹、文件的删除、重命名、新建事件;对于这类事件,Java7处理的不好,会有重复的,对此进行了封装。原理是根据事件发生的顺序及时间间隔来判断
	 * ,如同一个key的删除接着一个新建实际上为重命名事件
	 * <p>
	 * 存在的问题:由于Java7系统函数的问题,当把一个含有文件的目录拷贝到监控中的目录时,有些文件无法扫描到!!!!(子目录的监控没有问题)
	 * 
	 */
	public void startMonitorDir() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				while (true) {
					// 等待监控的事件的发生
					WatchKey key;
					try {
						key = ws.take();
					} catch (InterruptedException e) {
						e.printStackTrace();
						return;
					}

					//事件的有效性检测
					KeyState stateOrigin = keyMap.get(key);
					Path dir = stateOrigin.path;
					if (dir == null) {
						System.err.println("WatchKey not recognized!!");
					}

					//
					int keyEventCount = 0;
					ArrayList<KeyState> eventList = new ArrayList<>();
					for (WatchEvent<?> event : key.pollEvents()) {
						Kind<?> kind = event.kind();
						// 遇到了不关心的事件
						if (kind == OVERFLOW) {
							continue;
						}
						++keyEventCount;

						// 获取文件路径
						WatchEvent<Path> ev = cast(event);
						Path name = ev.context();//相对路径
						Path child = dir.resolve(name);//绝对路径

						//增加到事件队列中
						KeyState eventState = new KeyState(child,
								stateOrigin.level);//不使用level字段
						eventState.opTime = System.currentTimeMillis();
						if (kind == ENTRY_CREATE) {
							eventState.opType = OperationType.Create;
						} else if (kind == ENTRY_DELETE) {
							eventState.opType = OperationType.Delete;
						} else if (kind == ENTRY_MODIFY)
							eventState.opType = OperationType.Modify;
						eventList.add(eventState);

						//事件的信息
						if (debug)
							System.out.format("%d:%s: %s\n", keyEventCount,
									event.kind().name(), child);

						// 如果有新的子目录被创建(或者其他目录更名为该目录),则加入新的子目录到监控列表中
						if (watchSubDir && (kind == ENTRY_CREATE)) {
							try {
								if (Files.isDirectory(child, NOFOLLOW_LINKS)) {
									if (debug)
										System.out.println(key + "\t原始key,"
												+ keyMap.get(key));
									registerAll(child, stateOrigin.level + 1,
											maxLevel);
								}
							} catch (IOException e) {
								//e.printStackTrace();
							}
						}
					}

					//分析事件队列,获取正确的事件
					if (eventList.size() == 1) {
						KeyState state = eventList.get(0);
						processEvent(key, state);
					} else if (eventList.size() > 1) {

						KeyState eventStart;

						final int size = eventList.size();
						for (int i = 0; i < size; i++) {
							eventStart = eventList.get(i);
							if (i + 1 < size) {
								KeyState eventEnd = eventList.get(i + 1);
								//重命名,先删除再创建
								if (eventStart.opType == OperationType.Delete
										&& eventEnd.opType == OperationType.Create) {
									eventStart.opType = OperationType.Rename;
									eventStart.exPath = eventEnd.path;
									processEvent(key, eventStart);
									++i;
									for (int j = i + 1; j < size; j++) {//过滤掉紧跟的modify事件
										KeyState tmp = eventList.get(j);
										if (tmp.path.equals(eventStart.exPath))
											i++;
									}
								} else {
									processEvent(key, eventStart);
									for (int j = i + 1; j < size; j++) {//过滤掉紧跟的modify事件
										KeyState tmp = eventList.get(j);
										if (tmp.path.equals(eventStart.path))
											i++;
									}

								}

							} else
								processEvent(key, eventStart);

							//							if(i+1>=size||evtList.get(i+1).lastOpType!=)

						}

					}
					if (debug)
						System.out.println();
					// reset key and remove from set if directory no longer accessible
					boolean valid = key.reset();
					if (!valid) {
						keyMap.remove(key);
						// all directories are inaccessible
						if (keyMap.isEmpty()) {
							break;
						}
					}

				}
			}

		}).start();
	}

	/**
	 * 处理事件,仍需要进行过滤
	 * 
	 * @param key
	 * @param event
	 */
	private void processEvent(WatchKey key, KeyState event) {
		KeyState keyState = keyMap.get(key);
		if (event.opType != OperationType.Modify
				|| !event.path.equals(keyState.exPath)
				|| keyState.opTime - event.opTime > 200) {//同一文件的两个modify时间过短,过滤掉一个(之前也有过滤,但中间可能插入了文件夹的modify事件);注意keyState中exPath是事件记录,path是监控的文件夹路径

			if (event.opType != OperationType.Modify
					|| !Files
							.isDirectory(event.path, LinkOption.NOFOLLOW_LINKS)) {//过滤文件夹的modify事件,毕竟文件夹modify是因为文件夹下的文件的改变,已经有对应事件了!!!当然,理论上应该再检测一下文件夹是否处于监控列表中,懒得写了,反正都打算用jnotify了!!!
				if (debug)
					System.out.println("key=" + key.hashCode() + "\t" + event);
				eventHandler.handleEvent(event);
			}
		}
		keyState.exPath = event.opType == OperationType.Rename ? event.exPath
				: event.path;//作为上一步操作的文件进行记录
		keyState.opTime = event.opTime;
	}

	public static void main(String[] args) {
		DirWatcher watcher = new DirWatcher(Paths.get("D:\\test2"), true);
		watcher.startMonitorDir();
	}
}

java7文件夹监控

  • 0

    开心

    开心

  • 0

    板砖

    板砖

  • 0

    感动

    感动

  • 0

    有用

    有用

  • 0

    疑问

    疑问

  • 0

    难过

    难过

  • 0

    无聊

    无聊

  • 0

    震惊

    震惊

编辑推荐
引言 这一周主要精力集中学习一个同事开发的本地文件搜索项目上,其中客户端添加共享文件时主要是使
个人私心的缘故,经常写一些博客之类的文章,由于看到网络上面好多同志转载后不标明出处,所以特地
概述 最近学习FileSystemWatcher的用法,它主要是监控一个文件夹,当文件夹内的文件要是有更改就要记
Folder Monitor 是一款绿色便携软件,主要提供文件夹监控的功能,可以监控文件的创建、删除、修改和
电子书下载地址:http://www.infoq.com/cn/minibooks/know-more-about-java7 电子书并不完整,把第一
1:先按照前人的来,安装好后,what the fuck啥都没有,java -veriosn之后还是1.6 http://blog.csdn
Java7 新特性集合(中文版) Java7平台标准版文档 甲骨文有两个产品实现Java平台标准版(Java SE)7
之前的 fastdfs 分布式搞好后,现在开始搭建 java 、tomcat 环境 JDK 1.7 下载地址: http://www.or
1、TWR 自动关闭资源 自动关闭资源必须要注意书写规范,所有需要关闭的资源必须使用一个变量声明出
如果让我个人理解什么是fork-join,我立刻会想到hadoop的map/reduce。他们是同一种模型。更早之前,
版权所有 IT知识库 CopyRight © 2009-2015 IT知识库 IT610.com , All Rights Reserved. 京ICP备09083238号