Как да заключите файл в Java

1. Общ преглед

Когато четем или пишем файлове, трябва да се уверим, че има правилни механизми за заключване на файлове. Това гарантира целостта на данните в едновременни I / O базирани приложения.

В този урок ще разгледаме различни подходи за постигане на това с помощта на библиотеката Java NIO .

2. Въведение в File Locks

Като цяло има два вида брави :

    • Ексклузивни брави - известни също като брави за запис
    • Споделени брави - наричани още брави за четене

Казано по-просто, изключителното заключване предотвратява всички други операции - включително четене - докато операцията по запис завършва.

За разлика от това, споделеното заключване позволява едновременно четене на повече от един процес. Смисълът на заключването за четене е да се предотврати придобиването на заключване за запис от друг процес. Обикновено файлът в последователно състояние наистина трябва да се чете от всеки процес.

В следващия раздел ще видим как Java се справя с тези видове брави.

3. Заключване на файлове в Java

Библиотеката Java NIO позволява заключване на файлове на ниво ОС. Методите lock () и tryLock () на FileChannel са за тази цел.

Можем да създадем FileChannel чрез FileInputStream , FileOutputStream или RandomAccessFile . И трите имат метод getChannel () , който връща FileChannel .

Като алтернатива можем да създадем FileChannel директно чрез статичния отворен метод:

