Отделен:
RandomAccessFile
RandomAccessFile се използва за файлове състоящи се от записи с известна дължина, така че може да се придвижвате от някакъв запис на друг чрез seek( ), да го четете и презаписване. Записите не трябва непременно да са с еднаква дължина; само трябва да се знае колко са големи и къде във файла се намират.
Отначало е малко трудно да се повярва че RandomAccessFile не е част от InputStream или OutputStream йерархията. Той няма отношения с тези йерархии освен че се случва да използва DataInput и DataOutput интерфейсите (които също са реализирани от DataInputStream и DataOutputStream). Даже не използва нищо от функционалността на InputStream или OutputStream класовете – това е напълно отделен клас, написан от нулата, с изцяло собствени (най-вече native) методи. Причината за това може да е че RandomAccessFile има изоснови различно от другите IO типове поведение, доколкото може да се движите напред-назад във файла. Във всеки случай, той стои самостоятелно, като директен наследник на Object.
Основно, RandomAccessFile работи подобно на DataInputStream заедно с DataOutputStream и методите getFilePointer( ) за да намери къде сте във файла, seek( ) за придвижване към нова точка от файла, length( ) за определяне на максималната дължина на файла. Освен това конструкторите изискват втори аргументи (като fopen( ) в C) показващ дали четете (“r”) или четете и пишете (“rw”). Няма поддръжка за файлове само за четене, което означава, че RandomAccessFile би могъл да работи добре ако беше наследен от DataInputStream.
Още по-разочароващото е, че лесно може да се въобразим произволен достъп в различни потоци, като ByteArrayInputStream например, но съответните методи са налични само в RandomAccessFile, който работи само с файлове. BufferedInputStream не позволява да mark( ) мястото (чиято стойност се пази във вътрешна променлива) и reset( ) към това място, но това е ограничено и не много полезно.
Класът File
Класът File има измамно име – може да помислите, че е за файлове, но не е. Той може да представи или име на конкретен файл или имена на множество от файлове в директория. Ако това е множество от файлове, може да питате за него с метода list( ), а той връща масив от String. Има смисъл да се връща масив наместо някоя колекция, цащото броят на елементите е фиксиран, а ако искате друга директория просто създавате друг File обект. Фактически “FilePath” щеше да бъде по-добро име. Тази секция привежда пример с пълно използване на класа, включително асоциирания FilenameFilter interface.
A directory lister
Да предположим, че трябва списък на файловете в директория. Обектът File може да го направи по два начина. Ако извикате list( ) без аргументи, ще получите пълен списък на имената съдържани във File обекта. Обаче ако искате списък с ограничения, например на всички файлове с разширение .java, използвате “директорен филтър,” което е клас, казващ как да се изберат за изобразяване имената във File обектите.
Ето сорса на примера: (Виж стр. 89 при проблеми с пускането на програмата.)
//: c10:DirList.java
// Листинг на директорията
package c10;
import java.io.*;
public class DirList {
public static void main(String[] args) {
try {
File path = new File(".");
String[] list;
if(args.length == 0)
list = path.list();
else
list = path.list(new DirFilter(args[0]));
for(int i = 0; i < list.length; i++)
System.out.println(list[i]);
} catch(Exception e) {
e.printStackTrace();
}
}
}
class DirFilter implements FilenameFilter {
String afn;
DirFilter(String afn) { this.afn = afn; }
public boolean accept(File dir, String name) {
// Strip path information:
String f = new File(name).getName();
return f.indexOf(afn) != -1;
}
} ///:~
Класът DirFilter “реализира” interface FilenameFilter. (Интерфейсите бяха разгледани в глава 7.) Полезно е да се види колко прост е FilenameFilter interface:
public interface FilenameFilter {
boolean accept(File dir, String name);
}
Той казва, че всичко което прави този клас е да даде метода accept( ). Цялата причина за създаването на този клас е да се даде методът accept( ) на list( ) метода така че list( ) да може да извика обратно accept( ) за да определи кои файлови имена ще се включват в списъка. Тази техника често се нарича callback или понякога functor (тоест, DirFilter е функтор понеже единствената му задача е да съдържа метод). Понеже list( ) взима FilenameFilter обект за аргумент, това значи че може да дадете произволен обект от тип FilenameFilter за избор (даже по време на изпълнение) поведението на метода list( ). Предназначението на обратното извикване е да се даде гъвкавост откъм поведението на коде.
DirFilter показва че тъй като interface съдържа само множество от методи, не сте ограничени да работите само с тях. (Трябва най-малкото да дадете дефиниции за всичките методи на интерфейса, обаче.) В този случай се създава също и конструктор на DirFilter.
Методът accept( ) трябва да приеме File обект представящ къде даден файл е намерен, а String съдържа името на този файл. Бихте могли да изберете дали да използвате всеки от рагументите, но най-вероятно ще използвате поне файловото име. Запомнете че метода list( ) вика accept( ) за всяко от фойловите имена в списъка за да види дали то ще бъде включено – това се индицира чрез boolean резултата връщан от accept( ).
За да се работи само с имената, без информацията за пътя, трябва само да се вземе String обект и да се създаде File обект от него, тогава да се извика getName( ) което изрязва цялата информация за пътя по независим от платформата начин). Тогава accept( ) използва String класовия метод indexOf( ) да види дали стрингът afn се появява някъде във файловото име. Ако afn е намерен в стринга, върнатата стойност е началният индекс в afn, а ако не се намери върнатата стойност е -1. Помнете че това е просто търсене в стринг и няма търсене по съвпадения като в регулярни изрази с “wildcard” знаци като в “fo?.b?r*” което е много по-трудно за реализиране.
Методът list( ) връща масив. Може да проверите дължината му и после да се движите по него. Тази възможност непосредствено да се дават масиви като аргумент и да се връщат е огромно придвижване напред в сравнение с C и C++.
Анонимни вътрешни класове
Този пример е идеален за пренаписване с анонимни вътрешни класове (описани в глава 7). Като за начало се създава методът filter( ) който връща манипулатор към FilenameFilter:
//: c10:DirList2.java
// Uses Java 1.1 anonymous inner classes
import java.io.*;
public class DirList2 {
public static FilenameFilter
filter(final String afn) {
// Creation of anonymous inner class:
return new FilenameFilter() {
String fn = afn;
public boolean accept(File dir, String n) {
// Strip path information:
String f = new File(n).getName();
return f.indexOf(fn) != -1;
}
}; // End of anonymous inner class
}
public static void main(String[] args) {
try {
File path = new File(".");
String[] list;
if(args.length == 0)
list = path.list();
else
list = path.list(filter(args[0]));
for(int i = 0; i < list.length; i++)
System.out.println(list[i]);
} catch(Exception e) {
e.printStackTrace();
}
}
} ///:~
Забележете че аргументът към filter( ) трябва да бъде final. Това се изисква от вътрешния клас така че той да може да използва променливите на обекта извън обхвата си.
Този дизайн е усъвършенстван, понеже класът FilenameFilter сега е тясно свързан с DirList2. Обаче може да се придвижите стъпка напред с този подход и да дефинирате анонимен вътрешен клас за аргумент на list( ), в който случай той е даже по-малък:
//: c10:DirList3.java
// Building the anonymous inner class "in-place"
import java.io.*;
public class DirList3 {
public static void main(final String[] args) {
try {
File path = new File(".");
String[] list;
if(args.length == 0)
list = path.list();
else
list = path.list(
new FilenameFilter() {
public boolean
accept(File dir, String n) {
String f = new File(n).getName();
return f.indexOf(args[0]) != -1;
}
});
for(int i = 0; i < list.length; i++)
System.out.println(list[i]);
} catch(Exception e) {
e.printStackTrace();
}
}
} ///:~
Аргументът на main( ) е сега final, понеже анонимният вътрешен клас използва директно args[0].
Това показва как анонимните вътрешни класове позволяват да се създадат бързо и лесно класове. Тъй като всичко в Java се върти около класовете, това би могло да бъде полезна техника. Една полза е че кодът, който решава даден проблем остава тясно свързан и в една точка. От друга страна, не винаги е лесен за четене, така че трябва да се използва разумно.
Сортиран списък на директорията
А-а, искате имената сортирани? Тъй като нява поддръжка за сортиране в Java 1.0 и Java 1.1 (макар и сортирането да е включено в Java 2), ще трябва да го включим директно в програмата използвайки SortList създаден в глава 8:
//: c10:SortedDirList.java
// Displays sorted directory listing
import java.io.*;
import c08.*;
public class SortedDirList {
private File path;
private String[] list;
public SortedDirList(final String afn) {
path = new File(".");
if(afn == null)
list = path.list();
else
list = path.list(
new FilenameFilter() {
public boolean
accept(File dir, String n) {
String f = new File(n).getName();
return f.indexOf(afn) != -1;
}
});
sort();
}
void print() {
for(int i = 0; i < list.length; i++)
System.out.println(list[i]);
}
private void sort() {
StrSortList sv = new StrSortList();
for(int i = 0; i < list.length; i++)
sv.add(list[i]);
// The first time an element is pulled from
// the StrSortList the list is sorted:
for(int i = 0; i < list.length; i++)
list[i] = sv.get(i);
}
// Test it:
public static void main(String[] args) {
SortedDirList sd;
if(args.length == 0)
sd = new SortedDirList(null);
else
sd = new SortedDirList(args[0]);
sd.print();
}
} ///:~
Няколко други подобрения бяха направени. Вместо да се създават path и list като локални променливи на main( ), те са членове на клас, така че техните стойности са достъпни по времето на живот на класа. Фактически main( ) сега е просто начин да се тества класа. Може да видите, че конструктора на класа автоматично сортира имената щом списъка се създаде.
Сортирането е независимо от големи или малки букви, така че не получавате сортиран списък на думите с главни букви следван от тези с малки. Може обаче да забележите, че когато има две имена с еднакви бекви първо се изобразява това с голяма, което все още не е точно желаното повезение за сортирането. Този проблем ще се реши в Java 2.
Проверка за и създаване на директории
Класът File е повече от просто представяне на път, файл или група файлове. Също може да използвате File обект за създаване на нова директория или цялостен път, който не съществува. Може също да гледате характеристиките на файловете (дължина, дата на последната промяна, четене/писане), дали File обекта представя файл или директория и да изтриете файл. Тази програма показва останалите методи налични в класа File:
//: c10:MakeDirectories.java
// Demonstrates the use of the File class to
// create directories and manipulate files.
import java.io.*;
public class MakeDirectories {
private final static String usage =
"Usage:MakeDirectories path1 ...\n" +
"Creates each path\n" +
"Usage:MakeDirectories -d path1 ...\n" +
"Deletes each path\n" +
"Usage:MakeDirectories -r path1 path2\n" +
"Renames from path1 to path2\n";
private static void usage() {
System.err.println(usage);
System.exit(1);
}
private static void fileData(File f) {
System.out.println(
"Absolute path: " + f.getAbsolutePath() +
"\n Can read: " + f.canRead() +
"\n Can write: " + f.canWrite() +
"\n getName: " + f.getName() +
"\n getParent: " + f.getParent() +
"\n getPath: " + f.getPath() +
"\n length: " + f.length() +
"\n lastModified: " + f.lastModified());
if(f.isFile())
System.out.println("it's a file");
else if(f.isDirectory())
System.out.println("it's a directory");
}
public static void main(String[] args) {
if(args.length < 1) usage();
if(args[0].equals("-r")) {
if(args.length != 3) usage();
File
old = new File(args[1]),
rname = new File(args[2]);
old.renameTo(rname);
fileData(old);
fileData(rname);
return; // Exit main
}
int count = 0;
boolean del = false;
if(args[0].equals("-d")) {
count++;
del = true;
}
for( ; count < args.length; count++) {
File f = new File(args[count]);
if(f.exists()) {
System.out.println(f + " exists");
if(del) {
System.out.println("deleting..." + f);
f.delete();
}
}
else { // Doesn't exist
if(!del) {
f.mkdirs();
System.out.println("created " + f);
}
}
fileData(f);
}
}
} ///:~
Във fileData( ) може да видите различни методи за файловете, използвани по подходящ начин.
Първият метод изпитан в main( ) е renameTo( ), който позволява да се преименува (или премести) файл към напълно нов пъът, представен от аргумента, който е друг File обект. Това също работи за директории с всякаква дължина.
Ако експериментирате с горната програма, може да видите че е възможно да се направи път с всякаква сложност, понеже mkdirs( ) ще направи нужното. В Java 1.0, флагът -d показва, че директорията е изтрита, но все още е там (т.е. е белязана за изтриване - б.пр.); в Java 1.1 директорията е изтрита фактически.
Сподели с приятели: |