Назад Зміст Вперед

11.10 Операції з файловою системою із використанням інтерфейсу Path, класів File, Files, Paths.

Використання інтерфейсу Path

Java 7 представляє нову абстракцію для шляху, а саме інтерфейс Path. Він використовується в нових функціях і API, по всьому NIO.2. Об'єкт шляху містить імена каталогів та файлів, які складають повний шлях до файлу / каталогу, представленого об'єктом Path; Path містить методи для вилучення елементів шляху, маніпуляцій з ними і їх додавання.

Нижче наведено приклад коду, для виконання на Unux-системі, але користувачі Windows можуть розкоментувати один рядок і закоментувати інший, для виконання прикладу на своїх машинах (див. Коментарі в коді). Існування відповідних файлів і катологів (test і testfile.txt) в файлової системі не обов'язково. 
У цьому прикладі створюється об'єкт Path і витягується основна інформація, пов'язана з ним:
package test; 
 import java.nio.file.*; 
 public class Test1 { 
    public static void main(String[] args) { 
       // Створення об'єкта Path через виклик статичного методу get() класу Paths 
        Path testFilePath = Paths.get("/home/heorhi/testfile.txt"); 
         
        //Приклад рядка створення об'єкта Path шляху для запуску в Windows
        //Path testFilePath = Paths.get("D:\\test\\testfile.txt"); 
 
        //Виведення інформації про файл
        System.out.println("Printing file information: "); 
        System.out.println("\t file name: " + testFilePath.getFileName()); 
        System.out.println("\t root of the path: " + testFilePath.getRoot()); 
        System.out.println("\t parent of the target: " 
                + testFilePath.getParent()); 
 
        //Вивід елементів шляху
        System.out.println("Printing elements of the path: "); 
        for (Path element : testFilePath) { 
            System.out.println("\t path element: " + element); 
        }      }  } 
Вивід такий:
Printing file information:  
     file name: testfile.txt 
     root of the path: / 
     parent of the target: /home/heorhi 
Printing elements of the path:  
     path element: home 
     path element: heorhi 
     path element: testfile.txt 
Пояснення:
  • Спочатку створюється об'єкт класу Path, з використанням методу get () класу. Даний метод приймає рядок, що містить шлях.
  • Зверніть увагу на використання символу '\' в Paths.get ( "D: \\ test \\ testfile.txt") для Windows-систем. Без нього '\ t' буде інтерпретуватися як символ табуляції, що призведе до java.nio.file.InvalidPathException при запуску програми, тому що символи табуляції не можуть міститися в шляхах.
  • Потім відбувається вилучення імені файлу з використанням методу getFilename () об'єкта Path
  • Далі використовується метод getRoot () для отримання кореневого елемента об'єкта Path і метод getParent () для отримання батьківської директорії цільового файлу.
  • Наприкінці даного прикладу відбувається обхід елементів шляху за допомогою циклу foreach. Як альтернативу можна використовувати звичайний цикл і методи getNameCount () (для отримання числа елементів в дорозі) і getName (index) (для отримання елемента за індексом).
Перейдемо до іншого прикладу який включає отримання абсолютного шляху від відносного шляху і нормалізацію шляху:
package test; 
 
import java.io.IOException; 
import java.nio.file.*; 
 