try (FileChannel channel = FileChannel.open(path, openOptions)) { // write to the channel }

След това ще разгледаме различни опции за получаване на изключителни и споделени ключалки в Java. За да научите повече за файловите канали, разгледайте нашето Ръководство за Java FileChannel урок.

4. Ексклузивни брави

Както вече разбрахме, докато пишем във файл, можем да предотвратим четенето или писането на други процеси с помощта на изключителна ключалка .

Получаваме ексклузивни ключалки, като извикваме lock () или tryLock () в класа FileChannel . Можем да използваме и техните претоварени методи:

  • заключване (дълга позиция, дълъг размер, булево споделено)
  • tryLock (дълга позиция, дълъг размер, булево споделено)

В тези случаи споделеният параметър трябва да бъде зададен на false .

За да получим изключителна ключалка, трябва да използваме записваем FileChannel . Можем да го създадем чрез методите getChannel () на FileOutputStream или RandomAccessFile . Като алтернатива, както споменахме по-горе, можем да използваме статичния отворен метод на класа FileChannel . Всичко, от което се нуждаем, е да зададем втория аргумент на StandardOpenOption.APPEND :

try (FileChannel channel = FileChannel.open(path, StandardOpenOption.APPEND)) { // write to channel }

4.1. Ексклузивни брави с помощта на FileOutputStream

А FileChannel създадена от FileOutputStream да се записва. Следователно можем да придобием изключителна ключалка:

try (FileOutputStream fileOutputStream = new FileOutputStream("/tmp/testfile.txt"); FileChannel channel = fileOutputStream.getChannel(); FileLock lock = channel.lock()) { // write to the channel }

Тук channel.lock () или ще блокира, докато не получи заключване, или ще изведе изключение. Например, ако посоченият регион вече е заключен, се хвърля OverlappingFileLockException . Вижте Javadoc за пълен списък на възможните изключения.

Можем също така да извършим блокиране без блокиране с помощта на channel.tryLock () . Ако не успее да получи заключване, защото друга програма съдържа припокриваща се, тя връща null . Ако не успее да го направи по някаква друга причина, тогава се извежда подходящо изключение.

4.2. Ексклузивни ключалки, използващи RandomAccessFile

С RandomAccessFile трябва да зададем флагове на втория параметър на конструктора.

Тук ще отворим файла с разрешения за четене и запис:

try (RandomAccessFile file = new RandomAccessFile("/tmp/testfile.txt", "rw"); FileChannel channel = file.getChannel(); FileLock lock = channel.lock()) { // write to the channel } 

Ако отворим файла в режим само за четене и се опитаме да напишем в неговия канал, той ще хвърли NonWritableChannelException .

4.3. Изключителните ключалки изискват записваем файлов канал

Както споменахме по-горе, изключителните брави се нуждаят от записваем канал. Следователно не можем да получим ексклузивно заключване чрез FileChannel, създаден от FileInputStream :

Path path = Files.createTempFile("foo","txt"); Logger log = LoggerFactory.getLogger(this.getClass()); try (FileInputStream fis = new FileInputStream(path.toFile()); FileLock lock = fis.getChannel().lock()) { // unreachable code } catch (NonWritableChannelException e) { // handle exception }

В горния пример методът lock () ще изхвърли NonWritableChannelException . Всъщност това е така, защото извикваме getChannel във FileInputStream , който създава канал само за четене.

Този пример е само за да демонстрира, че не можем да пишем в канал, който не може да се записва. В реален сценарий не бихме могли да хванем и възстановим изключението.

5. Споделени брави

Не забравяйте, че споделените ключалки се наричат ​​още ключалки за четене . Следователно, за да получим заключване за четене, трябва да използваме четим FileChannel .

Такъв FileChannel може да бъде получен чрез извикване на метода getChannel () в FileInputStream или RandomAccessFile . Отново, друга опция е да се използва статичният отворен метод на класа FileChannel . В този случай задаваме втория аргумент на StandardOpenOption.READ :

try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ); FileLock lock = channel.lock(0, Long.MAX_VALUE, true)) { // read from the channel }

One thing to note here is that we chose to lock the entire file by calling lock(0, Long.MAX_VALUE, true). We could also have locked only a specific region of the file by changing the first two parameters to different values. The third parameter has to be set to true in the case of a shared lock.

To keep things simple, we’ll be locking the whole file in all the examples below, but keep in mind we can always lock a specific region of a file.

5.1. Shared Locks Using a FileInputStream

A FileChannel obtained from a FileInputStream is readable. We can, therefore, get a shared lock:

try (FileInputStream fileInputStream = new FileInputStream("/tmp/testfile.txt"); FileChannel channel = fileInputStream.getChannel(); FileLock lock = channel.lock(0, Long.MAX_VALUE, true)) { // read from the channel }

In the snippet above, the call to lock() on the channel will succeed. That's because a shared lock only requires that the channel is readable. It's the case here since we created it from a FileInputStream.

5.2. Shared Locks Using a RandomAccessFile

This time, we can open the file with just read permissions:

try (RandomAccessFile file = new RandomAccessFile("/tmp/testfile.txt", "r"); FileChannel channel = file.getChannel(); FileLock lock = channel.lock(0, Long.MAX_VALUE, true)) { // read from the channel }

In this example, we created a RandomAccessFile with read permissions. We can create a readable channel from it and, thus, create a shared lock.

5.3. Shared Locks Require a Readable FileChannel

For that reason, we can't acquire a shared lock through a FileChannel created from a FileOutputStream:

Path path = Files.createTempFile("foo","txt"); try (FileOutputStream fis = new FileOutputStream(path.toFile()); FileLock lock = fis.getChannel().lock(0, Long.MAX_VALUE, true)) { // unreachable code } catch (NonWritableChannelException e) { // handle exception } 

In this example, the call to lock() tries to get a shared lock on a channel created from a FileOutputStream. Such a channel is write-only. It doesn't fulfil the need that the channel has to be readable. This will trigger a NonWritableChannelException.

Again, this snippet is just to demonstrate that we can't read from a non-readable channel.

6. Things to Consider

In practice, using file locks is difficult; the locking mechanisms aren’t portable. We’ll need to craft our locking logic with this in mind.

In POSIX systems, locks are advisory. Different processes reading or writing to a given file must agree on a locking protocol. This will ensure the file’s integrity. The OS itself won’t enforce any locking.

В Windows заключванията ще бъдат изключителни, освен ако не е разрешено споделянето. Обсъждането на предимствата или недостатъците на специфичните за ОС механизми е извън обхвата на тази статия. И все пак е важно да знаете тези нюанси, когато прилагате заключващ механизъм.

7. Заключение

В този урок разгледахме няколко различни опции за получаване на заключвания на файлове в Java.

Първо, започнахме с разбирането на двата основни механизма за заключване и как библиотеката Java NIO улеснява заключването на файлове. След това преминахме през поредица от прости примери, показващи, че можем да получим изключителни и споделени ключалки в нашите приложения. Разгледахме и типовете типични изключения, които може да срещнем при работа с файлови ключалки.

Както винаги, изходният код за примерите е достъпен в GitHub.