Java (17) - práce se soubory

Velmi častým prostředkem komunikace programu s okolním světem je soubor. Protože je Java koncipována jako platformově nezávislá, musí se vypořádat s různými nástrahami, které rozličné souborové systémy skýtají. Že to není jednoduchý úkol, ale že není neřešitelný, se přesvědčíme při zkoumání javovských prostředků, které jsou pro tento účel k dispozici.

1.9.2005 06:00 | Lukáš Jelínek | přečteno 69227×

Úkol nelehký

Pokud potřebujeme uložit data z programu (nebo je načíst), ve většině případů k tomu použijeme soubor. A přestože soubor samotný se skoro na všech platformách tváří úplně stejně (je to různě dlouhá posloupnost bajtů), způsob práce se liší, a to dost zásadně.

Zajímají nás především dvě nejrozšířenější skupiny souborových systémů:

Speciální situace se pak řeší, pokud se nějaký souborový systém používá z operačního systému, ve kterém je cizí (typicky třeba FAT v Linuxu). Pak platí pravidla, která jsou směsicí výše uvedeného a je třeba si dávat zvlášť velký pozor.

Třída File

V balíku java.io najdeme třídu, která nám poskytne vše potřebné k práci se soubory - je to třída File. Nepředstavuje přímo konkrétní soubor, nýbrž tzv. abstraktní cestu (tedy obecně jakoukoli cestu identifikující nějaký soubor). Může odkazovat na platný soubor, ale také nemusí. Důležité ale je, že je to (podobně jako třeba String) invariant, jak se jednou vytvoří, už nelze změnit.

V řadě tříd ze standardní knihovny Javy najdeme metody, které vyžadují jako svůj argument název souboru. Prakticky ve všech případech lze použít jak textový řetězec (String; to jsme doposud běžně dělali), tak právě instanci třídy File, což je přenositelnější a robustnější řešení, protože můžeme již předem zjistit o daném názvu souboru nějaké informace nebo provést se souborem potřebné operace. Jako příklad mohu uvést třeba konstruktor třídy FileInputStream.

Specifikace abstraktní cesty

Zdůrazňuji, že objekt File může představovat jak soubory (běžné, ale i speciální, třeba soubory zařízení), tak adresáře. Cesta může být absolutní i relativní, může být dokonce i prázdná. Abstraktní cesta se vždy skládá z prefixu (např. označení kořenového adresáře, úvodní označení UNC cesty; u relativních cest prefix samozřejmě chybí) a z posloupnosti názvů jednotlivých adresářových úrovní (samozřejmě včetně případného názvu souboru na konci) oddělených separátorem.

Jak se s cestami pracuje, záleží na nastavení vlastností systému (system properties; někdy později se na ně podíváme důkladněji), a toto nastavení se samozřejmě liší podle platformy. Nejdůležitější je oddělovač názvů v cestě; ve třídě File je určen hodnotou konstanty separatorChar (znakové vyjádření), resp. separator (řetězcové vyjádření). Na unixových systémech je oddělovačem samozřejmě dopředné lomítko, na microsoftích systémech lomítko obrácené.

Když už jsme u těch konstant, třída File obsahuje ještě konstanty pathSeparatorChar a pathSeparator. Tyto konstanty představují oddělovač cest (v případech kdy máme zapsaných několik cest za sebou) a mají hodnotu dvojtečky (unixové systémy), resp. středníku (microsoftí systémy).

Vytvoření instance File

Jak jsem již řekl, jednou vytvořený objekt File už nemůžeme měnit. Má to svoji logiku, protože jestliže řetězec je v Javě neměnný, musí být jeho speciální případ (což abstraktní cesta bezpochyby je) také neměnný.

Objekt File můžeme vytvořit třemi způsoby: názvem souboru, názvem souboru vzhledem k rodiči, a pomocí URI (Uniform Resource Identifier; viz RFC 2396). Pokud ho vytáříme přímo z celé (absolutní nebo relativní) cesty, je situace jednoduchá, řetězec se pouze převede na abstraktní cestu. Pokud je dán rodič (ať už názvem nebo instancí File, a není null), je abstraktní cesta vytvořena jako relativní vůči této rodičovské cestě (adresáři), resp. proti výchozímu adresáři (pokud je rodič prázdná abstraktní cesta). Pokud se instance File vytváří z URI, musí být splněny určité požadavky (schéma musí být "file", cesta nesmí být prázdná atd.).

Separátor v řetězci nemusí odpovídat dané platformě. Pokud je konstruktor schopen ho normalizovat (tzn. převést do správné podoby), zpracuje se cesta bez problémů.

Operace s instancí třídy File

Práce s cestou k souboru

