Java 7 WatchService - Ігнараванне некалькі асобнікаў аднаго і таго ж падзеі

Javadoc для StandardWatchEventKinds.ENTRY_MODIFY кажа:

<�Р> Запіс каталога мадыфікаваная. Калі каталог зарэгістраваны для гэтага   падзея, то WatchKey у чарзе, калі ён заўважыў, што запіс у   каталог быў зменены. Колькасць падзей у гэтым выпадку роўна 1   або больш. </р>

Пры рэдагаванні змесціва файла праз рэдактар, ён будзе змяняць як дату (або іншыя метададзеныя) і змест. Такім чынам, вы атрымліваеце два ENTRY_MODIFY падзеі, але кожны з іх будзе мець Колькасць на 1 (па меншай меры, гэта тое, што я бачу).

Я спрабую кантраляваць канфігурацыйны файл ( servers.cfg раней зарэгістраваны ў WatchService ), які абнаўляецца ўручную (гэта значыць. Праз камандны радок VI </код >) з наступным кодам:

while(true) {
    watchKey = watchService.take();//blocks

    for (WatchEvent<?> event : watchKey.pollEvents()) {
        WatchEvent watchEvent = (WatchEvent) event;
        WatchEvent.Kind kind = watchEvent.kind();

        System.out.println(watchEvent.context() + ", count: "+ watchEvent.count() + ", event: "+ watchEvent.kind());
       //prints (loop on the while twice)
       //servers.cfg, count: 1, event: ENTRY_MODIFY
       //servers.cfg, count: 1, event: ENTRY_MODIFY

        switch(kind.name()) {
            case "ENTRY_MODIFY":
                handleModify(watchEvent.context());//reload configuration class
                break;
            case "ENTRY_DELETE":
                handleDelete(watchEvent.context());//do something else
                break;              
        }
    }   

    watchKey.reset();       
}

Так вы атрымаеце два ENTRY_MODIFY падзеі, вышэй будзе перазагрузіць канфігурацыю ў два разы, калі толькі адзін раз неабходна. Ці ёсць спосаб ігнараваць усе, акрамя аднаго з іх, мяркуючы, што там можа быць больш, чым адзін такі выпадак?

Калі WatchService API мае такую ​​ўтыліту так шмат лепш. (Я накшталт не хачу, каб праверыць час паміж кожным падзеяй. Усе метады-апрацоўшчыкі ў маім кодзе сінхронныя.

Тое ж самае адбываецца, калі вы ствараеце (капіяваць/ўставіць) файл з аднаго каталога ў назіранай каталогу. Як вы можаце аб'яднаць абодва гэтых ў адно падзея?

31
Вы спрабавалі выкарыстаць AtomicBoolean у якасці сігналу, каб адмяніць наступныя змены?
дададзена аўтар Robert H, крыніца
@RobertH Праверце гэта, калі хоць? Я хачу, каб наступныя змены, але толькі адзін для кожнага ручной аперацыі абнаўлення.
дададзена аўтар Sotirios Delimanolis, крыніца

13 адказы

WatcherServices паведамляе пра падзеі ў два разы, таму што асноўнай файл абнаўляецца двойчы. Пасля таго, як за ўтрыманне і адзін раз за час змянення файла. Гэтыя падзеі адбываюцца на працягу кароткага прамежку часу. Каб вырашыць гэтую праблему, сон паміж Апытанне() або ўзяць() выклікі і key.pollEvents() выклік. Напрыклад:

@Override
@SuppressWarnings( "SleepWhileInLoop" )
public void run() {
  setListening( true );

  while( isListening() ) {
    try {
      final WatchKey key = getWatchService().take();
      final Path path = get( key );

     //Prevent receiving two separate ENTRY_MODIFY events: file modified
     //and timestamp updated. Instead, receive one ENTRY_MODIFY event
     //with two counts.
      Thread.sleep( 50 );

      for( final WatchEvent<?> event : key.pollEvents() ) {
        final Path changed = path.resolve( (Path)event.context() );

        if( event.kind() == ENTRY_MODIFY && isListening( changed ) ) {
          System.out.println( "Changed: " + changed );
        }
      }

      if( !key.reset() ) {
        ignore( path );
      }
    } catch( IOException | InterruptedException ex ) {
     //Stop eavesdropping.
      setListening( false );
    }
  }
}

Выклік сон() дапамагае ліквідаваць двайныя званкі. Затрымка, магчыма, прыйдзецца дасягаць трох секунд.

24
дададзена
Хіба не WatchService ключавыя гэтыя падзеі ў фонавым рэжыме? Што б затрымліваючы атрыманне ім рабіць?
дададзена аўтар Sotirios Delimanolis, крыніца
Робячы гэта, вы атрымаеце падзея з лічыльным значэннем 2 замест двух асобных падзей са значэннем лічыльніка 1. Такім чынам, гадам ня трэба выключыць паўтараюцца падзеі ўручную. Я хаджу гэты ва. шчаслівая :)
дададзена аўтар mmdemirbas, крыніца
працаваў як шарм!
дададзена аўтар bullzeye, крыніца
Гэта лепшы адказ, дзякуй за апісанне таго, чаму існуе дзве падзеі.
дададзена аўтар nverbeek, крыніца

