Java 7源码分析第17篇 - 传统文件 I/O操作

文件操作是Java I/O中常用且重要的一类操作,数据在磁盘中的唯一最小描述就是文件,上层应用程序只能通过文件来操作磁盘上的数据。在Java中,文件通过File类来描述,表示一个真实存在的文件或者是一个包含多个文件的目录。Java.io.File类能够完成对文件的基本操作,如创建、删除等操作,也可以完成对目录的创建这些操作。

首先来看File中两个重要的变量:

 private String path;
 private transient int prefixLength;

在File中有多个构造函数,但是不难发现这些构造函数会有一个共同点,那就是为如上的两个变量赋值,如:

 public File(String pathname) {
        if (pathname == null) {
            throw new NullPointerException();
        }
        this.path = fs.normalize(pathname);
        this.prefixLength = fs.prefixLength(this.path);
    }
normalize()方法主要的作用是规范路径的写法,所以String path="D:\\a.cmd.txt"、String path="/D:/a.cmd.txt"和String path="D:\\\\\\\a.cmd.txt"都是合法的。

prefixLength是由transient关键字修饰,为了安全问题,所以并不希望把类里面所有的东西都能存储(因为那样,别人可以通过序列化知道类里面的内容),即不会随类一起序列化到本地,所以当还原后,这个关键字定义的变量也就不再存在。当调用normalize()方法将路径规范化后,调用prefixLength()方法计算前缀长度,方便计算路径的父路径或者计算路径中包含的文件名等操作。如:

public String getParent() {
        int index = path.lastIndexOf(separatorChar);
        if (index < prefixLength) {
            if ((prefixLength > 0) && (path.length() > prefixLength))
                return path.substring(0, prefixLength);
            return null;
        }
        return path.substring(0, index);
    }

测试如下:

String path="D:\\\\a/cmd.txt";
		File f=new File(path);
		System.out.println("path="+f.getPath());// path=D:\a\cmd.txt
		
        System.out.println(f.getName());// cmd.txt
        System.out.println(f.getParent());   // D:\a     
可以看到,路径被正确规范化,并且正确得到了文件名和父路径等信息。

其实对于文件的操作非常多,有兴趣的可以自己根据如下的一些例子去查看源代码,下面来介绍一些常见的操作。

1、列出指定目录下的文件或目录

//只能列出一层目录
public class ListMyDir {
	public static void main(String[] args) {
		String fileName = "C:" + File.separator + "data";
		File f = new File(fileName);
		File[] fs = f.listFiles();
		for (int i = 0; i < fs.length; i++) {
			System.out.println(fs[i].getName());
		}
	}
}

2、列出指定目录下的所有目录或文件(目录下的文件或目录也会列出)

	public void print(File f) {
		if (f != null) {
			if (f.isDirectory()) {
				File[] fileArray = f.listFiles();
				if (fileArray != null) {
					for (int i = 0; i < fileArray.length; i++) {
						print(fileArray[i]);// 递归调用
					}
				}
			} else {
				System.out.println(f);
			}
		}
	}


3、从外部读入一个文件

	public void readFile(String fileName) {
		File srcFile = new File(fileName);
		InputStream in = null;
		try {
			in = new FileInputStream(srcFile);
			byte[] b = new byte[(int) srcFile.length()];
			for (int i = 0; i < b.length; i++) {
				b[i] = (byte) in.read();
			}
			System.out.println(new String(b));
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (in != null) {
					in.close();
					in = null;
				}
			} catch (Exception e) {	}
		}
	}	


4、输出内容到外部文件

	public void writeWithByte() {
		String fileName = "D:" + File.separator + "hello.txt";
		OutputStream out = null;
		File f = new File(fileName);
		try {
			out = new FileOutputStream(f, true);
			String str = "   [Publicity ministry of ShangHai Municipal committee of CPC]";
			byte[] b = str.getBytes();
			out.write(b);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (out != null) {
					out.close();
					out = null;
				}
			} catch (Exception e) {	}
		}
	}

	public void writeWithByteArray() {
		String fileName = "D:" + File.separator + "hello.txt";
		OutputStream out = null;
		File f = new File(fileName);
		try {
			out = new FileOutputStream(f, true);
			String str = "   [hello with byte yi ge ge xie]";
			byte[] b = str.getBytes();
			for (int i = 0; i < b.length; i++) {
				out.write(b[i]);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (out != null) {
					out.close();
					out = null;
				}
			} catch (Exception e) {	}
		}
	}	
