Ako zamknúť súbor v Jave
1. Prehľad
Pri čítaní alebo zápise súborov musíme skontrolovať, či sú k dispozícii správne mechanizmy uzamykania súborov. To zaisťuje integritu údajov v súbežných aplikáciách založených na I / O.
V tomto výučbe sa pozrieme na rôzne prístupy, ako to dosiahnuť pomocou knižnice Java NIO.
2. Úvod do zámkov súborov
Všeobecne existujú dva typy zámkov:
- Exkluzívne zámky - známe tiež ako zámky na zápis
- Zdieľané zámky - označované tiež ako zámky na čítanie
Zjednodušene povedané, exkluzívny zámok zabráni všetkým ostatným operáciám - vrátane čítania -, kým sa operácia zápisu dokončí.
Zdieľaný zámok naopak umožňuje čítať viac ako jeden proces súčasne. Zmyslom zámku na čítanie je zabrániť získaniu zámku na zápis iným procesom. Spravidla by mal byť súbor v konzistentnom stave skutočne čitateľný akýmkoľvek procesom.
V nasledujúcej časti uvidíme, ako Java zvláda tieto typy zámkov.
3. Zámky súborov v prostredí Java
Knižnica Java NIO umožňuje zamykanie súborov na úrovni OS. The zámok () a tryLock () metódy a FileChannel sú na tento účel.
Môžeme vytvoriť FileChannel buď prostredníctvom a FileInputStream, a FileOutputStream, alebo a RandomAccessFile. Všetci traja majú a getChannel () metóda, ktorá vracia a FileChannel.
Prípadne môžeme vytvoriť a FileChannel priamo cez statiku otvorené metóda:
try (FileChannel channel = FileChannel.open (path, openOptions)) {// zapísať do kanála}
Ďalej preskúmame rôzne možnosti získania výlučných a zdieľaných zámkov v prostredí Java. Ak sa chcete dozvedieť viac informácií o kanáloch súborov, pozrite si náš návod Sprievodca Java FileChannel.
4. Exkluzívne zámky
Ako sme sa už dozvedeli, pri zápise do súboru môžeme zabrániť iným procesom v čítaní alebo zápise doň pomocou výlučného zámku.
Získame exkluzívne zámky volaním zámok () alebo tryLock () na FileChannel trieda. Môžeme tiež použiť ich preťažené metódy:
- zámok (dlhá pozícia, veľká veľkosť, booleovský zdieľaný)
- tryLock (dlhá pozícia, veľká veľkosť, booleovský zdieľaný)
V týchto prípadoch zdieľané parameter musí byť nastavený na nepravdivé.
Aby sme získali exkluzívny zámok, musíme použiť zapisovateľný FileChannel. Môžeme to vytvoriť prostredníctvom getChannel () metódy a FileOutputStream alebo a RandomAccessFile. Prípadne, ako už bolo spomenuté, môžeme použiť statický otvorené metóda FileChannel trieda. Všetko, čo potrebujeme, je nastaviť druhý argument na StandardOpenOption.APPEND:
try (FileChannel channel = FileChannel.open (path, StandardOpenOption.APPEND)) {// write to channel}
4.1. Exkluzívne zámky pomocou a FileOutputStream
A FileChannel vytvorené z a FileOutputStream je zapisovatelny. Môžeme teda získať exkluzívny zámok:
try (FileOutputStream fileOutputStream = new FileOutputStream ("/ tmp / testfile.txt"); FileChannel channel = fileOutputStream.getChannel (); FileLock lock = channel.lock ()) {// zapísať do kanála}
Tu, channel.lock () buď zablokuje, kým nezíska zámok, alebo vyvolá výnimku. Napríklad, ak je zadaný región už uzamknutý, znak OverlappingFileLockException je hodená. Úplný zoznam možných výnimiek nájdete na Javadoc.
Neblokujúci zámok môžeme vykonať aj pomocou channel.tryLock (). Ak sa mu nepodarí získať zámok, pretože iný program obsahuje prekrývajúci sa, vráti sa nulový. Ak tak neurobí z iného dôvodu, je vyvolaná príslušná výnimka.
4.2. Exkluzívne zámky pomocou a RandomAccessFile
S RandomAccessFile, musíme nastaviť príznaky na druhom parametri konštruktora.
Tu otvoríme súbor s povoleniami na čítanie a zápis:
try (RandomAccessFile file = new RandomAccessFile ("/ tmp / testfile.txt", "rw"); FileChannel channel = file.getChannel (); FileLock lock = channel.lock ()) {// zapísať do kanála}
Ak otvoríme súbor v režime iba na čítanie a pokúsime sa zapísať na jeho kanál, vyhodí sa a NonWritableChannelException.
4.3. Exkluzívne zámky vyžadujú zapisovateľné FileChannel
Ako už bolo spomenuté, exkluzívne zámky potrebujú zapisovateľný kanál. Preto nemôžeme získať exkluzívny zámok prostredníctvom a FileChannel vytvorené z a FileInputStream:
Cesta cesty = 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}
Vo vyššie uvedenom príklade je zámok () metóda vyhodí a NonWritableChannelException. Je to tak preto, lebo sa dovolávame getChannel na a FileInputStream, ktorý vytvára kanál iba na čítanie.
Tento príklad slúži iba na demonštráciu toho, že nemôžeme zapisovať na nezapisovateľný kanál. V scenári zo skutočného sveta by sme nezachytili a neurobili výnimku.
5. Zdieľané zámky
Pamätajte, že zdieľané zámky sa tiež nazývajú čítať zámky. Preto, aby sme získali zámok na čítanie, musíme použiť čitateľný FileChannel.
Taký FileChannel možno získať zavolaním na getChannel () metóda na a FileInputStream alebo a RandomAccessFile. Ďalšou možnosťou je opäť použitie statickej otvorené metóda FileChannel trieda. V takom prípade sme nastavili druhý argument na StandardOpenOption.READ:
try (FileChannel channel = FileChannel.open (path, StandardOpenOption.READ); FileLock lock = channel.lock (0, Long.MAX_VALUE, true)) {// čítať z kanála}
Tu si treba uvedomiť jednu vec, že sme sa rozhodli uzamknúť celý súbor volaním zámok (0, Long.MAX_VALUE, true). Mohli sme tiež uzamknúť iba konkrétnu oblasť súboru zmenou prvých dvoch parametrov na rôzne hodnoty. Tretí parameter musí byť nastavený na pravda v prípade zdieľaného zámku.
Aby sme to zjednodušili, uzamkneme celý súbor vo všetkých príkladoch uvedených nižšie, nezabúdajte však, že vždy môžeme uzamknúť konkrétnu oblasť súboru.
5.1. Zdieľané zámky pomocou a FileInputStream
A FileChannel získané z a FileInputStream je čitateľný. Môžeme preto získať zdieľaný zámok:
try (FileInputStream fileInputStream = new FileInputStream ("/ tmp / testfile.txt"); FileChannel channel = fileInputStream.getChannel (); FileLock lock = channel.lock (0, Long.MAX_VALUE, true)) {// čítať z kanála }
V úryvku vyššie hovor s zámok () na kanáli uspeje. Je to preto, lebo zdieľaný zámok vyžaduje iba to, aby bol kanál čitateľný. Je to tak v tomto prípade, pretože sme ho vytvorili z a FileInputStream.
5.2. Zdieľané zámky pomocou a RandomAccessFile
Tentokrát môžeme súbor otvoriť iba čítať oprávnenia:
try (RandomAccessFile file = new RandomAccessFile ("/ tmp / testfile.txt", "r"); FileChannel channel = file.getChannel (); FileLock lock = channel.lock (0, Long.MAX_VALUE, true)) {// čítať z kanála}
V tomto príklade sme vytvorili a RandomAccessFile s povoleniami na čítanie. Z neho môžeme vytvoriť čitateľný kanál a vytvoriť tak zdieľaný zámok.
5.3. Zdieľané zámky vyžadujú čitateľné FileChannel
Z tohto dôvodu nemôžeme získať zdieľaný zámok prostredníctvom a FileChannel vytvorené z a FileOutputStream:
Cesta cesty = 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 výnimka}
V tomto príklade je výzva na zámok () sa pokúša získať zdieľaný zámok na kanáli vytvorenom z FileOutputStream. Takýto kanál je určený iba na zápis. Nespĺňa potrebu, aby bol kanál čitateľný. To spustí a NonWritableChannelException.
Tento úryvok má opäť len preukázať, že nemôžeme čítať z nečitateľného kanála.
6. Čo je potrebné zvážiť
V praxi je použitie zámkov súborov ťažké; uzamykacie mechanizmy nie sú prenosné. S týmto budeme musieť vytvoriť našu logiku zamykania.
V systémoch POSIX sú zámky poradenské. Rôzne procesy čítania alebo zápisu do daného súboru sa musia dohodnúť na uzamykacom protokole. Týmto sa zabezpečí integrita súboru. Samotný OS nebude vynucovať žiadne uzamykanie.
V systéme Windows budú zámky exkluzívne, pokiaľ nebude zdieľanie povolené. Diskusia o výhodách alebo nevýhodách mechanizmov špecifických pre OS je mimo rozsahu tohto článku. Pri implementácii uzamykacieho mechanizmu je však dôležité poznať tieto nuansy.
7. Záver
V tejto príručke sme preskúmali niekoľko rôznych možností získavania zámkov súborov v prostredí Java.
Najprv sme začali porozumením dvoch hlavných mechanizmov uzamykania a toho, ako knižnica Java NIO uľahčuje uzamykanie súborov. Potom sme prešli sériou jednoduchých príkladov, ktoré ukazujú, že v našich aplikáciách môžeme získať výlučné a zdieľané zámky. Pozreli sme sa tiež na typy typických výnimiek, s ktorými sa môžeme stretnúť pri práci so zámkami súborov.
Zdrojový kód príkladov je ako vždy k dispozícii na serveri GitHub.