Лучший способ проверить, не существует ли файла в каталоге

#java

Вопрос:

Я пытаюсь выполнить задание согласования, в котором мне нужно прочитать входную строку из (огромного) файла и проверить, есть ли в определенном каталоге файл, имя файла которого начинается с входной строки, или нет. Каталог(ы) является/являются массивными и может содержать до 800 000 файлов. Учитывая это, я использую File[] directoryListing только один раз, а затем повторяю строки входного файла против него. Вот код:

 public class CheckForFile {   public static void main(String[] args) {  String dirPath = "W:\ThePath\ToThe\Directory";  File dir = new File(dirPath);  if (dir.isDirectory() amp;amp; dir.exists()) {  File[] directoryListing = dir.listFiles();  String line;  try (BufferedReader br = new BufferedReader(new FileReader("input.csv"))) {  while ((line = br.readLine()) != null) {  String[] strArray = line.split(",", -1);  System.out.println(fileExistsInDir(strArray[0], directoryListing));  }   } catch (IOException e) {  e.printStackTrace();  }  } else {  System.out.println(dirPath   " - is not a Directory.. ");  }   }   public static String fileExistsInDir(String fileNameStartsWithStr, File[] directoryListing) {  if (directoryListing != null) {  for (File child : directoryListing) {  if (child.isFile()) {  if (child.getName().startsWith(fileNameStartsWithStr)) {  } else {  return "file DO NOT exist for - "   fileNameStartsWithStr;  }  }  }  } else {  System.out.println("directoryListing empty...");  }  return null;  }  }  

Я проверяю, какой файл отсутствует, по записи в файле input.csv. Приведенный выше код работает нормально. Но поскольку это путь к удаленному общему каталогу Windows, для получения списка файлов требуется некоторое время. Есть ли лучший способ сделать все это? Вопрос здесь в том, чтобы посмотреть file DO NOT exist for - foobar в консоли. Любые предложения/указания будут высоко оценены.

Обновление: В каталоге отсутствует всего несколько файлов, но они перечислены в файле input.csv.

Постановка проблемы: Необходимо выяснить, какие файлы отсутствуют в каталоге в этом списке.

