Cara Mengunci Fail di Java

1. Gambaran keseluruhan

Semasa membaca atau menulis fail, kita perlu memastikan mekanisme penguncian fail yang betul ada. Ini memastikan integriti data dalam aplikasi berasaskan I / O serentak.

Dalam tutorial ini, kita akan melihat pelbagai pendekatan untuk mencapainya menggunakan perpustakaan Java NIO .

2. Pengenalan Kunci Fail

Secara umum, terdapat dua jenis kunci :

    • Kunci eksklusif - juga dikenali sebagai kunci tulis
    • Kunci dikongsi - juga disebut sebagai kunci baca

Ringkasnya, kunci eksklusif menghalang semua operasi lain - termasuk pembacaan - semasa operasi menulis selesai.

Sebaliknya, kunci bersama membolehkan membaca lebih daripada satu proses pada masa yang sama. Maksud kunci baca adalah untuk mengelakkan pemerolehan kunci tulis dengan proses lain. Biasanya, fail dalam keadaan yang konsisten semestinya dapat dibaca oleh proses apa pun.

Di bahagian seterusnya, kita akan melihat bagaimana Java menangani kunci jenis ini.

3. Kunci Fail di Java

Perpustakaan Java NIO membolehkan mengunci fail di peringkat OS. Kaedah kunci () dan tryLock () dari FileChannel adalah untuk tujuan tersebut.

Kita dapat membuat FileChannel melalui FileInputStream , FileOutputStream , atau RandomAccessFile . Ketiganya mempunyai kaedah getChannel () yang mengembalikan FileChannel .

Sebagai alternatif, kita boleh membuat FileChannel secara langsung melalui kaedah terbuka statik :

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

Seterusnya, kami akan mengkaji pelbagai pilihan untuk mendapatkan kunci eksklusif dan dikongsi di Java. Untuk mengetahui lebih lanjut mengenai saluran fail, lihat panduan kami untuk tutorial Java FileChannel.

4. Kunci Eksklusif

Seperti yang telah kita pelajari, semasa menulis ke fail, kita dapat mengelakkan proses lain membaca atau menulisnya dengan menggunakan kunci eksklusif .

Kami mendapat kunci eksklusif dengan memanggil kunci () atau tryLock () pada kelas FileChannel . Kami juga boleh menggunakan kaedah mereka yang terlalu banyak:

  • kunci (kedudukan panjang, ukuran panjang, boolean dikongsi)
  • tryLock (kedudukan panjang, ukuran panjang, boolean dikongsi)

Dalam kes tersebut, parameter bersama harus ditetapkan ke false .

Untuk mendapatkan kunci eksklusif, kita mesti menggunakan FileChannel yang boleh ditulis . Kita boleh membuatnya melalui kaedah getChannel () dari FileOutputStream atau RandomAccessFile . Sebagai alternatif, seperti yang telah disebutkan sebelumnya, kita dapat menggunakan kaedah terbuka statik dari kelas FileChannel . Yang kita perlukan adalah menetapkan argumen kedua ke StandardOpenOption.APPEND :

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

4.1. Kunci Eksklusif Menggunakan FileOutputStream

A FileChannel dihasilkan daripada FileOutputStream boleh ditulis. Oleh itu, kami dapat memperoleh kunci eksklusif:

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

Di sini, channel.lock () akan menyekat sehingga mendapat kunci, atau akan membuang pengecualian. Sebagai contoh, jika kawasan yang ditentukan sudah terkunci, OverlappingFileLockException akan dilemparkan. Lihat Javadoc untuk senarai lengkap kemungkinan pengecualian.

Kita juga boleh melakukan kunci tanpa penyekat menggunakan channel.tryLock () . Sekiranya gagal mendapatkan kunci kerana program lain mempunyai program yang bertindih, maka ia akan menjadi nol . Sekiranya gagal melakukannya kerana alasan lain, maka pengecualian yang tepat akan dilemparkan.

4.2. Kunci Eksklusif Menggunakan RandomAccessFile

Dengan RandomAccessFile , kita perlu menetapkan bendera pada parameter kedua pembina.