У мяне была аналагічная праблема - я выкарыстоўваю WatchService API, каб каталогі ў сінхранізацыі, але заўважыў, што ў шматлікіх выпадках, абнаўлення былі выкананы двойчы. Я, здаецца, вырашана пытанне шляхам праверкі пазнакі на файлах - гэта, здаецца, адсяваць другую аперацыю капіявання. (Прынамсі, у Windows 7 - Я не магу быць упэўнены, што ён будзе карэктна працаваць у іншых аперацыйных сістэмах)

Можа быць, вы маглі б выкарыстоўваць нешта падобнае? Захоўваеце часовую пазнаку з файла і перазагрузіць толькі тады, калі пазнака часу абнаўляецца?

15
дададзена
Што рабіць, калі файл быў зменены два ці больш раз у гэтай групе падзей? Гіпатэтычна вы бы 4, 6, 8 ... і г.д. змяніць падзеі, але значэнне .lastModified() будзе прадстаўляць толькі апошняе падзея.
дададзена аўтар 2rs2ts, крыніца

One of my goto solutions for problems like this is to simply queue up the unique event resources and delay processing for an acceptable amount of time. In this case I maintain a Set that contains every file name derived from each event that arrives. Using a Set<> ensures that duplicates don't get added and, therefore, will only be processed once (per delay period).

Each time an interesting event arrives I add the file name to the Set<> and restart my delay timer. When things settle down and the delay period elapses, I proceed to processing the files.

AddFileToProcess() і processFiles() метады «сінхранізаваныя», каб гарантаваць, што ніякія ConcurrentModificationExceptions ня выкідваюцца.

Гэты спрошчаны/аўтаномны прыклад з'яўляецца вытворных ад Oracle, WatchDir.java </а>:

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.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.WatchEvent;
import java.nio.file.WatchKey;
import java.nio.file.WatchService;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;

public class DirectoryWatcherService implements Runnable {
    @SuppressWarnings("unchecked")
    static  WatchEvent cast(WatchEvent<?> event) {
        return (WatchEvent)event;
    }

    /*
     * Wait this long after an event before processing the files.
     */
    private final int DELAY = 500;

    /*
     * Use a SET to prevent duplicates from being added when multiple events on the 
     * same file arrive in quick succession.
     */
    HashSet filesToReload = new HashSet();

    /*
     * Keep a map that will be used to resolve WatchKeys to the parent directory
     * so that we can resolve the full path to an event file. 
     */
    private final Map keys;

    Timer processDelayTimer = null;

    private volatile Thread server;

    private boolean trace = false;

    private WatchService watcher = null;

    public DirectoryWatcherService(Path dir, boolean recursive) 
        throws IOException {
        this.watcher = FileSystems.getDefault().newWatchService();
        this.keys = new HashMap();

        if (recursive) {
            registerAll(dir);
        } else {
            register(dir);
        }

       //enable trace after initial registration
        this.trace = true;
    }

    private synchronized void addFileToProcess(String filename) {
        boolean alreadyAdded = filesToReload.add(filename) == false;
        System.out.println("Queuing file for processing: " 
            + filename + (alreadyAdded?"(already queued)":""));
        if (processDelayTimer != null) {
            processDelayTimer.cancel();
        }
        processDelayTimer = new Timer();
        processDelayTimer.schedule(new TimerTask() {

            @Override
            public void run() {
                processFiles();
            }
        }, DELAY);
    }

    private synchronized void processFiles() {
        /*
         * Iterate over the set of file to be processed
         */
        for (Iterator it = filesToReload.iterator(); it.hasNext();) {
            String filename = it.next();

            /*
             * Sometimes you just have to do what you have to do...
             */
            System.out.println("Processing file: " + filename);

            /*
             * Remove this file from the set.
             */
            it.remove();
        }
    }

    /**
     * Register the given directory with the WatchService
     */
    private void register(Path dir) throws IOException {
        WatchKey key = dir.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
        if (trace) {
            Path prev = keys.get(key);
            if (prev == null) {
                System.out.format("register: %s\n", dir);
            } else {
                if (!dir.equals(prev)) {
                    System.out.format("update: %s -> %s\n", prev, dir);
                }
            }
        }
        keys.put(key, dir);
    }

