Thinking in Java——第18章I/O系统(一)
18.1 File类
File(文件)类既能代表一个特定的文件的名称,又能代表一个目录下的一组文件的名称。如果指的是一个文件集,可以对此调用list()方法,这个方法返回一个字符数组。
18.1.1 目录列表器
注意:使用Arrays.sort()和String.CASE_INSENSITIVE.ORDERComparator可以快速对一个数组进行按照字母顺序排序。
import java.util.*;
import java.io.*;
import java.util.regex.*;
public class DirList{
public static void main(String[] args)
{
File path = new File("D:/files/app/src/main/java/com/superdaxue/tingtashuo/fragment");
String []list;
if(args.length == 0){
list = path.list();
}else{
list = path.list(new DirFilter(args[0]));
System.out.println(args[0]);
}
Arrays.sort(list,String.CASE_INSENSITIVE_ORDER);
for (String dirItem : list)
{
System.out.println(dirItem);
}
}
}
class DirFilter implements FilenameFilter{
private Pattern pattern;
public DirFilter(String regex){
pattern = Pattern.compile(regex);
}
public boolean accept(File dir , String name){
return pattern.matcher(name).matches();
}
}
18.1.2 目录实用工具
java.io.File.getCanonicalFile()返回此抽象路径名的规范形式
import java.util.regex.*;
import java.util.*;
import java.io.*;
public final class Directory
{
public static File[]local(File dir , final String regex){
return dir.listFiles(new FilenameFilter(){
private Pattern pattern = Pattern.compile(regex);
public boolean accept(File dir,String name){
return pattern.matcher(new File(name).getName()).matches();
}
});
}
public static File[]local (String path , final String regex){
return local(new File(path), regex);
}
public static class TreeInfo implements Iterable<File>
{
public List<File>files = new ArrayList<File>();
public List<File>dirs = new ArrayList<File>();
public Iterator<File> iterator (){
return files.iterator();
}
void addAll(TreeInfo other){
files.addAll(other.files);
dirs.addAll(other.dirs);
}
public String toString(){
return "";//
}
}
public static TreeInfo walk(String start,String regex){
return recurseDirs(new File(start),regex);
}
public static TreeInfo walk(File start , String regex){
return recurseDirs(start,regex);
}
public static TreeInfo walk(File start){
return recurseDirs(start,".*");
}
public static TreeInfo walk(String start){
return recurseDirs(new File(start),".*");
}
static TreeInfo recurseDirs(File startDir , String regex){
TreeInfo result = new TreeInfo();
for (File item : startDir.listFiles())
{
if (item.isDirectory())
{
result.dirs.add(item);
result.addAll(recurseDirs(item,regex));
}else{
if (item.getName().matches(regex))
{
result.files.add(item);
}
}
}
return result;
}
public static void main(String[] args)
{
// PPrint.pprint(Directory.walk(PATH).dirs);
// for (File f : Directory.local(PATH,".*") )
// {
// System.out.println(f);
// }
new ProcessFiles(new ProcessFiles.Strategy(){
public void process(File file){
System.out.println(file);
}
},"java").start(args);
}
}
class PPrint
{
public static String pformat(Collection<?>c){
if (c.size() == 0)
{
return "[]";
}
StringBuilder sb = new StringBuilder("[");
for ( Object elem : c )
{
if(c.size() != 1){
sb.append("\n");
sb.append(elem);
}
}
if (c.size() != 1)
{
sb.append("\n");
sb.append("]");
}
return sb.toString();
}
public static void pprint(Collection<?>c){
System.out.println(pformat(c));
}
public static void pprint(Object [] o){
System.out.println(pformat(Arrays.asList(o)));
}
}
class ProcessFiles
{
private static final String PATH = "D:/files/app/src/main/java/com/superdaxue/tingtashuo/fragment";
public interface Strategy
{
void process(File file);
}
private Strategy strategy;
private String ext;
public ProcessFiles(Strategy strategy,String ext){
this.strategy = strategy;
this.ext = ext;
}
public void start(String [] args){
try
{
if (args.length == 0)
{
processDirectoryTree(new File(PATH));
}else{
for (String arg : args )
{
File fileArg = new File(arg);
if (fileArg.isDirectory())
{
processDirectoryTree(fileArg);
}else{
if (!arg.endsWith("."+ext))
{
arg += "." +ext;
}
strategy.process(new File(arg).getCanonicalFile());
}
}
}
}
catch (IOException e)
{
throw new RuntimeException(e);
//e.printStackTrace();
}
}
public void processDirectoryTree (File root) throws IOException{
for (File file : Directory.walk(root.getAbsolutePath(),".*\\."+ext))
{
strategy.process(file.getCanonicalFile());
}
}
}
18.1.3 目录的检查及创建
File类不仅仅只代表存在的文件或目录,也可以用File对象来创建新的目录或尚不存在的整个目录路径。还可以查看文件的特性(如:大小,最后的修改日期,读/写),检查某个File对象代表的是一个文件还是一个目录。
import java.io.*;
class MakeDirectories
{
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 ;
}
int count = 0 ;
boolean del = false;
if (args[0].equals("-d"))
{
count ++ ;
del = true;
}
count -- ;
while (++ count < args.length)
{
File f = new File(args[count]);
if (f.exists())
{
System.out.println(f + " exists");
if(del){
System.out.println("deleting ... " + f);
f.delete();
}
}else{
if (!del)
{
f.mkdirs();
System.out.println("created " + f);
}
fileData(f);
}
}
}
private static void usage(){
System.out.println(
"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"
);
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 length:" +f.getPath() +
"\n lastModified:"+f.lastModified()
);
if(f.isFile()){
System.out.println("It's a file");
}else{
System.out.println("It's a directory");
}
}
}
18.2 输入和输出
Java类库中的I/O类分为输入和输出两部分。任何来自Inputstream或者Reader派生而来的类都含有名为read()的基本用法,用于读取单个字节或者字节数组。同样,任何自OutputStream或Writer派生而来的类名为write()的基本方法,用于写单个字节或者字节数组。
18.2.1 InputStream类型
InputStream:表示从不同数据源产生输入的类。
数据源包括:
- 字节数组
- String对象
- 文件
- “管道”,工作方式与实际管道类似,即,从一端输入,从另一端输出。
- 一个由其他种类的流组成的序列,以便我们可以将它们收集合并到一个流内
- 其他数据源,如Internet连接等
类 | 功能 | 构造器参数如何使用 |
---|---|---|
ByteArrayInput | 允许将内存缓冲区当作InoutStream使用 | 缓冲区,字节从其中取出作为一种数据源:将其与FileterInputStream对象相连以提供有用接口 |
StringBufferInputStream | 将String转换成InputStream | 字符串。底层实现实际使用StringBuffer作为一种数据源:将其与FilterInputStream对象相连提供有用接口 |
FileInputStream | 用于从文件中读取信息 | 字符串,表示文件名、文件或FileDescriptor对象作为一种数据源:将其与FileterInputStream对象相连以提供有用接口 |
PipedInputSream | 产生用于写入相关PipedOutStream的数据。实现“管道化” | PipedIntsream作为多线程中的数据源:将其FilterInputStream对象相连以提供有用接口 |
SequenceInputStream | 将两个或多个InputStream对象转换成单一InputStream | 两个InputStream对象或一个容纳InputStream对象的容器Enumeration作为一种数据源:将其与FilterInputStream对象以提供有用有用接口 |
FilterInputStream | 抽象类,作为“装饰器”的接口。其中,“装饰器”为其他的InputStraem提供有用功能 |
18.2.2 OutputStream类型
类 | 功能 | 构造器参数如何使用 |
---|---|---|
ByteArrayOutputStream | 在内存中创建缓冲区。所有送往”流”的数据都要放置在此缓冲区 | 缓冲区初始化尺寸(可选的)用于指定数据的目的地:将其与FilterOutputStream对象相连以提供接口 |
FileOutputStream | 用于将信息写至文件 | 字符串,表示文件名、文件或FileDescriptor对象指定数据的目的地:将其与FilterOutputStream对象相连以提供有用接口 |
PipedOutputStream | 任何写入其中的信息都会自动作为相关PipedInputStream的输出。实现“管道化” | PipedInputSrteam指定用于多线程的数据的目的地:将其与FilterOutputStream对象相连以提供 |
FilterOutputStream | 抽象类,作为“装饰器”的接口。其中,“装饰器”为其他OutputStraem提供有用功能 |
18.3 添加属性和有用的接口
FilterInoutStraem 和 FilterOutputStraem是用来提供装饰器接口以控制特定输入流(InputStraem)和输出流(OutputStraem)两个类。FilterInputStram 和 FilterOutputStream分别自I/O类库中的基类InputStream和OutputSrteam派生而来。
18.3.1 通过FilterInputStream从InputStrtream读取数据
FilterInputStream 类能够完成两件完全不同的事情。DataInputStream允许不同的基本类型数据以及String对象。其他FilterInputStream类则内部修改InputStream的行为方式:是否缓冲,是否保留它所读过的行,以及是否把单一字符推回输入流。
18.4 Reader 和 Writer
InputStreamReader可以把InputStraem转换为Reader,而OutputStreamWriter可以把OutputStream转为Writer
18.4.1数据的来源和出处
18.4.2更改流的行为
回头多看两遍
18.5自我独立的类 :RandomAccessFile
RandomAccessFile适用于由大小已知的记录组成的文件,我们可以使用seek()将记录从一处转移到另一处,然后读取和修改。RandomAccessFile是一个完全独立的类。直接从Object派生。getFilePointer用于查找当前所处的文件位置,seek()用于在文件内移至新的位置,length()用于判断文件的最大尺寸。构造还需要第二个参数用来指示只是”随机读”(r)还是”既读既写”(rw),但不支持只写。
18.6I/O流的典型使用方式
18.6.1缓冲输入文件
想要打开一个文件用于字符输入,可以使用以String或File对象作为文件名的FileInputReader。使用BufferedReader,提供readLine()方法。当readLine()返回null时,就证明达到了文件末尾。
import java.io.*;
class BufferedInputFile
{
public static void main(String[] args) throws IOException
{
System.out.println(read("BufferedInputFile.java"));
}
public static String read(String filename) throws IOException{
BufferedReader in = new BufferedReader(new FileReader(filename));
String s;
StringBuilder sb = new StringBuilder();
while((s = in.readLine()) != null){
sb.append(s + "\n");
}
in.close();
return sb.toString();
}
}
18.6.2从内存输入
从BufferedInputFile.read()读入的String结果被用来创建一个StringReader,然后调用read()每次读取一个字符,并把它发送到控制台。
import java.io.*;
class MemoryInput
{
public static void main(String[] args) throws IOException
{
StringReader in = new StringReader(read("MemoryInput.java"));
int c ;
while((c = in.read())!= -1){
System.out.println((char)c);
}
}
public static String read(String filename) throws IOException{
BufferedReader in = new BufferedReader(new FileReader(filename));
String s;
StringBuilder sb = new StringBuilder();
while((s = in.readLine()) != null){
sb.append(s + "\n");
}
in.close();
return sb.toString();
}
}
18.6.3 格式化的内存输入
要读取格式化数据,使用DataInputStream,一个面向字节的I/O类。必须使用InputStream类而不是Reader类。可以用InputStream以字节的形式读取任何数据
import java.io.*;
class FormattedMemoryInput
{
public static void main(String[] args) throws IOException
{
try
{
DataInputStream in = new DataInputStream(new ByteArrayInputStream(
read("FormattedMemoryInput.java").getBytes()
));
while(true)
System.out.println((char)in.readByte());
}
catch (IOException e)
{
System.out.println("End of Stream");
}
}
public static String read(String filename) throws IOException{
BufferedReader in = new BufferedReader(new FileReader(filename));
String s;
StringBuilder sb = new StringBuilder();
while((s = in.readLine()) != null){
sb.append(s + "\n");
}
in.close();
return sb.toString();
}
}
这里为ByteArrayInputStream提供字节数组,为了产生该数组String包含了一个可以实现此项工作的getBytes()方法。产生的ByteArrayInputStream是一个适合传递给DataInputStream的InputSream。
如果从DataInputStream用readByte()一次一个字节地读取字符,那么任何字节的值都是合法的结果,这就造成返回值不能用来检测输入是否结束。为了判断是否结束,可以使用available()这个方法查看有多少还有多少可供存取的字符。
import java.io.*;
class TestEOF
{
public static void main(String[] args)
{
try
{
DataInputStream in = new DataInputStream(
new BufferedInputStream(new FileInputStream("TestEOF.java"))
);
while(in.available() != 0){
System.out.print((char)in.readByte());
}
}
catch (IOException e)
{
System.out.println("End int stream");
}
}
}
**注意**available()的工作方式会随着所读取的的媒介类型的不同而有所不同。字面的意思就是”在没有阻塞的情况下所读取的字节数”。一个文件就是整个文件。这个方法在对于不同的流的时候可能就不是这样,谨慎使用。
18.6.4 基本的文件输出
FileWriter对象可以向文件写入。首先,创建一个与指定文件连接的FileWriter。实际上,通常会用BufferedWriter将其包装起来用以缓冲输出。
import java.io.* ;
class BasicFileOutput
{
private static String file = "BasicFileOutput.out";
public static void main(String[] args) throws IOException
{
BufferedReader in = new BufferedReader(new StringReader(read("BasicFileOutput.java")));
PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(file)));
int lineCount = 1;
String s;
while( (s = in.readLine()) != null){
out.println(lineCount ++ + ":" +s);
}
out.close();
System.out.println(read(file));
}
public static String read(String filename) throws IOException{
BufferedReader in = new BufferedReader(new FileReader(filename));
String s;
StringBuilder sb = new StringBuilder();
while((s = in.readLine()) != null){
sb.append(s + "\n");
}
in.close();
return sb.toString();
}
}
一旦读完读完输入流,readLine()会返回null。当读取完时,out调用close()。如果不为所有的输出文件调用close(),就会发现缓冲区内容不会被刷新清空。
18.6.5存储和恢复数据
PrintWriter可以对数据进行格式化。为了输出可供另一”流”恢复的数据,需要用DataOutputStream写入数据,并用DataInputStream恢复数据。注意DataOutputStream和DataInputStream是面向字节的,要使用InputStream和OutStream。
import java.io.*;
class StoringAndRecoveringData
{
public static void main(String[] args) throws IOException
{
DataOutputStream out = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("Data.txt")));
out.writeDouble(3.14159);
out.writeUTF("That was pi");
out.writeDouble(1.41413);
out.writeUTF("Square root of 2");
out.close();
DataInputStream in = new DataInputStream(
new BufferedInputStream(new FileInputStream("Data.txt"))
);
System.out.println(in.readDouble());
System.out.println(in.readUTF());
System.out.println(in.readDouble());
System.out.println(in.readUTF());
}
}
如果使用DataOutputStream写入数据,Java保证可以使用DataInputStream准确的读取数据——无论读和写数据的平台多么不同。
18.6.6 读写随机访问文件
使用RandomAccessFile时,利用seek()可以在文件中到处移动,并修改文件中的某个值。
import java.io.*;
class UsingRandomAccessFile
{
private static String file = "rtest.dat";
private static void display() throws IOException{
RandomAccessFile rf = new RandomAccessFile(file,"r");
for (int i = 0;i < 7 ;i ++ )
{
System.out.println(
"Value" + i + ":" + rf.readDouble()
);
}
System.out.println(rf.readUTF());
rf.close();
}
public static void main(String[] args) throws IOException
{
RandomAccessFile rf = new RandomAccessFile(file,"rw");
for (int i = 0 ; i < 7 ; i ++ )
{
rf.writeDouble( i * 1.414);
}
rf.writeUTF("The end of ths file");
rf.close();
display();
rf = new RandomAccessFile(file,"rw");
rf.seek(5 * 8);
rf.writeDouble(47.0001);
rf.close();
display();
}
}
因为double总是8字节长,用seek()查找第5个双精度值,只需用 5 * 8 产生查找位置;
RandomAccessFile除了实现DataInput和DataOutPut接口外,有效地与I/O继承层次结构的其他部分实现了分离。因为它不支持装饰,所以不能将其与InputStream及OutputStream子类的任何部分组合起来。第二个参数可以指定”只读”(r)或”读写”(rw)方式打开一个RandomAccessFile文件。