5、实现文件的复制

public void copy(String src, String des) {
		File srcFile = new File(src);
		File desFile = new File(des);
		InputStream in = null;
		OutputStream out = null;
		try {
			in = new FileInputStream(srcFile);
			out = new FileOutputStream(desFile);
			byte[] b = new byte[(int) srcFile.length()];
			for (int i = 0; i < b.length; i++) {
				b[i] = (byte) in.read();
			}
			out.write(b);
			System.out.println("copied [" + srcFile.getName() + "]    with    "
					+ srcFile.length());
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (out != null) {
					out.close();
					out = null;
				}
			} catch (Exception e) {	}
			try {
				if (in != null) {
					in.close();
					in = null;
				}
			} catch (Exception e) {	}
		}
	}
或者使用缓存实现文件的复制,如下:

public void copy(String src, String des) {
		File srcFile = new File(src);
		File desFile = new File(des);
		BufferedInputStream bin = null;
		BufferedOutputStream bout = null;
		try {
			bin = new BufferedInputStream(new FileInputStream(srcFile));
			bout = new BufferedOutputStream(new FileOutputStream(desFile));
			byte[] b = new byte[1024];
			while (bin.read(b) != -1) {
				bout.write(b);
			}
			bout.flush();
			System.out.println("copied [" + srcFile.getName() + "]    with    "
					+ srcFile.length());
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (bout != null) {
					bout.close();
					bout = null;
				}
			} catch (Exception e) {	}
			try {
				if (bin != null) {
					bin.close();
					bin = null;
				}
			} catch (Exception e) {	}
		}
	}

可能在写入或读取文件的时候,通常还需要更加灵活的操作,如向一个文件中的开始处添加字符串,读取一个文件末尾的指定数量的字符等等,这时候就需要通过RandomAccessFile类中的方法进行操作了。这主要是通过一个类似于数组指针来实现的,可以将一个文件中存储的所有内容放到一个数组中,然后通过指定来读取到特定的部分,常见的这类方法如:

seek(long pos) // Sets the file-pointer offset, measured from the beginning of this file, at which the next read or write occurs.
getFilePointer()   // Returns the current offset in this file.
可以看到,能够灵活地操作file-pointer来读取或写入内容。


举个例子:

public class RandomAccess {
	public void writeToFile() {
		String fileName = "D:" + File.separator + "hello.txt";
		RandomAccessFile randomIO = null;
		try {

			File f = new File(fileName);
			randomIO = new RandomAccessFile(f, "rw");
			randomIO.writeBytes("asdsad");
			randomIO.writeInt(12);
			randomIO.writeBoolean(true);
			randomIO.writeChar(‘A‘);
			randomIO.writeFloat(1.21f);
			randomIO.writeDouble(12.123);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if (randomIO != null) {
					randomIO.close();
					randomIO = null;
				}
			} catch (Exception e) {	}
		}
	}

	public static void main(String[] args) {
		RandomAccess randomA = new RandomAccess();
		randomA.writeToFile();
	}
}


传统的文件操作只能实现一些简单的输入、输出、创建、删除等操作,如果要进行更加复杂的操作就无能为力了。例如,读取一个非常大的日志文件会造成I/O阻塞、对文件的属性支持力度不够、不支持目录树导航的类或方法等等,而这些操作往往对一个文件的操作来说非常重要,所以Java 7提供了亲的I/O API,即NIO.2。

由于新的NIO中文件或目录使用的对象为Path,所以为了兼容新的类,File中提供了toPath()方法,源代码如下:

 public Path toPath() {// 在Java1.7中,主要对文件的操作就是Path对象,提供了一个兼容
        Path result = filePath;
        if (result == null) {
            synchronized (this) {
                result = filePath;
                if (result == null) {
                    result = FileSystems.getDefault().getPath(path);
                    filePath = result;
                }
            }
        }
        return result;
    }
可以直接将已经存在的File对象转换为NIO中的Path对象进行操作。















































seek(long pos)
Sets the file-pointer offset, measured from the beginning of this file, at which the next read or write occurs.

Java 7源码分析第17篇 - 传统文件 I/O操作

上一篇:SAP abap 集中检索帮助:WBS 元素的匹配码(PRPM)


下一篇:用 PS 下载 FTP 服务器的文件