Update2: В соответствии с решением DuncG я попробовал это:

 public static void main(String[] args) throws IOException {    Instant start = Instant.now();    try (Streamlt;Stringgt; lines = Files.lines(Paths.get("input.csv"))) {  Setlt;Stringgt; scanfor = lines  .map(line -gt; line.split(",", -1))  .filter(line -gt; line.length gt; 0)  .map(line -gt; line[0])  .filter(s -gt; s.length() gt; 0)  .collect(Collectors.toSet());  System.out.println("scanfor size: "   scanfor.size());   try (Streamlt;Pathgt; scan = Files.find(Paths.get("W:\ThePath\ToThe\Directory"),  1, (p, a) -gt; !a.isDirectory() amp;amp; !matches(p.getFileName().toString(), scanfor))) {   long count = scan.peek(System.out::println).count();   System.out.println("Number of files not matching CSV criteria: "   count);  }  }    Instant finish = Instant.now();  long timeElapsed = Duration.between(start, finish).toMinutes();    System.out.println("Total time consumed :"  timeElapsed );  }   private static boolean matches(String fn, Setlt;Stringgt; scanfor) {  // Search by exact match in the set  for (int i = fn.length(); i gt;= 1; i--) {  if (scanfor.contains(fn.substring(0, i)))  return true;  }  return false;  }  

I started with half of the file records. My console is showing: scanfor size: 472948 . Now it seems to be running forever and its been more than 30 minutes I am waiting for it to end. What might be wrong here?

Update3:

I tried this as suggested by DuncG:

 public static void main(String[] args) throws IOException {   Instant start = Instant.now();   System.out.println(start);   try (Streamlt;Stringgt; lines = Files.lines(Paths.get("input.csv"))) {  Setlt;Stringgt; scanfor = lines.map(line -gt; line.split(",", -1)).filter(line -gt; line.length gt; 0)  .map(line -gt; line[0]).filter(s -gt; s.length() gt; 0).collect(Collectors.toSet());   IntSummaryStatistics stats = scanfor.stream().mapToInt(String::length).summaryStatistics();  System.out.println("scanfor stats: "   stats);   Path out = Paths.get("app.log");   try (BufferedWriter os = Files.newBufferedWriter(out, StandardCharsets.UTF_8, StandardOpenOption.WRITE);  Streamlt;Pathgt; scan = Files.find(  Paths.get("W:\ThePath\ToThe\Directory"), 1,  (p, a) -gt; !a.isDirectory() amp;amp; !matches(p.getFileName().toString(), scanfor, stats))) {   scan.map(Path::toString).forEach(s -gt; write(os, s));  }  System.out.println("saved as: "   out);  }   Instant finish = Instant.now();   System.out.println(finish);   long timeElapsed = Duration.between(start, finish).toMinutes();   System.out.println("Total time consumed in Minutes :"   timeElapsed);   }   private static void write(BufferedWriter wr, String s) {  try {  wr.write(s);  wr.newLine();  } catch (IOException e) {  throw new UncheckedIOException(e);  }  }   private static boolean matches(String fn, Setlt;Stringgt; scanfor, IntSummaryStatistics stats) {  // Can search by exact match in the set knowing the smallest/largest string of  // scanfor  for (int i = stats.getMin(), max = Math.min(fn.length(), stats.getMax()); i lt;= max; i  ) {  if (scanfor.contains(fn.substring(0, i)))  return true;  }  return false;  }  

И получил следующий результат:

 2021-10-31T18:13:02.733379900Z scanfor stats: IntSummaryStatistics{count=472948, sum=17972024, min=38, average=38.000000, max=38} saved as: app.log 2021-10-31T18:53:39.232551600Z Total time consumed in Minutes :40  

Не так много пользы по сравнению с обновлением 2. Прошло почти столько же времени.

Комментарии:

1. @FedericoklezCulloca учитывая, что для проверки требуется много файлов, это не кажется хорошим решением, так как для этого требуется вызов списка файлов для каждой строки, которую необходимо проверить.

2. Справедливо. Закрытое голосование отменено.

3. Или получить все файлы в списке строк, а затем выполнить поиск в нем будет лучшей идеей? Думаю, да. Я думаю, это будет лучшей идеей. Не так ли? Только сейчас мне пришла в голову эта идея.!

4. Насколько велик CSV-файл?

5. Csv содержит от 800000 до 1,5 миллионов записей/строк.

Ответ №1:

Классы ввода-вывода файлов не очень быстры при сканировании огромных файловых систем. Вызов dir.listFiles() , как вы заметили, очень медленный, потому что он проверяет каждое имя в каталоге и создает массив из 800 000 элементов. Пакет Files NIO намного лучше справляется с большим потоком каталогов, так как такие вызовы, как Files.find возврат, очень быстро возвращают результаты при выборе файлов или папок.

Итак: если CSV — файл имеет управляемый размер для загрузки за один шаг, вы можете сначала загрузить строки соответствия в набор, а затем выполнить сканирование каталога (глубина=1), чтобы захватить все файлы-простой предикат в find пропусках мимо каталогов и проверяет совпадения в CSV.

 try(var lines = Files.lines(csv)) {  Setlt;Stringgt; scanfor = lines.map(line -gt; line.split(",", -1))  .filter(line -gt; line.length gt; 0)  .map(line -gt; line[0])  .filter(s -gt; s.length() gt; 0)  .collect(Collectors.toSet());   IntSummaryStatistics stats = scanfor.stream().mapToInt(String::length).summaryStatistics();  System.out.println("scanfor stats: " stats);   try(var os = Files.newBufferedWriter(out);  var scan = Files.find(dir, 1, (p,a) -gt; !a.isDirectory() amp;amp; !matches(p.getFileName().toString(), scanfor, stats))) {   scan.map(Path::toString).forEach(s -gt; write(os, s));  }  System.out.println("saved as: " out); } private static void write(BufferedWriter wr, String s) {  try  {  wr.write(s);  wr.newLine();  } catch (IOException e) {  throw new UncheckedIOException(e);  } } private static boolean matches(String fn, Setlt;Stringgt; scanfor, IntSummaryStatistics stats) {  // Can search by exact match in the set knowing the smallest/largest string of scanfor  for (int i = stats.getMin(), max = Math.min(fn.length(), stats.getMax()); i lt;= max ; i  ) {  if (scanfor.contains(fn.substring(0, i)))  return true;  }  return false; }  

РЕДАКТИРОВАТЬ Я только что перечитал ваш вопрос, он изначально нашел совпадения. Вы можете выбрать поиск файлов, которые соответствуют или не соответствуют критериям CSV с matches !matches предикатом или find в нем.

Комментарии:

1. Я попробовал @DuncG. Пожалуйста, ознакомьтесь с моим обновлением 2 в оригинальном посте. Для 200 файлов это работает как ракета. Но для примерно 470 тысяч файлов это займет целую вечность.

2. Если он пишет много строк: поскольку консоль Windows работает очень медленно, попробуйте поменять System.out::println для записи строки в какой-либо выходной файл (или используйте перенаправление java Blah gt; file.out в командной строке, чтобы исключить проблему скорости консоли.

3. Я изменил совпадения, чтобы уменьшить количество сравнений и выходных данных в файл, но если это не поможет, вам следует вернуться к своему оригиналу.

4. Я добавил результат в качестве обновления 3, @DuncG.

5. Каковы сроки поиска каталога scanfor vs и сколько времени занимает исходный список файлов?

Ответ №2:

Я использовал FileVisitor , как показано ниже, чтобы вернуть несколько List файлов:

 public static Listlt;Stringgt; getFilesList() {    String dirPath = "W:\ThePath\ToThe\Directory";    Listlt;Stringgt; filesList = new ArrayListlt;Stringgt;();   FileVisitorlt;Pathgt; simpleFileVisitor = new SimpleFileVisitorlt;Pathgt;() {   @Override  public FileVisitResult visitFile(Path visitedFile, BasicFileAttributes fileAttributes) throws IOException {  filesList.add(visitedFile.getFileName().toString());  return FileVisitResult.CONTINUE;  }  };  FileSystem fileSystem = FileSystems.getDefault();  Path rootPath = fileSystem.getPath(dirPath);  try {  Files.walkFileTree(rootPath, simpleFileVisitor);  } catch (IOException ioe) {  ioe.printStackTrace();  }  return filesList; }  

И я получил список обратно за 34.x минуты.