Di sini, kita akan membuka fail dengan kebenaran membaca dan menulis:

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

Sekiranya kita membuka fail dalam mod baca sahaja dan cuba menulis ke salurannya, ia akan membuang NonWritableChannelException .

4.3. Kunci Eksklusif Memerlukan Saluran Fail yang Boleh Ditulis

Seperti yang disebutkan sebelumnya, kunci eksklusif memerlukan saluran yang dapat ditulis. Oleh itu, kami tidak dapat memperoleh kunci eksklusif melalui FileChannel yang dibuat dari 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 }

Dalam contoh di atas, kaedah kunci () akan membuang NonWritableChannelException . Ini kerana kami menggunakan getChannel pada FileInputStream , yang membuat saluran baca sahaja.

Contoh ini hanya untuk menunjukkan bahawa kita tidak dapat menulis ke saluran yang tidak dapat ditulis. Dalam senario dunia nyata, kita tidak akan menangkap dan mengubah pengecualian.

5. Kunci Berkongsi

Ingat, kunci bersama juga dipanggil kunci baca . Oleh itu, untuk mendapatkan kunci baca, kita mesti menggunakan FileChannel yang boleh dibaca .

Seperti FileChannel boleh diperolehi dengan menghubungi getChannel () kaedah pada FileInputStream atau RandomAccessFile . Sekali lagi, pilihan lain adalah menggunakan kaedah terbuka statik dari kelas FileChannel . Dalam kes itu, kami menetapkan argumen kedua ke StandardOpenOption.READ :

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

Satu perkara yang perlu diperhatikan di sini ialah kita memilih untuk mengunci keseluruhan fail dengan memanggil kunci (0, Long.MAX_VALUE, true) . Kami juga hanya dapat mengunci kawasan tertentu fail dengan mengubah dua parameter pertama ke nilai yang berbeza. Parameter ketiga harus ditetapkan menjadi benar sekiranya kunci bersama.

Untuk memastikan semuanya mudah, kami akan mengunci keseluruhan fail dalam semua contoh di bawah, tetapi perlu diingat bahawa kami selalu dapat mengunci kawasan tertentu fail.

5.1. Kunci Berkongsi Menggunakan FileInputStream

A FileChannel diperolehi daripada FileInputStream boleh dibaca. Oleh itu, kita boleh mendapatkan kunci bersama:

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 }

Dalam coretan di atas, panggilan untuk mengunci () pada saluran akan berjaya. Ini kerana kunci yang dikongsi hanya memerlukan saluran itu dapat dibaca. Ini berlaku di sini sejak kami membuatnya dari FileInputStream .

5.2. Kunci Berkongsi Menggunakan RandomAccessFile

Kali ini, kita dapat membuka fail dengan izin baca :

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 }

Dalam contoh ini, kami membuat RandomAccessFile dengan kebenaran membaca. Kami dapat membuat saluran yang dapat dibaca daripadanya dan, dengan itu, membuat kunci bersama.

5.3. Kunci Berkongsi Memerlukan Saluran Fail yang Boleh Dibaca

Oleh sebab itu, kami tidak dapat memperoleh kunci bersama melalui FileChannel yang dibuat dari 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.

Pada Windows, kunci akan eksklusif kecuali perkongsian dibenarkan. Membincangkan faedah atau kekurangan mekanisme khusus OS adalah di luar skop artikel ini. Namun, penting untuk mengetahui nuansa ini ketika menerapkan mekanisme penguncian.

7. Kesimpulannya

Dalam tutorial ini, kami telah mengkaji beberapa pilihan yang berbeza untuk mendapatkan kunci fail di Java.

Pertama, kami bermula dengan memahami dua mekanisme penguncian utama dan bagaimana perpustakaan Java NIO memudahkan penguncian fail. Kemudian kami melalui beberapa contoh ringkas yang menunjukkan kami dapat memperoleh kunci eksklusif dan bersama dalam aplikasi kami. Kami juga melihat jenis pengecualian khas yang mungkin kami hadapi ketika bekerja dengan kunci fail.

Seperti biasa, kod sumber untuk contoh boleh didapati di GitHub.