Protože, jak bylo řečeno, je objekt File abstraktní cestou k souboru, můžeme s touto cestou pracovat a získávat různé její varianty. Lze získat celou cestu v různých podobách, části cesty a některé další verze.

Všechny uvedené metody, které vrací cestu nebo její část, mají jako návratovou hodnotu textový řetězec. Kromě nich existují jejich obdoby, které vracejí novou instanci typu File. Jsou to metody getAbsoluteFile(), getCanonicalFile() a getParentFile().

Abstraktní cestu lze také převést (metodou getURL()) na odpovídající URL, anebo na URI (metodou getURI()). Protože však metoda getURL() neumí správně naložit se zakázanými znaky, doporučuje se vždy použít getURI() a získaný objekt převést jeho metodou getURL() na URL (protože URL jsou podmnožinou URI, často se ve skutečnosti ani vnitřně nic převádět nebude).

Zjišťování informací o souboru

Nyní už přejdeme k operacím, které se týkají souboru, na který abstraktní cesta odkazuje. Množina operací není velká, musí být totiž dostatečně přenositelná mezi platformami.

Adresářové informace

Předchozí metody se týkaly všech souborů bez rozdílu, tedy včetně adresářů. Pro adresáře samotné máme k dispozici speciální sadu metod, které využijeme pro přístup k souborům v těchto adresářích:

O filtraci souborů

Výše uvedené metody provádějí filtraci souborů podle poskytnutého rozhraní. To je typická ukázka toho, jak se v Javě podobné věci řeší - existuje mnoho a mnoho objektových metod, které jako mají parametr nějaké jednoduché rozhraní, obsahující třeba jen jedinou metodu. Chování je pak plně v režii implementace tohoto rozhraní.

Pokud budeme implementaci potřebovat jen v jednom jediném případě, s výhodou využijeme možnosti vytvořit anonymní třídu přímo na daném místě. Viz příklad:

File f = new File("/home/username/docs");       // vybereme adresář

String list[] = f.list(new FilenameFilter() {
    boolean accept(File dir, String name) {
        return name.endsWith(".pdf");           // jen názvy *.pdf
    }
});

Arrays.sort(list);    // abecední seřazení

for (int i=0; i<list.length; i++) {
    System.out.println(list[i]);
}

Uvedený příklad vypíše v abecedním pořadí všechny soubory z daného adresáře, jejichž název končí na .pdf (jsou to tedy dokumenty formátu PDF).

Manipulační operace

Zatím jsme o souborech pouze zjišťovali různé informace. S tím si rozhodně nelze vystačit, občas musíme také někde něco změnit. Třída File nabízí několik manipulačních operací, tak se na ně podívejme:

Následující příklad ukáže několik operací provedených na souboru identifikovaném abstraktní cestou. Nejdříve se adresářová cesta převede do kanonického tvaru, potom se daný adresář vytvoří, v něm se založí nový soubor a ten se nakonec přejmenuje. Všimněte si, že některé operace vyžadují ošetření výjimky IOException.

File dir = new File("../user2/texty");
try {
    dir = dir.getCanonicalFile();
} catch (IOException e) {
    System.err.println("Nelze ziskat kanonicky tvar: " + dir);
    System.exit(1);
}

if (!dir.makedir()) {
    System.err.println("Nelze vytvorit adresar " + dir);
    System.exit(2);
}

File f = new File(dir, "test.txt");
try {
    if (!f.createNewFile()) {
        System.err.println("Soubor " + f + " jiz existuje");
    }
} catch (IOException e) {
    System.err.println("Nelze vytvorit soubor " + f);
    System.exit(3);
}

File f2 = new File(f.getParent(), "test2.txt");
if (!f.renameTo(f2)) {
    System.err.println("Nelze prejmenovat soubor na " + f2);
    System.exit(4);
}

Dočasné soubory

Občas potřebujeme uložit nějaká data do dočasného souboru, abychom je použili později. Lze to samozřejmě udělat ručně, tedy zvolením nějakého umístění souboru, to ale nebude přenositelné. Ve třídě File máme k dispozici dvě statické metody, kterými tento problém snadno vyřešíme:

Vytvořený soubor se nemaže automaticky při skončení programu. Pokud to potřebujeme (což je skoro vždy), použijeme metodu deleteOnExit().

Od souborů k síti

Kapitola (pravda, poněkud "výčtově" orientovaná) o práci se soubory tímto dospěla ke svému konci. Ke konci ale nedospěly I/O operace, protože Java v této oblasti nabízí velmi mnoho. Příště přijde řada na komunikaci po síti, která je v dnešní době stejně důležitá jako práce se soubory.

Online verze článku: http://www.linuxsoft.cz/article.php?id_article=866