先看一下java.io.File类。它其实应该命名为FileInfo会好一些。看看它能做什么:
它能用来创建一个文件:
- File file = new File("MyFile");
- if (!file.exists()) {
- try {
- file.createNewFile();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
查看一个文件的属性:
- boolean canRead = file.canRead();
- long lastModified = file.lastModified();
- String absolutePath = file.getAbsolutePath();
- long freeSpace = file.getFreeSpace();
改变一个文件的属性:
- file.setExecutable(false);
- file.setWritable(true);
- file.setLastModified(System.currentTimeMillis());
移动/重命名一个文件:
- File otherFile = new File("MyOtherFile");
- file.renameTo(otherFile);
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- boolean exists = file.exists(); // false;
创建一个文件夹:
- File folder = new File("MyFolder");
- if (!folder.exists())
- folder.mkdir();
列出一个文件夹下的所有文件和文件夹:
- String fileNamesInFolder[] = folder.list();
- File filesInFolder[] = folder.listFiles();
当然你也可以
列出一个文件夹下你感兴趣的文件和文件夹:
- String interestedfileNames[] = folder.list(new FilenameFilter() {
- public boolean accept(File dir, String name) {
- return name.endsWith(".txt");
- }
- });
用File的静态方法
得到系统所有的根目录:
- File roots[] = File.listRoots();
- System.out.println(Arrays.toString(roots));
得到一个文件或文件夹的父文件夹(只对绝对路径有效):
- System.out.println(file.getParent()); // null!!
- File absFile = file.getAbsoluteFile();
- System.out.println(absFile.getParent()); // works!!
最后是
删除一个文件或文件夹:
- boolean deleted = file.delete();
- deleted = folder.delete();
输入输出(I/O)的对象并不仅仅是文件,可以是任何形式的设备,比如屏幕或是网络。下面介绍四个I/O最最基本的类,它们是InputStream、OutputStream、Reader和Writer。
InputStream是所有“输入流”的基类,它是一个纯虚类并定义了一个读取字节的纯虚方法:“public int read() throws IOException”,你可以这样定义InputStream的子类:
- class MyInputStream extends InputStream {
- @Override
- public int read() throws IOException {
- if (itor >= data.length)
- return -1;
- return data[itor++];
- }
-
- private int itor = 0;
- private byte data[] = { 2, 5, 9, 8, 3, 4 };
- }
在定义好这个纯虚read方法只读取一个字节,但返回一个int值。这个int值如果为-1说明读取已经完毕,但如果这个值作为有效值的话,它的前三个字节会被忽略。read方法定义好后,InputStream的
read(byte[] b)和
read(byte[] b, int off, int len)方法会调用read()方法来实现更复杂的功能。
OutputStream是所有“输出流”的基类,它也是一个纯虚类,定义了两个写入字节的纯虚方法:“public void write(int b) throws IOException”。定义子类:
- class MyOutputStream extends OutputStream {
- @Override
- public void write(int b) throws IOException {
- data.add((byte)b);
- }
-
- private ArrayList<Byte> data = new ArrayList<Byte>();
- }
Write方法写入传入int值的最后一个字节。同样的,OutputStream的
write(byte[] b)和
write(byte[] b, int off, int len)方法会调用write()方法来完成更复杂的功能。
Reader是所有“字符读取器”的纯虚基类,它有两个纯虚方法:“public void close() throws IOException”、“public int read(char[] cbuf, int off, int len) throws IOException”。定义子类:
- class MyReader extends Reader {
- @Override
- public void close() throws IOException {
- closed = true;
- }
- @Override
- public int read(char[] cbuf, int off, int len) throws IOException {
- if (closed)
- throw new IOException();
- if (index >= data.length())
- return -1;
- int count = 0;
- for (int i = 0; i < len && index < data.length(); ++i) {
- cbuf[i+off] = data.charAt(index++);
- ++count;
- }
- return count;
- }
-
- private boolean closed = false;
- private String data = "This is the data. You are happy~";
- private int index = 0;
- }
Reader是InputStream的补充,它提供了读取字符的功能,而不仅仅是字节。在定义好read(char[] cbuf, int off, int len)方法后,Reader的“read()”、“read(char[] cbuf)”和“read(CharBuffer target)”方法就可以利用定义好的方法来提供更简单的读取字符的方法。
Writer是所有“写入字符器”的纯虚基类,它定义了三个纯虚方法:“public void close() throws IOException”、“public void flush() throws IOException”和“public void write(char[] cbuf, int off, int len) throws IOException”。定义子类:
- class MyWriter extends Writer {
- @Override
- public void close() throws IOException {
- closed = true;
- }
- @Override
- public void flush() throws IOException {
- if (closed)
- throw new IOException();
- System.out.println(data);
- }
- @Override
- public void write(char[] cbuf, int off, int len) throws IOException {
- if (closed)
- throw new IOException();
- for (int i = 0; i < len; ++i)
- data += cbuf[i+off];
- }
-
- private boolean closed = false;
- private String data = new String();
- }
定义好这个纯虚方法后,Writer类的“
append(char c)”、“
append(CharSequence csq)”、“
append(CharSequence csq, int start, int end)”、“
write(char[] cbuf)”、“writer.write(int c)”、“
write(String str)”和“
write(String str, int off, int len)”方法也都可以用了。
现在回到一个比较基本的问题,怎么从读写一个文件的数据?java提供了两个类:FileInputStream和FileOutputStream。我们可以用它们基类里定义的方法:InputStream.read(byte[] bytes)和OutputStream.write(byte[] bytes)。提起精神来,下面的代码有点长,虽然不复杂:
- void testFileIOStream() throws IOException {
- long ldata = -328910192;
- int idata = 2305910;
- short sdata = 4652;
- char cdata = 'A';
- double ddata = 98323.8253221;
- float fdata = 2382.784f;
-
- // Write to file
- FileOutputStream fos = new FileOutputStream("MyFile");
- fos.write(DataConvertor.longToBytes(ldata));
- fos.write(DataConvertor.intToBytes(idata));
- fos.write(DataConvertor.shortToBytes(sdata));
- fos.write(DataConvertor.charToBytes(cdata));
- fos.write(DataConvertor.doubleToBytes(ddata));
- fos.write(DataConvertor.floatToBytes(fdata));
- fos.flush();
- fos.close();
-
- byte[] lBytes = new byte[Long.SIZE/8];
- byte[] iBytes = new byte[Integer.SIZE/8];
- byte[] sBytes = new byte[Short.SIZE/8];
- byte[] cBytes = new byte[Character.SIZE/8];
- byte[] dBytes = new byte[Double.SIZE/8];
- byte[] fBytes = new byte[Float.SIZE/8];
-
- // Read from file
- FileInputStream fis = new FileInputStream("MyFile");
- fis.read(lBytes);
- fis.read(iBytes);
- fis.read(sBytes);
- fis.read(cBytes);
- fis.read(dBytes);
- fis.read(fBytes);
- fis.close();
-
- // Print Values
- System.out.println("Long data: " + DataConvertor.bytesToLong(lBytes));
- System.out.println("Int data: " + DataConvertor.bytesToInt(iBytes));
- System.out.println("Short data: " + DataConvertor.bytesToShort(sBytes));
- System.out.println("Char data: " + DataConvertor.bytesToChar(cBytes));
- System.out.println("Double data: " + DataConvertor.bytesToDouble(dBytes));
- System.out.println("Float data: " + DataConvertor.bytesToFloat(fBytes));
- }
看到上面的代码里有个
DataConvertor的类,它可以把基本类型和字节数组进行转换。它可不是java里自带的,我们得自己实现它:
- class DataConvertor {
- public static byte[] longToBytes(long l) {
- return numberToBytes(l, Long.SIZE/8);
- }
-
- public static long bytesToLong(byte[] bytes) {
- return bytesToNumber(bytes, Long.SIZE/8).longValue();
- }
- public static byte[] intToBytes(int n) {
- return numberToBytes(n, Integer.SIZE/8);
- }
-
- public static int bytesToInt(byte[] bytes) {
- return bytesToNumber(bytes, Integer.SIZE/8).intValue();
- }
-
- public static byte[] shortToBytes(short s) {
- return numberToBytes(s, Short.SIZE/8);
- }
-
- public static short bytesToShort(byte[] bytes) {
- return bytesToNumber(bytes, Short.SIZE/8).shortValue();
- }
-
- public static byte[] doubleToBytes(double d) {
- return longToBytes(Double.doubleToLongBits(d));
- }
-
- public static double bytesToDouble(byte[] bytes) {
- return Double.longBitsToDouble(bytesToLong(bytes));
- }
-
- public static byte[] floatToBytes(float f) {
- return intToBytes(Float.floatToRawIntBits(f));
- }
-
- public static float bytesToFloat(byte[] bytes) {
- return Float.intBitsToFloat(bytesToInt(bytes));
- }
-
- public static byte[] charToBytes(char c) {
- return numberToBytes((int)c, Character.SIZE/8);
- }
-
- public static char bytesToChar(byte[] bytes) {
- return (char)(bytesToNumber(bytes, Character.SIZE/8).intValue());
- }
-
- private static byte[] numberToBytes(Number n, final int size) {
- byte[] bytes = new byte[size];
- long l = n.longValue();
- for (int i = 0; i < size; i++)
- bytes[i] = (byte)((l >> 8*i) & 0xff);
- return bytes;
- }
-
- private static Number bytesToNumber(byte[] bytes, final int size) {
- long l = 0;
- for (int i = 0; i < size; i++)
- l |= ((long)(bytes[i] & 0xff) << (8*i));
- return l;
- }
- }
是不是有点复杂?如果我们每次写文件总要和这么底层的字节打交道的话,那样总是显得比较繁琐。java提供了另一组I/O流:
DataInputStream和
DataOutputStream,它可以外嵌在其它的I/O流对象外,比如FileInputStream。用这两个类重写上面的读写文件的功能:
- void testDataIOStream() throws IOException {
- DataOutputStream dos = new DataOutputStream(new FileOutputStream("MyFile"));
- dos.writeLong(ldata);
- dos.writeInt(idata);
- dos.writeShort(sdata);
- dos.writeChar(cdata);
- dos.writeDouble(ddata);
- dos.writeFloat(fdata);
- dos.flush();
- dos.close();
-
- DataInputStream dis = new DataInputStream(new FileInputStream("MyFile"));
- long l = dis.readLong();
- int n = dis.readInt();
- short s = dis.readShort();
- char c = dis.readChar();
- double d = dis.readDouble();
- float f = dis.readFloat();
- dis.close();
- }
java的I/O类库用的是
装饰者模式,比如DataInputStream可以是任何一个InputStream的装饰者。通过这种模式,你可以组合出各种功能的I/O对象。
如果需要向文件存取字符串,而不是字节,就可以使用FileReader和FileWriter了:
- testFileRW() throws IOException {
- FileWriter fw = new FileWriter("MyFile");
- fw.write("Hello, ");
- fw.write("hava a good day\n");
- fw.append("Here's another line!");
- fw.flush();
- fw.close();
-
- FileReader fr = new FileReader("MyFile");
- CharBuffer cb = CharBuffer.allocate(1000);
- int n = fr.read(cb);
- fr.close();
-
- System.out.println(cb.array());
- }
我们同样可以给Reader和Writer添加装饰者,比如
BufferedReader和
BufferedWriter。它们提供一个缓冲区来防止每次都执行实际读写操作,同时还提供读写行的能力:
- void testBufferRW() throws IOException {
- BufferedWriter bw = new BufferedWriter(new FileWriter("MyFile"));
- bw.write("Write into buffer");
- bw.newLine(); // BufferedWriter only!!
- bw.write("Another line");
- bw.flush();
- bw.close();
-
- BufferedReader br = new BufferedReader(new FileReader("MyFile"));
- String line;
- do {
- line = br.readLine(); // BufferedReader only!!
- System.out.println(line);
- } while (line != null);
- }
除了文件,我们也可以对其它类型的目标进行读写,比如内存。我们可以用CharArrayWriter/CharArrayReader读写内存(或者ByteArrayInputStream/ByteArrayOutputStream读写字节)。下面给了一个在内存中读写字符串例子,同时也介绍一个PrintWriter和StringReader:
- void testCharArrayRW() throws IOException {
- // write to memory instead of file
- CharArrayWriter baw = new CharArrayWriter();
- PrintWriter pw = new PrintWriter(baw);
- pw.print((int)3);
- pw.print((boolean)true);
- pw.println("OK");
- pw.flush();
- pw.close();
-
- // read from memory using CharArrayReader
- CharArrayReader car = new CharArrayReader(baw.toCharArray());
- BufferedReader br = new BufferedReader(car);
- String line;
- while ((line = br.readLine()) != null)
- System.out.println(line);
- br.close();
-
- // read from memory using StringReader
- StringReader sr = new StringReader(baw.toString());
- br = new BufferedReader(sr);
- sr.skip(2);
- while ((line = br.readLine()) != null)
- System.out.println(line);
- sr.close();
- }
InputStreamReader和OutputStreamWriter可以把InputStream和OutputStream转成Reader和Writer:
- void testIOStreamRW() throws IOException {
- OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("MyFile"));
- osw.write("Just for fun");
- osw.flush();
- osw.close();
-
- InputStreamReader isr = new InputStreamReader(new FileInputStream("MyFile"));
- int c;
- while ((c = isr.read()) >= 0)
- System.out.print((char)c);
- isr.close();
- }
系统的标准I/O包括System.in、System.out和System.err。System.out和System.err是两个PrintStream对象,System.in是一个InputStream对象。可以使用System的setIn(InputStream)、setOut(PrintStream)和setErr(PrintStream)方法来重定向这些标准I/O的目标。
I/O库里有一个类RandomAccessFile,它不是Reader/Writer或InputStream/OutputStream的子类,而是直接继承自Object类并实现了DataInput和DataOuput这两个接口。(DataInputStream和DataOutputStream也分别实现了这两个接口)。同时它还可以定位到文件的随机位置进行读写:
- void testRandomAccessFile() throws IOException {
- RandomAccessFile raf = new RandomAccessFile("MyFile", "rw");
- for (int i = 1; i <= 10; ++i)
- raf.writeInt(i);
-
- // return to the beginning
- raf.seek(0);
- for (int i = 0; i < 10; ++i)
- System.out.print(raf.readInt() + " "); // 1 2 3 4 5 6 7 8 9 10
-
- // modify the 5th int
- raf.seek(Integer.SIZE/8 * 4);
- raf.writeInt(555);
-
- raf.seek(0);
- for (int i = 0; i < 10; ++i)
- System.out.print(raf.readInt() + " "); // 1 2 3 4 555 6 7 8 9 10
- }
java里还提供压缩文件的读写。最常用的两种压缩算法是Zip和GZiP。先看看GZip的简单例子:
- testGZipIOStream() throws FileNotFoundException, IOException {
- GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream("MyZip.gz"));
- DataOutputStream dos = new DataOutputStream(gzos);
- dos.writeInt(38);
- dos.flush();
- dos.close();
-
- GZIPInputStream gzis = new GZIPInputStream(new FileInputStream("MyZip.gz"));
- DataInputStream dis = new DataInputStream(gzis);
- int n = dis.readInt();
- dis.close();
-
- System.out.println(n);
- }
上面的代码会生成一个MyZip.gz文件,它里面带一个名为“MyZip”的Entry。用解压缩软件就可以看到。
java对Zip文件的支持比较好,可以添加多个Entry,甚至是子文件夹。下例把一个文件压缩到一个zip文件里,同时创建了一个子文件夹下的Entry:
- void testZipIOStream() throws IOException {
- // create a file to compress into a zip file
- FileWriter fw = new FileWriter("MyFile");
- fw.write("Just for fun");
- fw.flush();
- fw.close();
-
- ZipOutputStream zos = new ZipOutputStream(new FileOutputStream("MyZip.zip"));
- zos.setComment("Here's some comment");
- zos.putNextEntry(new ZipEntry("MyFile"));
- // compress MyFile into the zip file
- FileReader fr = new FileReader("MyFile");
- int b;
- while ((b=fr.read()) >= 0)
- zos.write(b);
- // another entry in a sub folder.
- zos.putNextEntry(new ZipEntry("Parent/SubFile"));
- zos.write('A');
- zos.flush();
- zos.close();
-
- ZipInputStream zis = new ZipInputStream(new FileInputStream("MyZip.zip"));
- // read MyFile
- ZipEntry ze = zis.getNextEntry();
- System.out.println(ze.getName()); // MyFile
- InputStreamReader isr = new InputStreamReader(zis);
- int c;
- while ((c=isr.read()) >= 0)
- System.out.print((char)c); // Output: Just for fun
- System.out.println();
- // read SubFile
- ze = zis.getNextEntry();
- System.out.println(ze.getName()); // Parent/SubFile
- System.out.println((char)isr.read()); // 'A'
- isr.close();
- }
JDK1.4引入了一个新的I/O类库:java.nio.*(nio = new io)。它通过通道(java.nio.channels.Channel)和缓冲器(java.nio.ByteBuffer)来提高I/O的效率。FileInputStream、FileOutputStream和RandomAccessFile都提供了一个getChannel的方法来使用通道传输数据。下面给一个简单的例子:
- void testChannel() throws IOException {
- FileChannel fc = new FileOutputStream("MyFile").getChannel();
- fc.write(ByteBuffer.wrap("string data".getBytes()));
- fc.close();
- fc = new RandomAccessFile("MyFile", "rw").getChannel();
- // locate to the end
- fc.position(fc.size());
- fc.write(ByteBuffer.wrap("\nAppend to the end".getBytes()));
- fc.close();
-
- fc = new FileInputStream("MyFile").getChannel();
- ByteBuffer bb = ByteBuffer.allocate(1024);
- fc.read(bb);
- bb.flip(); // prepare to read the buffer
- while (bb.hasRemaining())
- System.out.print((char)bb.get());
- }
ByteBuffer就是唯一可以直接和Channel交互的缓冲器。它有一点不方便的地方就是,当读取这个缓冲器的数据前,必须调用ByteBuffer.flip()方法,而在向这个缓冲器写数据前,要调用ByteBuffer.clear()方法。(clear方法并不清空已有的数据,待会再作详细说明)。这种不便利性也是为了提高存取速度而作的牺牲。
ByteBuffer只能通过静态方法ByteBuffer.wrap和ByteBuffer.allocate创建,它在被创建后大小(Capacity)就是固定的。它用一个position的整型值来指示当前读写的位置,同时用一个limit的整型成员来表示当前数据的大小(字节数)。
当向ByteBuffer里写数据时,应该先调用clear方法,它会把postion设为0同时把limit设为和Capacity一样。之后每调用put方法写数据时,position就向后移n个字节。当position超过limit时,就抛出异常。
在读数据前,应该先调用flip方法,它会把limit值设为已写数据的尾部,并把position设为0。读时可以用hasRemaining方法来判断是否还有可读数据。remaining等于limit与position的差值。
你还可以用mark和reset方法来标记和重置缓冲区的某个特定的位置。
在上面的代码里,我们使用ByteBuffer直接读写字符串。我们也可以使用CharBuffer:
- testCharBuffer() throws UnsupportedEncodingException {
- ByteBuffer bb = ByteBuffer.wrap("some text".getBytes());
- System.out.println(bb.asCharBuffer()); // Output: ????
-
- bb = ByteBuffer.allocate(1024);
- bb.put("some text".getBytes("UTF-16BE"));
- bb.flip(); // after "put" call this to read
- bb.rewind(); // return to the beginning
- System.out.println(bb.asCharBuffer()); // Output: some text
-
- bb.clear(); // only reset the position, won't erase the content
- CharBuffer cb = bb.asCharBuffer();
- cb.put("other");
- cb.flip();
- System.out.println(cb); // Output: other
- }
CharBuffer是ByteBuffer的一个
视图缓冲器,即它底层和ByteBuffer共用一块缓冲区,但维护独自的position和limit等成员。此外还有IntBuffer、DoubleBuffer等视图缓冲器。注意到上面的代码里,在用裸缓冲器写字符串我们必须把对它进行
UTF-16BE的编码,否则CharBuffer不能识别。
你可以用Channel的TransferTo和TransferFrom方法在两个通道间传递数据:
- void testTransferChannel() throws IOException {
- FileChannel in = new FileInputStream("MyFile").getChannel();
- FileChannel out = new FileOutputStream("Other").getChannel();
- in.transferTo(0, in.size(), out);
- }
有了通道,我们就可以使用
MappedByteBuffer把一个文件(整个或部份地)映射到内存中进行读写:
- void testMappedBuffer() throws FileNotFoundException, IOException {
- int start = 2;
- int length = 12;
- MappedByteBuffer mbb = new RandomAccessFile("MyFile", "rw").getChannel().
- map(FileChannel.MapMode.READ_WRITE, start, length);
- while (mbb.hasRemaining())
- System.out.print((char)mbb.get());
- }
内存映射的读写比直接使用I/O stream要快速很多,同时还能解决大文件不能全部装入内存的问题。
我们可以对文件或其中的某一部份进行
加锁:
- void testFileLock() throws IOException {
- FileOutputStream fos = new FileOutputStream("MyFile");
- FileLock fl = fos.getChannel().tryLock();
- if (fl != null) {
- System.out.println("Got lock!");
- fl.release();
- }
-
- int start = 2;
- int length = 12;
- boolean shared = false;
- FileLock fl_part = fos.getChannel().tryLock(start, length, shared);
- if (fl_part != null) {
- System.out.println("Got part lock!!");
- fl.release();
- }
-
- fos.close();
- }
java对序列化的支持作的非常好,只要让需要序列化的类实现Serializable接口,而这个接口没有任何方法。之后使用ObjectOutputStream和ObjectInputStream就可以读写对象了:
- class MyClass implements Serializable {
- public void print() {
- System.out.println("MyClass - " + i);
- }
- private int i;
- {
- i = new Random().nextInt(199);
- }
- }
- void testSerialization() throws FileNotFoundException, IOException, ClassNotFoundException {
- MyClass mc = new MyClass();
- mc.print();
-
- ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("MyFile"));
- oos.writeObject(mc);
- oos.close();
-
- ObjectInputStream ois = new ObjectInputStream(new FileInputStream("MyFile"));
- MyClass mc2 = (MyClass)ois.readObject();
- ois.close();
- mc2.print();
- }
如果你不想使用java自带的序列化方式(直接把二进制数据写入内存),可以实现Externalizable接口。它有两个方法:writeExternal和readExternal。想要使用Externalizable接口的类必须提供一个public的不带参数的构造器。看一个例子:
- class MyExternalizable implements Externalizable {
- @Override
- public void readExternal(ObjectInput in) throws IOException,
- ClassNotFoundException {
- i = in.readInt();
- }
- @Override
- public void writeExternal(ObjectOutput out) throws IOException {
- out.writeInt(i);
- }
-
- // A public constructor without arguments is a must!!
- public MyExternalizable() {}
-
- public void print() {
- System.out.println(i);
- }
-
- private int i;
- {
- i = new Random().nextInt(198);
- }
- }
接下来就可以和Serializable一样进行序列化了。如果你还是想用Serializable,但只想序列化对象里的其中一部份成员,可以用
transient关键字:
- class MySerializable implements Serializable {
- private int i;
- private transient int j;
- }
如果你想实现Serializable,同时又想定义自己序列化的方法,也是可以的。只要实现
readObject和
writeObject方法。这不是覆盖,而是通过反射被调用:
- class MySerializable implements Serializable {
- private int i;
- private transient int j;
-
- private void writeObject(ObjectOutputStream out) throws IOException {
- out.defaultWriteObject(); // use default behavior
- out.writeInt(j);
- }
-
- private void readObject(ObjectInputStream in) throws ClassNotFoundException, IOException {
- in.defaultReadObject();
- j = in.readInt();
- }
- }
我们还可以用java.util.prefs.Preferences向注册表读写数据:
- void testPreferences() throws BackingStoreException {
- Preferences p = Preferences.userNodeForPackage(IOTest4.class);
- p.putInt("MyKey", 8); // write to registry
-
- int n = p.getInt("MyKey", 0); // read from registry
- System.out.println(n);
-
- p.removeNode();
- }
阅读(1241) | 评论(0) | 转发(0) |