    /**
     * Register the given directory, and all its sub-directories, with the
     * WatchService.
     */
    private void registerAll(final Path start) throws IOException {
       //register directory and sub-directories
        Files.walkFileTree(start, new SimpleFileVisitor() {
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
                throws IOException
            {
                if (dir.getFileName().toString().startsWith(".")) {
                    return FileVisitResult.SKIP_SUBTREE;
                }

                register(dir);
                return FileVisitResult.CONTINUE;
            }
        });
    }

    @SuppressWarnings("unchecked")
    @Override
    public void run() {
        Thread thisThread = Thread.currentThread();

        while (server == thisThread) {
            try {
               //wait for key to be signaled
                WatchKey key;
                try {
                    key = watcher.take();
                } catch (InterruptedException x) {
                    return;
                }

                Path dir = keys.get(key);
                if (dir == null) {
                    continue;
                }

                for (WatchEvent<?> event: key.pollEvents()) {
                    WatchEvent.Kind<?> kind = event.kind();

                    if (kind == OVERFLOW) {
                        continue;
                    }

                    if (kind == ENTRY_MODIFY) {

                        WatchEvent ev = (WatchEvent)event;
                        Path name = ev.context();
                        Path child = dir.resolve(name);

                        String filename = child.toAbsolutePath().toString();

                        addFileToProcess(filename);
                    }
                }

                key.reset();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    public void start() {
        server = new Thread(this);
        server.setName("Directory Watcher Service");
        server.start();
    }


    public void stop() {
        Thread moribund = server;
        server = null;
        if (moribund != null) {
            moribund.interrupt();
        }
    }

    public static void main(String[] args) {
        if (args==null || args.length == 0) {
            System.err.println("You need to provide a path to watch!");
            System.exit(-1);
        }

        Path p = Paths.get(args[0]);
        if (!Files.isDirectory(p)) {
            System.err.println(p + " is not a directory!");
            System.exit(-1);
        }

        DirectoryWatcherService watcherService;
        try {
            watcherService = new DirectoryWatcherService(p, true);
            watcherService.start();
        } catch (IOException e) {
            System.err.println(e.getMessage());
        }
    }

}

3
дададзена

Вы ўпэўненыя, што ёсць праблема з jdk7? Гэта дае правільны вынік для мяне (jdk7u15, вокны)

код

import java.io.IOException;
import java.nio.file.*;

public class WatchTest {

    public void watchMyFiles() throws IOException, InterruptedException {
        Path path = Paths.get("c:/temp");
        WatchService watchService = path.getFileSystem().newWatchService();
        path.register(watchService, StandardWatchEventKinds.ENTRY_MODIFY);

        while (true) {
            WatchKey watchKey = watchService.take();//blocks

            for (WatchEvent<?> event : watchKey.pollEvents()) {
                WatchEvent watchEvent = (WatchEvent) event;
                WatchEvent.Kind kind = watchEvent.kind();

                System.out.println(watchEvent.context() + ", count: " +
                        watchEvent.count() + ", event: " + watchEvent.kind());
               //prints (loop on the while twice)
               //servers.cfg, count: 1, event: ENTRY_MODIFY
               //servers.cfg, count: 1, event: ENTRY_MODIFY

                switch (kind.name()) {
                    case "ENTRY_MODIFY":
                        handleModify(watchEvent.context());//reload configuration class
                        break;
                    case "ENTRY_DELETE":
                        handleDelete(watchEvent.context());//do something else
                        break;
                    default:
                        System.out.println("Event not expected " + event.kind().name());
                }
            }

            watchKey.reset();
        }
    }

    private void handleDelete(Path context) {
        System.out.println("handleDelete  " + context.getFileName());
    }

    private void handleModify(Path context) {
        System.out.println("handleModify " + context.getFileName());
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        new WatchTest().watchMyFiles();
    }
}

Выхад, як калі файл цэны ніжэй скапіяваны або адрэдагаваны з дапамогай нататніка.

config.xml, count: 1, event: ENTRY_MODIFY
handleModify config.xml

Vi выкарыстоўвае мноства дадатковых файлаў, і, здаецца, каб абнавіць файл атрыбут некалькі разоў. Notepad ++ робіць роўна ў два разы.

2
дададзена
Так, NVM, вы не глядзіце на яго. Я здзіўлены, што вы атрымаеце толькі адзін ENTRY_MODIFY хоць. Рашэнне Tofarr з'яўляецца тое, што я зрабіў на дадзены момант, але я да гэтага часу даю яму некаторы час для іншых магчымых рашэнняў.
дададзена аўтар Sotirios Delimanolis, крыніца
<�Код> WatchService гэта функцыя Java 7, так што так, у гэтым сэнсе. Калі вы скапіявалі файл, вы павінны ўбачыць адначасова ENTRY_CREATED і ENTRY_MODIFY .
дададзена аўтар Sotirios Delimanolis, крыніца
@ Sotirios Delimanolis: Вы маеце на ўвазе вы атрымаеце стварыць апавяшчэнне, калі назіранне за StandardWatchEventKinds.ENTRY_MODIFY? Рашэнне ад tofarr здаецца добрым рашэннем.
дададзена аўтар Jayan, крыніца
У мяне была такая ж праблема, то я прачытаў каментар Джая ў дачыненні да Notepad ++ абнаўленне файла двойчы атрыбутаў. Праблема не назіральнік, ён быў рэдактарам файла, у маім выпадку.
дададзена аўтар Volceri, крыніца

Вось поўная рэалізацыя з дапамогай Timestamps , каб пазбегнуць стральбы некалькі падзей:

import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;

import static java.nio.file.LinkOption.NOFOLLOW_LINKS;
import static java.nio.file.StandardWatchEventKinds.*;

public abstract class DirectoryWatcher
{
    private WatchService watcher;
    private Map keys;
    private Map fileTimeStamps;
    private boolean recursive;
    private boolean trace = true;

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

    /**
     * Register the given directory with the WatchService
     */
    private void register(Path directory) throws IOException
    {
        WatchKey watchKey = directory.register(watcher, ENTRY_MODIFY, ENTRY_CREATE, ENTRY_DELETE);

        addFileTimeStamps(directory);

        if (trace)
        {
            Path existingFilePath = keys.get(watchKey);
            if (existingFilePath == null)
            {
                System.out.format("register: %s\n", directory);
            } else
            {
                if (!directory.equals(existingFilePath))
                {
                    System.out.format("update: %s -> %s\n", existingFilePath, directory);
                }
            }
        }

        keys.put(watchKey, directory);
    }

    private void addFileTimeStamps(Path directory)
    {
        File[] files = directory.toFile().listFiles();
        if (files != null)
        {
            for (File file : files)
            {
                if (file.isFile())
                {
                    fileTimeStamps.put(file.toPath(), file.lastModified());
                }
            }
        }
    }

    /**
     * Register the given directory, and all its sub-directories, with the
     * WatchService.
     */
    private void registerAll(Path directory) throws IOException
    {
        Files.walkFileTree(directory, new SimpleFileVisitor()
        {
            @Override
            public FileVisitResult preVisitDirectory(Path currentDirectory, BasicFileAttributes attrs)
                    throws IOException
            {
                register(currentDirectory);
                return FileVisitResult.CONTINUE;
            }
        });
    }

    /**
     * Creates a WatchService and registers the given directory
     */
    DirectoryWatcher(Path directory, boolean recursive) throws IOException
    {
        this.watcher = FileSystems.getDefault().newWatchService();
        this.keys = new HashMap<>();
        fileTimeStamps = new HashMap<>();
        this.recursive = recursive;

        if (recursive)
        {
            System.out.format("Scanning %s ...\n", directory);
            registerAll(directory);
            System.out.println("Done.");
        } else
        {
            register(directory);
        }

       //enable trace after initial registration
        this.trace = true;
    }

    /**
     * Process all events for keys queued to the watcher
     */
    void processEvents() throws InterruptedException, IOException
    {
        while (true)
        {
            WatchKey key = watcher.take();

            Path dir = keys.get(key);
            if (dir == null)
            {
                System.err.println("WatchKey not recognized!!");
                continue;
            }

            for (WatchEvent<?> event : key.pollEvents())
            {
                WatchEvent.Kind watchEventKind = event.kind();

               //TBD - provide example of how OVERFLOW event is handled
                if (watchEventKind == OVERFLOW)
                {
                    continue;
                }

               //Context for directory entry event is the file name of entry
                WatchEvent watchEvent = cast(event);
                Path fileName = watchEvent.context();
                Path filePath = dir.resolve(fileName);

                long oldFileModifiedTimeStamp = fileTimeStamps.get(filePath);
                long newFileModifiedTimeStamp = filePath.toFile().lastModified();
                if (newFileModifiedTimeStamp > oldFileModifiedTimeStamp)
                {
                    fileTimeStamps.remove(filePath);
                    onEventOccurred();
                    fileTimeStamps.put(filePath, filePath.toFile().lastModified());
                }

                if (recursive && watchEventKind == ENTRY_CREATE)
                {
                    if (Files.isDirectory(filePath, NOFOLLOW_LINKS))
                    {
                        registerAll(filePath);
                    }
                }

                break;
            }

            boolean valid = key.reset();

            if (!valid)
            {
                keys.remove(key);

                if (keys.isEmpty())
                {
                    break;
                }
            }
        }
    }

    public abstract void onEventOccurred();
}

Пашырыць клас і рэалізаваць onEventOccurred() <�код /> метад.

1
дададзена

Я склаў WatchDir.java і прапановы @ Nilesh ў у Назіраныя класа, які паведаміць яго назіральнікаў адзін раз, калі глядзеў файл зменены.

Я спрабаваў зрабіць гэта як чытаным і па магчымасці кароткім, але ўсё-ткі прызямліўся з больш чым 100 радкоў. Паляпшэння вітаюцца, вядома.

выкарыстанне:

FileChangeNotifier fileReloader = new FileChangeNotifier(File file);
fileReloader.addObserver((Observable obj, Object arg) -> {
    System.out.println("File changed for the " + arg + " time.");
});

См маё рашэнне на GitHub: FileChangeNotifier.java .

1
дададзена
@igracia вы вельмі маеце рацыю, дзякуй! Мой выпадак выкарыстання глядзеў адзін файл, не думаў пра маштабаванасці. Рашэнне FaNaJ ў ніжэй, верагодна, працуе нашмат лепш пры праглядзе вялікай колькасці файлаў.
дададзена аўтар Florian Sesser, крыніца
Я завалены мая рэалізацыя ўжо з ignoreNext сцяг для таго, калі я сам мадыфікаваў файл (крыху хутка і брудна ...). Гэта павінна быць адносна лёгка адмовіцца, што і дадаць Карта Шляхоў да спісу назіральнікаў. Я хутчэй хацеў б не зрабіць гэта самім без прэцэдэнту, хоць, і, успомніўшы, што спроба тэставання гэтага на розных платформах ...
дададзена аўтар Florian Sesser, крыніца
+1 для </назіранага кода>. Адзіная праблема з гэтым рашэннем з'яўляецца тое, што гэта не вельмі маштабуюцца, як вы нерастуе адзін паток для кожнага назіральніка. Гэта шмат, калі вы збіраецеся глядзець некалькі сотняў файлаў!
дададзена аўтар igracia, крыніца
Я думаю, вы маглі б аб'яднаць неяк, і ёсць FileChangeNotifier зарэгістраваць Observable ўяўленне аб змене файла. Проста ў той час як пятлю ў растворы вы паказвалі апавяшчэння назіральнікаў, і ў вас ёсць лепшае з абодвух сьветаў ;-) Выдача выпадковых, залежыць ад платформы сон здаецца вельмі схільныя да шанцу.
дададзена аўтар igracia, крыніца

Я змяніў WatchDir.java атрымаць толькі антрапагенныя мадыфікацыі. Параўноўваючы .lastModified() файла.

long lastModi=0; //above for loop
if(kind==ENTRY_CREATE){
    System.out.format("%s: %s\n", event.kind().name(), child);
}else if(kind==ENTRY_MODIFY){
    if(child.toFile().lastModified() - lastModi > 1000){
        System.out.format("%s: %s\n", event.kind().name(), child);
    }
}else if(kind==ENTRY_DELETE){
    System.out.format("%s: %s\n", event.kind().name(), child);
}
    lastModi=child.toFile().lastModified();
1
дададзена

Неправераная, але, магчыма, гэта будзе працаваць:

AtomicBoolean modifyEventFired = new AtomicBoolean();
modifyEventFired.set(false);

while(true) {
    watchKey = watchService.take();//blocks

    for (WatchEvent<?> event : watchKey.pollEvents()) {
        WatchEvent watchEvent = (WatchEvent) event;
        WatchEvent.Kind kind = watchEvent.kind();

        System.out.println(watchEvent.context() + ", count: "+ watchEvent.count() + ", event: "+ watchEvent.kind());
       //prints (loop on the while twice)
       //servers.cfg, count: 1, event: ENTRY_MODIFY
       //servers.cfg, count: 1, event: ENTRY_MODIFY

        switch(kind.name()) {
            case "ENTRY_MODIFY":
                if(!modifyEventFired.get()){
                   handleModify(watchEvent.context());//reload configuration class
                   modifyEventFired.set(true);                           
                }
                break;
            case "ENTRY_DELETE":
                handleDelete(watchEvent.context());//do something else
                break;              
        }
    }   
    modifyEventFired.set(false);
    watchKey.reset();       
}
0
дададзена
Калі вы пашырыцца ваша для кожнага цыклу, вы можаце атрымаць доступ да метаду памеру() з pollEvents спіс: List <>> падзеі = watchKey.pollEvents (); System.out.println (events.size ()); Ці гэта таксама вяртае 1, ці ж яна сапраўды паказаць 2 падзеі? Колькасць watchEvent павінна быць 2, калі яго дублікат, але ў дадзеным выпадку я думаю, што ваш 2 можа лепш адлюстраваць у спісе.
дададзена аўтар Robert H, крыніца
<�Код> pollEvents (). Памер() гэта адзін, але ўзяць() адбываецца два разы (гэта зноў замыкаецца, чакання). Гэта збівае з толку казаць пра гэта. Ёсць два OS падзеі (напрыклад: змяняць змесціва і змяняць метададзеныя), але толькі адзін чалавек падзея. Я мяркую, што WatchService API бачыць, як два WatchKeys з адным WatchEvent кожны.
дададзена аўтар Sotirios Delimanolis, крыніца
<�Код> для пятля на самай справе толькі адзін раз у цыкле. Цыкл у той час як завесы двойчы з адным падзеяй у watchKey.pollEvents() , так што я не думаю, што гэта будзе працаваць.
дададзена аўтар Sotirios Delimanolis, крыніца
    /**
 * 
 * 
 * in windows os, multiple event will be fired for a file create action
 * this method will combine the event on same file
 * 
 * for example:
 * 
 * pathA -> createEvent -> createEvent
 * pathA -> createEvent + modifyEvent, .... -> modifyEvent
 * pathA -> createEvent + modifyEvent, ...., deleteEvent -> deleteEvent
 * 
 * 
 * 
 * 在windows环境下创建一个文件会产生1个创建事件+多个修改事件, 这个方法用于合并重复事件
 * 合并优先级为 删除 > 更新 > 创建
 * 
 *
 * @param events
 * @return
 */
private List filterEvent(List events) {


   //sorted by event create > modify > delete
    Comparator eventComparator = (eventA, eventB) -> {
        HashMap map = new HashMap<>();
        map.put(StandardWatchEventKinds.ENTRY_CREATE, 0);
        map.put(StandardWatchEventKinds.ENTRY_MODIFY, 1);
        map.put(StandardWatchEventKinds.ENTRY_DELETE, 2);
        return map.get(eventA.kind()) - map.get(eventB.kind());

    };
    events.sort(eventComparator);

    HashMap hashMap = new HashMap<>();
    for (WatchEvent<?> event : events) {
       //if this is multiple event on same path
       //the create event will added first
       //then override by modify event
       //then override by delete event
        hashMap.put(event.context().toString(), event);
    }


    return new ArrayList<>(hashMap.values());


}
0
дададзена

Я паспрабаваў гэта, і гэта выдатна працуе:

import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import static java.nio.file.StandardWatchEventKinds.*;

public class FileWatcher implements Runnable, AutoCloseable {

    private final WatchService service;
    private final Map watchTargets = new HashMap<>();
    private final List fileListeners = new CopyOnWriteArrayList<>();
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock r = lock.readLock();
    private final Lock w = lock.writeLock();
    private final AtomicBoolean running = new AtomicBoolean(false);

    public FileWatcher() throws IOException {
        service = FileSystems.getDefault().newWatchService();
    }

    @Override
    public void run() {
        if (running.compareAndSet(false, true)) {
            while (running.get()) {
                WatchKey key;
                try {
                    key = service.take();
                } catch (Throwable e) {
                    break;
                }
                if (key.isValid()) {
                    r.lock();
                    try {
                        key.pollEvents().stream()
                                .filter(e -> e.kind() != OVERFLOW)
                                .forEach(e -> watchTargets.values().stream()
                                        .filter(t -> t.isInterested(e))
                                        .forEach(t -> fireOnEvent(t.path, e.kind())));
                    } finally {
                        r.unlock();
                    }
                    if (!key.reset()) {
                        break;
                    }
                }
            }
            running.set(false);
        }
    }

    public boolean registerPath(Path path, boolean updateIfExists, WatchEvent.Kind... eventKinds) {
        w.lock();
        try {
            WatchTarget target = watchTargets.get(path);
            if (!updateIfExists && target != null) {
                return false;
            }
            Path parent = path.getParent();
            if (parent != null) {
                if (target == null) {
                    watchTargets.put(path, new WatchTarget(path, eventKinds));
                    parent.register(service, eventKinds);
                } else {
                    target.setEventKinds(eventKinds);
                }
                return true;
            }
        } catch (Throwable e) {
            e.printStackTrace();
        } finally {
            w.unlock();
        }
        return false;
    }

    public void addFileListener(FileListener fileListener) {
        fileListeners.add(fileListener);
    }

    public void removeFileListener(FileListener fileListener) {
        fileListeners.remove(fileListener);
    }

    private void fireOnEvent(Path path, WatchEvent.Kind eventKind) {
        for (FileListener FileListener: fileListeners) {
            fileListener.onEvent(path, eventKind);
        }
    }

    public boolean isRunning() {
        return running.get();
    }

    @Override
    public void close() throws IOException {
        running.set(false);
        w.lock();
        try {
            service.close();
        } finally {
            w.unlock();
        }
    }

    private final class WatchTarget {

        private final Path path;
        private final Path fileName;
        private final Set eventNames = new HashSet<>();
        private final Event lastEvent = new Event();

        private WatchTarget(Path path, WatchEvent.Kind[] eventKinds) {
            this.path = path;
            this.fileName = path.getFileName();
            setEventKinds(eventKinds);
        }

        private void setEventKinds(WatchEvent.Kind[] eventKinds) {
            eventNames.clear();
            for (WatchEvent.Kind k : eventKinds) {
                eventNames.add(k.name());
            }
        }

        private boolean isInterested(WatchEvent e) {
            long now = System.currentTimeMillis();
            String name = e.kind().name();
            if (e.context().equals(fileName) && eventNames.contains(name)) {
                if (lastEvent.name == null || !lastEvent.name.equals(name) || now - lastEvent.when > 100) {
                    lastEvent.name = name;
                    lastEvent.when = now;
                    return true;
                }
            }
            return false;
        }

        @Override
        public int hashCode() {
            return path.hashCode();
        }

        @Override
        public boolean equals(Object obj) {
            return obj == this || obj != null && obj instanceof WatchTarget && Objects.equals(path, ((WatchTarget) obj).path);
        }

    }

    private final class Event {

        private String name;
        private long when;

    }

    public static void main(String[] args) throws IOException, InterruptedException {
        FileWatcher watcher = new FileWatcher();
        if (watcher.registerPath(Paths.get("filename"), false, ENTRY_MODIFY, ENTRY_CREATE, ENTRY_DELETE)) {
            watcher.addFileListener((path, eventKind) -> System.out.println(path + " -> " + eventKind.name()));
            new Thread(watcher).start();
            System.in.read();
        }
        watcher.close();
        System.exit(0);
    }

}

FileListener:

import java.nio.file.Path;
import java.nio.file.WatchEvent;

public interface FileListener {

    void onEvent(Path path, WatchEvent.Kind eventKind);

}
0
дададзена
Ці можаце вы растлумачыць, што робіць гэты код? Ці ёсць <�я> аб'яднаць абодва гэтых ў адно падзея? Ці ёсць выкарыстоўваць рашэнне, пры якім спрацоўвае толькі адзін выпадак?
дададзена аўтар Sotirios Delimanolis, крыніца
OP: Я накшталт не хачу, каб праверыць час паміж падзеямі
дададзена аўтар Florian Sesser, крыніца
@SotiriosDelimanolis ў метадзе isInterested мы ігнаруючы ўзнікненне тых жа падзей, якія адбыліся на працягу 100 мс . але ў некаторых выпадках гэта можа быць небяспечна. Напрыклад, калі два прыкладання змяняюць адзін і той жа файл і дадатак B завяршае яго працу 50 мс пасля чым прымяненне А зрабіў ...
дададзена аўтар FaNaJ, крыніца

Калі вы выкарыстоўваеце RxJava вы можаце выкарыстоўваць throttleLast аператара. У прыведзеным ніжэй прыкладзе толькі апошняе падзея ў 1000 мілісекунд выпраменьваецца для кожнага файла ў каталогу назіранаму.

public class FileUtils {
    private static final long EVENT_DELAY = 1000L;

    public static Observable watch(Path directory, String glob) {
        return Observable.create(subscriber -> {
            final PathMatcher matcher = directory.getFileSystem().getPathMatcher("glob:" + glob);

            WatchService watcher = FileSystems.getDefault().newWatchService();
            subscriber.setCancellable(watcher::close);

            try {
                directory.register(watcher,
                        ENTRY_CREATE,
                        ENTRY_DELETE,
                        ENTRY_MODIFY);
            } catch (IOException e) {
                subscriber.onError(e);
                return;
            }

            while (!subscriber.isDisposed()) {
                WatchKey key;
                try {
                    key = watcher.take();
                } catch (InterruptedException e) {
                    if (subscriber.isDisposed())
                        subscriber.onComplete();
                    else
                        subscriber.onError(e);
                    return;
                }

                for (WatchEvent<?> event : key.pollEvents()) {
                    WatchEvent.Kind<?> kind = event.kind();

                    if (kind != OVERFLOW) {
                        WatchEvent ev = (WatchEvent) event;
                        Path child = directory.resolve(ev.context());

                        if (matcher.matches(child.getFileName()))
                            subscriber.onNext(new FileWatchEvent(kindToType(kind), child));
                    }
                }

                if (!key.reset()) {
                    subscriber.onError(new IOException("Invalid key"));
                    return;
                }
            }
        }).groupBy(FileWatchEvent::getPath).flatMap(o -> o.throttleLast(EVENT_DELAY, TimeUnit.MILLISECONDS));
    }

    private static FileWatchEvent.Type kindToType(WatchEvent.Kind kind) {
        if (StandardWatchEventKinds.ENTRY_CREATE.equals(kind))
            return FileWatchEvent.Type.ADDED;
        else if (StandardWatchEventKinds.ENTRY_MODIFY.equals(kind))
            return FileWatchEvent.Type.MODIFIED;
        else if (StandardWatchEventKinds.ENTRY_DELETE.equals(kind))
            return FileWatchEvent.Type.DELETED;
        throw new RuntimeException("Invalid kind: " + kind);
    }

    public static class FileWatchEvent {
        public enum Type {
            ADDED, DELETED, MODIFIED
        }

        private Type type;
        private Path path;

        public FileWatchEvent(Type type, Path path) {
            this.type = type;
            this.path = path;
        }

        public Type getType() {
            return type;
        }

        public Path getPath() {
            return path;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            FileWatchEvent that = (FileWatchEvent) o;

            if (type != that.type) return false;
            return path != null ? path.equals(that.path) : that.path == null;
        }

        @Override
        public int hashCode() {
            int result = type != null ? type.hashCode() : 0;
            result = 31 * result + (path != null ? path.hashCode() : 0);
            return result;
        }
    }
}
0
дададзена

У мяне была аналагічная праблема. Я ведаю, што гэта позна, але гэта можа дапамагчы камусьці. Мне проста трэба, каб выключыць дублікаты ENTRY_MODIFY . Кожны раз, калі ENTRY_MODIFY спрацоўвае, COUNT() вяртае альбо 2 ці 1. Калі 1, то будзе яшчэ адна падзея з COUNT() 1. Так проста паставіць глабальны лічыльнік, які трымае колькасць вяртаюцца значэнняў і выконваць аперацыі толькі тады, калі лічыльнік становіцца 2. Нешта падобнае можна зрабіць:

WatchEvent event; 
int count = 0;

if(event.count() == 2)
     count = 2;

if(event.count() == 1)
     count++;

if(count == 2){
     //your operations here
     count = 0;
}
0
дададзена
Я зрабіў некаторыя бенчмаркетынгу. На маёй машыне, захаванне файла з 1 вынікаў лініі прыкладна 6ms паміж «дублююць» падзей. Захаванне файла з 10000000 радкоў прыводзіць прыкладна 500мс паміж «дублюючых» падзей.
дададзена аўтар tresf, крыніца
Гэта, здаецца, добра, калі 2 гарантуецца. У маіх тэстах, розныя прыкладання паводзяць сябе па-рознаму. <�Код> CMD і ява трыгера адна падзея на запісы, дзе ў якасці notepad.exe і Notepad ++ трыгер два.
дададзена аўтар tresf, крыніца
Вы маеце рацыю. Гэта не было, што карысна ў некаторых выпадках, так што я ў канчатковым выніку з дапамогай гэта рашэнне ад Dave Jarvis. Можа быць, вы можаце выкарыстоўваць абодва рашэнні ў спалучэнні, калі гэта дапамагае вам.
дададзена аўтар user2729516, крыніца
Паўтараюцца выклікі фактычна залежаць ад асноўнай файлавай сістэмы і прыкладання, якое змяняе змесціва файла. Паводзіны мяняецца ад прыкладання да прыкладання, а таму прабег можа вар'іравацца.
дададзена аўтар user2729516, крыніца

Я вырашыў гэтую праблему, вызначыўшы глабальную лагічную зменную з імем «modifySolver», які ілжывымі па змаўчанні. Вы можаце справіцца з гэтай праблемай, як я пакажу ніжэй:

else if (eventKind.equals (ENTRY_MODIFY))
        {
            if (event.count() == 2)
            {
                getListener(getDirPath(key)).onChange (FileChangeType.MODIFY, file.toString ());
            }
            /*capture first modify event*/
            else if ((event.count() == 1) && (!modifySolver))
            {
                getListener(getDirPath(key)).onChange (FileChangeType.MODIFY, file.toString ());
                modifySolver = true;
            }
            /*discard the second modify event*/
            else if ((event.count() == 1) && (modifySolver))
            {
                modifySolver = false;
            }
        }
0
дададзена