class Test2 { 
    public static void main(String[] args) throws IOException { 
        Path testFilePath = Paths.get("./Test"); 
         
        //Приклад шляху в Windows 
        //Path testFilePath = Paths.get(".\\Test"); 
         
        System.out.println("The file name is: " + testFilePath.getFileName()); 
        System.out.println("It's URI is: " + testFilePath.toUri()); 
        System.out.println("It's absolute path is: " 
                + testFilePath.toAbsolutePath()); 
        System.out.println("It's normalized path is: " 
                + testFilePath.normalize()); 
 
        //// Отримання іншого об'єкта рядка за нормалізованим відносним шляхом 
        Path testPathNormalized = Paths 
                .get(testFilePath.normalize().toString()); 
        System.out.println("It's normalized absolute path is: " 
                + testPathNormalized.toAbsolutePath()); 
        System.out.println("It's normalized real path is: " 
                + testFilePath.toRealPath(LinkOption.NOFOLLOW_LINKS)); 
    } 
} 
Пояснення до коду:
  • Метод toUri () повертає URI (шлях який може бути відкритий з браузера).
  • Метод toAbsolutePath () повертає абсолютний шлях від даного відносного шляху. У разі, якщо був введений абсолютний шлях, метод поверне його ж.
  • Метод normalize () виконує нормалізацію шляху, іншими словами видаляє непотрібні символи (такі як "." І "..") з об'єкта Path.
  • Метод toRealPath () повертає абсолютний шлях від отриманого шляху (як toAbsolutePath ()) і нормалізує його (як normalize ()). Крім того, якщо всі параметри обрані правильно, то він може навіть працювати з символьними посиланнями. Однак, для цього методу необхідно, щоб кінцевий файл / каталог існував в файлової системі (це не є обов'язковою умовою для інших методів Path).

Приклад виведення при виконанні даного коду (файл Test не повинен існувати в файлової системі для аналогічного висновку):
The file name is: Test 
It's URI is: file:///home/heorhi/workspace/OCPJP/./Test 
It's absolute path is: /home/heorhi/workspace/OCPJP/./Test 
It's normalized path is: Test 
It's normalized absolute path is: /home/heorhi/workspace/OCPJP/Test 
Exception in thread "main" java.nio.file.NoSuchFileException: /home/heorhi/workspace/OCPJP/Test 
    at sun.nio.fs.UnixException.translateToIOException(UnixException.java:86) 
    at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:102) 
    at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:107) 
    at sun.nio.fs.UnixPath.toRealPath(UnixPath.java:876) 
    at test.Test2.main(Test2.java:23
(Користувачі Windows-систем отримають sun.nio.fs.WindowsException.translateToIOException замість sun.nio.fs.UnixException.translateToIOException і т. Д. І т.п.)
Приклад виведення при виконанні даного коду (файл Test повинен існувати в файловій системі для аналогічного висновку):
The file name is: Test 
It's URI is: file:///home/heorhi/workspace/OCPJP/./Test/ 
It's absolute path is: /home/heorhi/workspace/OCPJP/./Test 
It's normalized path is: Test 
It's normalized absolute path is: /home/heorhi/workspace/OCPJP/Test 
It's normalized real path is: /home/heorhi/workspace/OCPJP/Test 
Інтерфейс Path містить два методи для порівняння об'єктів Path: equals () and compareTo (). Метод equals () порівнює шляхи і повертає Boolean. Метод compareTo () порівнює шляхи посимвольний і повертає: 0, якщо шляху рівні; негативне ціле значення, якщо шлях в об'єкті викликає метод лексикографічно менше шляху в об'єкті, переданому в якості параметра; натуральне значення в протилежному випадку.
Приклад коду:
package test; 
 import java.nio.file.*; 
 class Test3 { 
    public static void main(String[] args) { 
        Path path1 = Paths.get("Test"); 
        Path path2 = Paths.get("/home/heorhi/workspace/OCPJP/Test"); 
        System.out.println("(path1.compareTo(path2) == 0) is: " 
                + (path1.compareTo(path2) == 0)); 
        System.out.println("path1.equals(path2) is: " + path1.equals(path2)); 
        System.out.println("path2.equals(path1.toAbsolutePath()) is " 
                + path2.equals(path1.toAbsolutePath())); 
         
        System.out.println(path1.toAbsolutePath()); 
    } 
} 
ОстаннійSystem.out.println є підказкою — він виводить шлях,який повинен бути переданий вpath2 для аналогічного виведення. Виведення таке:
(path1.compareTo(path2) == 0) is: false 
path1.equals(path2) is: false 
path2.equals(path1.toAbsolutePath()) is true 
/home/heorhi/workspace/OCPJP/Test 

Використання класу Files

Розглянемо клас Files (введений в Java 7, знаходиться в пакеті java.nio.file), який можна використовувати для виконання різних операцій з файлами та каталогами. Files є службовим класом, це означає, що це final-клас з private-конструктором і містить тільки статичні методи. В цьому класі знаходиться безліч методів для виконання різних дій. Розглянемо деякі з них.

Вище був показаний приклад коду, в якому з'ясовувалося, чи вказують два шляхи на один файл. Існує спосіб перевірити це за допомогою методу isSameFile () з класу Files:
package test; 
import java.io.IOException;  import java.nio.file.*;   class Test4 {      public static void main(String[] args) throws IOException {          Path path1 = Paths.get("Test");          Path path2 = Paths.get("/home/heorhi/workspace/OCPJP/Test");            System.out.println("Files.isSameFile(path1, path2) is: "                  + Files.isSameFile(path1, path2));      }  } 
Оскільки тут порівнюються файли, а не шляхи, то існування відповідного файлу обов'язково (див. підказку для шляху в попередньому прикладі) інакше буде отримана помилка java.nio.file.NoSuchFileException.
У разі, якщо файл за вказаною адресою існує, отримаємо такий висновок:
Files.isSameFile(path1, path2) is: true 
Можна визначити, маємо ми справу з файлом або Директорією (папкою) за допомогою методу isDirectory () класу Files і перевірити їх існування за допомогою методу exists ():
package test; 
 import java.nio.file.*; 
 class Test5 { 
    public static void main(String[] args) { 
        //Проверка для файла 
        Path path = Paths.get("/home/heorhi/workspace/OCPJP/src/test/Test5.java"); 
         
        //Перевірка для директорії  
        //Path path = Paths.get("/home/heorhi/workspace/OCPJP/src/test"); 
         
        if (Files.exists(path, LinkOption.NOFOLLOW_LINKS)) { 
            System.out.println("The file/directory " + path.getFileName() 
                    + " exists"); 
            if (Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS)) { 
                System.out.println(path.getFileName() + " is a directory"); 
            } else { 
                System.out.println(path.getFileName() + " is a file"); 
            } 
        } else { 
            System.out.println("The file/directory " + path.getFileName() + " does not exist"); 
        } 
    } 
} 
Висновок повинен вийде приблизно такий (вибирайте шляху відповідно до вашої ОС і розташуванням файлів):
The file/directory Test5.java exists 
Test5.java is a file 
Цікавий висновок можна отримати, якщо написати Path path = Paths.get("/"); для Unix-систем абоPath path = Paths.get("С:\\"); для Windows-систем, тобто якщо передати в якості параметра ім'я кореневого каталогу:
The file/directory null exists 
null is a directory 
Для корневого каталогу path.getFileName() повертається null.
Клас Files містить методи isReadable (), isWriteable () і isExecutable () для перевірки можливості читання, записи і виконання файлів:
package test; 
 import java.nio.file.*; 
 class Test6 { 
    public static void main(String[] args) { 
        Path path = Paths.get("Вставьте сюда путь к какому-либо файлу"); 
        System.out.printf("Readable: %b, Writable: %b, Executable: %b ", 
                Files.isReadable(path), Files.isWritable(path), 
                Files.isExecutable(path)); 
    } 
} 
Метод getAttribute () дозволяє отримати властивості (атрибути) файлу. Метод приймає змінне число параметрів: перший - об'єкт Path; другий - ім'я атрибута; далі від нуля до декількох значень LinkOption (це enum):
package test; 
 import java.io.IOException; 
 import java.nio.file.*; 
 class Test7 { 
    public static void main(String[] args) { 
        Path path = Paths.get("Вставьте сюда путь к какому-либо файлу"); 
        try { 
            Object object = Files.getAttribute(path, "creationTime"); 
            System.out.println("Creation time: " + object); 
             
            //Здесь указан третий параметр 
            object = Files.getAttribute(path, "lastModifiedTime", 
                    LinkOption.NOFOLLOW_LINKS); 
            System.out.println("Last modified time: " + object); 
 
            object = Files.getAttribute(path, "size"); 
            System.out.println("Size: " + object); 
 
            object = Files.getAttribute(path, "isDirectory"); 
            System.out.println("isDirectory: " + object); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
} 

Копіювання файлів

Тепер розглянемо копіювання файлу / ДИРЕКТОРІЇ. Для цього використовуємо метод Files.copy (). Сигнатура даного методу:
Path copy(Path source, Path target, CopyOption. . . options)
Перший параметр - шлях до вихідного файлу, другий - шлях до того файлу, що буде створено в результаті копіювання (включаючи ім'я нового файлу), далі можна задати параметри копіювання, а можна і не поставити, як в прикладі нижче:
package test; 
 import java.io.IOException; 
 import java.nio.file.*; 
 
public class Test8 { 
    public static void main(String[] args) { 
        Path pathSource = Paths.get("Вставте сюди шлях до файлу / директорії для копіювання"); 
        Path pathDestination = Paths.get("Вставте сюди шлях для нового файлу / директорії"); 
        try { 
            Files.copy(pathSource, pathDestination); 
            System.out.println("Source file copied successfully"); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
} 
Після першого запуску все повинно відпрацювати коректно для коректних шляхів. Але якщо даний код без змін скомпілювати і виконати повторно, то буде отримано виключенняjava.nio.file.FileAlreadyExistsException. Воно пов'язане з тим, що цільовий файл вже існує. Для уникнення таких проблем можна вказати, щоб в разі його існування його перезаписувати. Для цього треба трохи змінити один рядок коду:
Files.copy(pathSource, pathDestination, StandardCopyOption.REPLACE_EXISTING); 
Слід зазначити, що при копіюванні директор не будуть копіюватися містяться в ній файли і директорії. Це виглядає безглуздо - нижче буде показано, як це виправити.

Переміщення файлу

Метод для переміщення файлу дуже схожий на метод для копіювання:
Path move(Path source, Path target, CopyOption. . . options)
Значення переданих параметрів збігаються за змістом. Приклад коду відрізняється від попереднього мінімально:
package test; 
import java.io.IOException; 
import java.nio.file.Files; 
import java.nio.file.Path; 
import java.nio.file.Paths; 
import java.nio.file.StandardCopyOption; 
 
public class Test9 { 
    public static void main(String[] args) { 
        Path pathSource = Paths.get("Вставте сюди шлях до файлу / директорії, який треба перемістити"); 
        Path pathDestination = Paths.get("Вставте сюди шлях для нового місця розташування файлу / директорії"); 
        try { 
            Files.move(pathSource, pathDestination, StandardCopyOption.REPLACE_EXISTING); 
            System.out.println("Source file copied successfully"); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
} 
Якщо при копіюванні директорії містяться в ній файли і директорії не копіювались, то при її переміщенні, в разі відсутності помилок, переміщається і весь вміст.

Видалення файлу

Подивимося на приклад коду:

package test; 
import java.io.IOException; 
import java.nio.file.*; 
 
public class Test10 { 
    public static void main(String[] args) { 
        Path pathSource = Paths.get("Вставте сюди шлях до файлу / директорії для видалення"); 
        try { 
            Files.delete(pathSource); 
            System.out.println("File deleted successfully"); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
} 
Кілька моментів, які необхідно пам'ятати щодо методу Files.delete ():
  • У разі видалення каталогу, необхідно, щоб він був порожнім, інакше буде отримано виключення(java.nio.file.DirectoryNotEmptyException)
  • Якщо передати в даний метод символьне посилання, то буде вилучено посилання, а не цільовий файл.
  • Для даного методу необхідно, щоб файл існував, інакше буде отримано виключенняjava.nio.file.NoSuchFileException)
  • Якщо необхідно ігнорувати дані випадки, то краще підійде метод deleteIfExists (), який видаляє файл в разі його існування і не кидає такий виняток при його відсутності.

Обхід дерева файлів

При роботі з файлової системою може виникнути необхідність обходу дерева файлів, наприклад при пошуку файлу або копіюванні каталогу з усім його вмістом. Клас Files містить два методи, що дозволяють обходити дерево файлів. Їх сигнатури наведені нижче:
Path walkFileTree(Path start, FileVisitor<!--? super Path--> visitor) 
Path walkFileTree(Path start, Set<filevisitoption> options, int maxDepth, FileVisitor<!--? super Path--> visitor) 
Обидва методи приймають шлях, з якого почнеться обхід дерева і екземпляр типу FileVisitor, який буде визначати поведінку при обході дерева. Другий метод має два додаткові параметри: Set, що містить опції обходу, і максимальну глибину. Максимальна глибина визначає, наскільки рівнів каталогів відбуватиметься обхід. Якщо в її якості вказати 0, то буде розглядатися тільки вказаний файл, а якщо вказати MAX_VALUE, то будуть пройдені всі підкаталоги.
FileVisitor — це інтерфейс, який містить наступні методи:
  • FileVisitResult preVisitDirectory(T dir, BasicFileAttributesattrs) — виконується перед доступом до елементів каталогу.
  • FileVisitResult visitFile(T file, BasicFileAttributes attrs) — виконується при доступі до файлу.
  • FileVisitResult postVisitDirectory(T dir, IOException exc) — виконується, коли всі елементи директорії пройдені.
  • FileVisitResult visitFileFailed(T file, IOException exc) — виконується, якщо до файлу немає доступу.
Вам необхідно реалізувати інтерфейс FileVisitor, щоб передати відповідний об'єкт в метод walkFileTree (). Але якщо необхідності реалізовувати всі чотири методи цього інтерфейсу немає, то можна просто розширити реалізацію класу SimpleFileVisitor, перевизначивши лише необхідні методи.
Приклад:
package test; 
import java.io.IOException; 
import java.nio.file.*; 
import java.nio.file.attribute.BasicFileAttributes; 
 
class MyFileVisitor extends SimpleFileVisitor { 
    public FileVisitResult visitFile(Path path, 
            BasicFileAttributes fileAttributes) { 
        System.out.println("file name:" + path.getFileName()); 
        return FileVisitResult.CONTINUE; 
    } 
 
    public FileVisitResult preVisitDirectory(Path path, 
            BasicFileAttributes fileAttributes) { 
        System.out.println("Directory name:" + path); 
        return FileVisitResult.CONTINUE; 
    } 
} 
 
public class Test11 { 
    public static void main(String[] args) { 
 
        Path pathSource = Paths.get("Введіть сюди шлях до будь-якого каталогу, який містить інші каталоги та файли"); 
        try { 
            Files.walkFileTree(pathSource, new MyFileVisitor()); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
} 
При виконанні даного коду будуть виведені всі вкладені каталоги і файли за вказаним шляхом. Ось що слід зрозуміти:

  • Оголошується клас MyFileVisitor, успадкований від SimpleFileVisitor, в якому перевизначені два методи: visitFile () (для виведення імені файлу) і preVisitDirectory () (для виведення імені директорії).
  • Викликається walkFileTree () в який передається об'єкт MyFileVisitor.
  • Метод walkFileTree () починає виконання з переданого в нього каталогу. При цьому викликається метод visitFile () при кожному проході файлу, preVisitDirectory () - перед переглядом елементів директорії, postVisitDirectory () - після перегляду елементів директорії, visitFileFailed () - в разі відсутності доступу до файлу / директорії.
  • З цих чотирьох методів були перевизначені тільки два для виведення імен каталогів і файлів.
  • Можна контролювати потік обходу за допомогою повертаються цими методами значень (enum FileVisitResult). Їх чотири:
  1. CONTINUE: вказує на те, що обхід дерева слід продовжити.
  2. TERMINATE: вказує, що обхід потрібно негайно припинити.
  3. SKIP_SUBTREE: вказує, що підкаталоги повинні бути пропущені для обходу.
  4. SKIP_SIBLINGS: вказує на те, що обхід повинен бути зупинений в поточному каталозі і каталогах одного рівня з ним. Якщо це значення повертається з preVisitDirectory (), то вкладені файли / каталоги не обходяться і postVisitDirectory () не спрацьовує. Якщо це значення повертається з visitFile (), то інші файли каталогу не обходяться. Якщо він повертається з postVisitDirectory (), то інші каталоги того ж рівня не будуть обходитися.
Приклад:
package test; 
import java.io.IOException; 
import java.nio.file.*; 
import java.nio.file.attribute.*; 
 
class MyFileCopyVisitor extends SimpleFileVisitor { 
    private Path source, destination; 
 
    public MyFileCopyVisitor(Path s, Path d) { 
        source = s; 
        destination = d; 
    } 
 
    public FileVisitResult visitFile(Path path, 
            BasicFileAttributes fileAttributes) { 
        Path newd = destination.resolve(source.relativize(path)); 
        try { 
            Files.copy(path, newd, StandardCopyOption.REPLACE_EXISTING); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
        return FileVisitResult.CONTINUE; 
    } 
 
    public FileVisitResult preVisitDirectory(Path path, 
            BasicFileAttributes fileAttributes) { 
        Path newd = destination.resolve(source.relativize(path)); 
        try { 
            Files.copy(path, newd, StandardCopyOption.REPLACE_EXISTING); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
        return FileVisitResult.CONTINUE; 
    } 
} 
 
public class Test12 { 
    public static void main(String[] args) { 
        Path pathSource = Paths.get("Вставте сюди шлях до бажано не порожній директорії для копіювання"); 
        Path pathDestination = Paths.get("Вставте сюди шлях до нового положення директорії"); 
        try { 
            Files.walkFileTree(pathSource, new MyFileCopyVisitor(pathSource,    pathDestination)); 
            System.out.println("Files copied successfully!"); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
    } 
} 
У методі preVisitDirectory () відбувається копіювання відвідуваного каталогу і аналогічно копіюється файл в метод visitFile (). Щоб отримати новий шлях призначення, використовується метод relativize () з класу Path.

P.S. Не плутайте File з Files, Path з Paths, і FileSystem c FileSystems. File старий клас (Java 4), а Files був введений в Java 7. Інтерфейс Path є шлях до файлу / каталогу і визначає корисний список методів, а клас Paths є службовим класом, який надає два методи для отримання об'єкта типу Path. Клас FileSystems надає список фабричних методів для отримання класу FileSystem, в той час як FileSystem надає набір методів, для отримання інформації про файлову систему.
.