LINUXSOFT.cz
Username: Password:     
    CZ UK PL

> 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 | read 69235×

DISCUSSION   

Ú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ů:

  • systémy unixového typu - je jich celá řada (UFS, Ext2, ReiserFS atd.), pro všechny z nich je typické, že celý systém tvoří jediný adresářový strom, jako oddělovač jednotlivých úrovní se používá běžné (dopředné) lomítko, rozlišují se velká a malá písmena, téměř všechny znaky ASCII jsou platné a pro ukládání názvů lze obecně používat různé kódové stránky.
  • systémy firmy Microsoft (FAT a NTFS) - rozlišují se jednotlivá logická zařízení ("písmena disků"), oddělovačem je zpětné lomítko, velikost písmen se nerozlišuje, okruh platných znaků je poměrně malý (i když to některé systémové funkce řádně nekontrolují a lze tak na disk propašovat i soubory s neplatnými názvy, které už pak nelze odstranit), kódování se liší podle jednotlivých systémů (NTFS používá Unicode, FAT potom staré kódové stránky, např. pro češtinu CP 852). Situaci ještě komplikuje používání názvů souborů UNC.

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.

  • getPath() - vrátí (normalizovanou) abstraktní cestu v podobě, jak byla zadána při vytváření objektu (tedy absolutní zůstane absolutní atd.). Stejný efekt má i metoda toString().
  • getAbsolutePath() - vrátí cestu převedenou do absolutního tvaru. Mechanismus případného převodu z relativní cesty na absolutní je platformově závislý, většinou se ale jako báze použije domovský adresář uživatele.
  • getCanonicalPath() - vrací kanonický tvar cesty. V praxi to znamená, že se pokusí cestu maximálně "vyhodnotit", zpracovat. Odstraní všechny označení stejného nebo nadřazeného adresáře (tečku a dvě tečky), zpracuje symbolické odkazy atd.. Chování je silně platformově závislé a liší se podle toho, zda cesta (nebo její části) existuje či nikoli.
  • getName() - získá z cesty pouhý název souboru (bez adresářové cesty).
  • getParent() - vrací rodičovský adresář souboru. Vychází se pouze z cesty, soubor ani rodičovský adresář nemusí existovat.
  • isAbsolute() - zjistí, zda je cesta absolutní.
  • compareTo() - lexikograficky porovná tuto abstraktní cestu s jinou (ani jedna nemusí existovat). Porovnávání je platformově závislé, podle systému se použije rozlišení malých/velkých písmen. Podobně pracuje metoda equals(), která pouze zjišťuje, zda jsou abstraktní cesty totožné.

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.

  • exists() - základní věc: zjištění, zda vůbec soubor existuje. Pokud se chystáme dělat s ním nějaké další věci, je dobré zavolat nejprve tuto metodu a ověřit si jeho přítomnost.
  • isFile(), isDirectory() - pomocí těchto metod poznáme, zda se jedná o "normální" soubor nebo o adresář. Opět je to platformově závislé, např. symbolický odkaz na soubor se tváří jako běžný soubor. Platí ale, že jakékoli soubory/adresáře vytvořené z Javy zcela jistě projdou správně těmito testy.
  • canRead(), canWrite() - dozvíme se, zda můžeme číst či zapisovat do daného souboru. Pokud byl ale přístup odepřen, už se nedozvíme proč. To je daň za přenositelnost, nemáme možnost zjišťovat třeba přístupová práva.
  • isHidden() - zjišťuje, zda je soubor označen jako "skrytý". V unixových systémech za skryté soubory považuje ty, jejich název začíná tečkou, ve Windows pak soubory s nastaveným atributem "hidden".
  • length() - zjistí velikost souboru. Protože vrací hodnotu typu long, není problém ani s opravdu velkými soubory.
  • lastModified() - jediný časový údaj, který můžeme o souboru zjistit, je čas poslední modifikace. Ne všechny souborové systémy poskytují další časové informace, proto je to takto omezeno. Navíc v praxi je to právě ten nejpotřebnější údaj, podle něhož můžeme např. zjišťovat, že někdo změnil konfigurační soubor.

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:

  • list() - nejjednodušší varianta. Prostě vrátí pole obsahující seznam všech souborů v daném adresáři - položky tohoto pole budou textové řetězce s názvy souborů. Pořadí souborů není definováno, může být libovolné (závisí na implementaci; v Linuxu budou soubory pravděpodobně uspořádány tak, jak jsou zaznamenány v adresáři). To samozřejmě není problém, protože si soubory můžeme (ať už podle názvu nebo jinak) seřadit podle potřeby.
  • listFiles() - dělá přesně totéž co list(), ale místo pole textových řetězců vrací pole objektů File, tedy abstraktních cest. Zda použijeme tuto nebo přechozí metodu, záleží na konkrétní situaci.
  • list(FilenameFilter f) - modifikace metody list() s tím, že předem vybíráme jen některé soubory. Které to budou, to určí implementace rozhraní FilenameFilter. O filtraci ještě bude řeč.
  • listFiles(FilenameFilter f) - opět metoda vracející pole objektů File, tentokrát s filtrací (viz výše).
  • listFiles(FileFilter f) - další modifikace, ale s jiným typem filtru.
  • listRoots() - v souborových systémech unixovského typu je hierarchie přísně stromová, vždy máme jediný kořen. V jiných systémech to ale platit nemusí (a také neplatí), proto je třeba mít možnost dostat se ke všem dostupným kořenům - a to zajišťuje právě tato statická metoda. Vrací pole všech kořenů adresářových stromů, které jsou v danou chvíli k dispozici.

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:

  • renameTo(File f) - metoda přejmenuje soubor podle zadání. Všimněte si, že se jako parametr zadává jiná instance objektu File. Jak jsme si již řekli, instance File je neměnná, proto i po úspěšném přejmenování souboru zůstane tak, jak je (bude obsahovat původní cestu k souboru). Naopak nové jméno souboru bude odpovídat zadané instanci, o čemž se můžeme přesvědčit tak, že zavoláme metodu exists(). Chování je silně platformově závislé, nemůžeme spoléhat, že metoda bude dělat vždy to, co dělala na některé platformě. Návratovou hodnotu je třeba vždy testovat.
  • delete() - pokusí se smazat soubor. Pokud to jde, smaže ho. Neprázdné adresáře mazat nelze, musí se nejdřív explicitně vyprázdnit.
  • deleteOnExit() - zajímavá metoda, naplánuje smazání souboru při ukončování programu. Zafunguje pouze při čistém ukončení programu, tedy ne při "sestřelení" (na Linuxu signálem SIGKILL) nebo při zavolání metody System.halt(). Metoda se používá pro automatické mazání dočasných souborů (viz níže). Pozor - naplánované smazání už nejde zrušit!
  • createNewFile() - vytvoří nový prázdný soubor. Metoda nemá příliš velké využití, ale někdy se hodí.
  • mkdir(), mkdirs() - dvojice metod pro vytváření adresářů. Liší se pouze tím, že ta první vytvoří pouze ten jediný adresář, na který odkazuje instance File, kdežto ta druhá vytvoří, pokud je třeba, i všechny nadřazené adresáře.
  • setLastModified(long time) - změní časový údaj o poslední změně souboru. Někdy se to může hodit.
  • setReadOnly() - nastaví, že soubor bude pouze ke čtení. Nepříjemné je, že to je pouze jednosměrná operace a v Javě nemáme prostředky, jak to vrátit zpět.

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:

  • createTempFile(String prefix, String suffix) - vytvoří dočasný soubor s daným prefixem (min. 3 znaky dlouhým) a danou koncovkou (může být null, pak se použije .tmp). Soubor vytvoří v adresáři pro dočasné soubory (např. /tmp), vrací instanci třídy File.
  • createTempFile(String prefix, String suffix, File directory) - od předchozí metody se liší tím, že umožňuje specifikovat adresář pro uložení souboru (pokud je null, chování této metody je shodné s chováním té předchozí).

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.

 

DISCUSSION

For this item is no comments.

Add comment is possible for logged registered users.
> Search Software
> Search Google
1. Pacman linux
Download: 4881x
2. FreeBSD
Download: 9068x
3. PCLinuxOS-2010
Download: 8565x
4. alcolix
Download: 10950x
5. Onebase Linux
Download: 9662x
6. Novell Linux Desktop
Download: 0x
7. KateOS
Download: 6249x

1. xinetd
Download: 2414x
2. RDGS
Download: 937x
3. spkg
Download: 4762x
4. LinPacker
Download: 9969x
5. VFU File Manager
Download: 3199x
6. LeftHand Mała Księgowość
Download: 7204x
7. MISU pyFotoResize
Download: 2813x
8. Lefthand CRM
Download: 3564x
9. MetadataExtractor
Download: 0x
10. RCP100
Download: 3124x
11. Predaj softveru
Download: 0x
12. MSH Free Autoresponder
Download: 0x
©Pavel Kysilka - 2003-2024 | mailatlinuxsoft.cz | Design: www.megadesign.cz