《java i o》百度百科_《Thinking in Java 第4版》(十四)——Java I/O系统

先看一下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 data = new ArrayList();

}

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();

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值