Java IO 之三:File、RandomAccessFile、重定向输入输出

一、File类

在Java中,有一个File类,专门用来处理和文件或者文件夹相关的事情,其包含的方法有很多,这里就不一一列出,链接在http://docs.oracle.com/javase/7/docs/api/java/io/File.html ,在前两篇文章中也很多地方用到了File类,不多提,有时间将平时封装好的File类贴过来。

二、RandomAccessFile

该类就是说,我们可以随机读写文件的数据。我们要注意,该类不是InputStream的子类!其构造函数有两个

RandomAccessFile(File file, String mode) 

Creates a random access file stream to read from, and optionally to write to, the file specified by the File argument.
RandomAccessFile(String name, String mode) 

Creates a random access file stream to read from, and optionally to write to, a file with the specified name.

查看RandomAccessFile的源文件,我们可以发现

    public RandomAccessFile(String name, String mode)
        throws FileNotFoundException
    {
        this(name != null ? new File(name) : null, mode);
    }

RandomAccessFile(String name, String mode)构造函数也是调用的第一个构造函数。

传入的mode一个有4中选择

Value

Meaning

"r" Open for reading only. Invoking any of the write methods of the resulting object will cause an IOException to be thrown.
"rw" Open for reading and writing. If the file does not already exist then an attempt will be made to create it.
"rws" Open for reading and writing, as with "rw", and also require that every update to the file’s content or metadata be written synchronously to the underlying storage device.
"rwd"   Open for reading and writing, as with "rw", and also require that every update to the file’s content be written synchronously to the underlying storage device.

其中’r’,’rw’两个参数比较容易理解,主要是看一下 ‘rws’,’rwd’的区别是什么?

看定义来说,都是rws是要求每一个更新或者元数据都要同步到底层的存储设备,而rwd是同步每一个更新到底层的存储设备,还是不明白到底区别是什么?从源码里面找一找。

RandomAccessFile(File file, String mode)构造函数中,我们发现

字符类型的mode传入之后,首先会被处理一下,其关系如下:

‘r’ :O_RDONLY=1=(byte)00000001

‘rw’:O_RDWR=2=(byte)00000010

‘rws’:O_RDWR|O_SYNC=2|4=(byte)00000110

‘rwd’:O_RDWR|O_DSYNC=2|8=(byte)00001010

有些眉目了吧,再往下看,在构造函数的最后,程序调用了一个

open(name,imode)

看一下该函数的声明吧

    private native void open(String name, int mode)
        throws FileNotFoundException;

调用了底层函数,好了,下一步不知道怎么找了,不过再仔细想想,底层的话,Linux自带的系统库中也有open函数,别的不说,mode的属性定义应该是相似的把,我们查查,调用

man 2 open

看到里面找到这么一段

POSIX  provides  for  three different variants of synchronized I/O, corresponding to the flags O_SYNC, O_DSYNC, and O_RSYNC.
       Currently (2.6.31), Linux only implements O_SYNC, but glibc maps O_DSYNC and O_RSYNC to the same numerical value as  O_SYNC.
       Most  Linux  file systems don't actually implement the POSIX O_SYNC semantics, which require all metadata updates of a write
       to be on disk on returning to userspace, but only the O_DSYNC semantics, which require only actual file  data  and  metadata
       necessary to retrieve it to be on disk by the time the system call returns.

好了,大体明白了,POSIX标准中总共只是三种不同的I/O同步模式,O_SYNC,O_DSYNC,O_RSYNC,并且在Linux2.6.31版本的内核上,该三种同步模式,其实际上的实现是一致的,大体明白了,就是同步的属性,但是有metadata和没有这个的区别在于什么呢?

但还是不太明白到底是怎么回事,继续谷歌,关键字 POSIX,O_SYNC,O_DSYNC,找到这样一片文章

http://www.taobaodba.com/html/326_innodb_flush_method-%E4%B8%8E-linux-file-io.html

里面有一段:

O_DSYNC告诉内核,当向文件写入数据的时候,只有当数据写到了磁盘时,写入操作才算完成(write才返回成功)。和O_DSYNC同类的文件标志,还有O_SYNC,O_RSYNC,O_DIRECT。

  • O_SYNC比O_DSYNC更严格,不仅要求数据已经写到了磁盘,而且对应的数据文件的属性(例如文件长度等)也需要更新完成才算write操作成功。可见O_SYNC较之O_DSYNC要多做一些操作。
  • O_RSYNC表示文件读取时,该文件的OS cache必须已经全部flush到磁盘了【附录3】
  • 如果使用O_DIRECT打开文件,则读/写操作都会跳过OS cache,直接在device(disk)上读/写。因为没有了OS cache,所以会O_DIRECT降低文件的顺序读写的效率。

好了,这样子就搞懂是怎么回事了,搞定。(心里面有个小小的疑问,Java的底层native库编译的库是怎么编译的呢?在Linux基础上是不是就是依赖于Linux的系统库呢?那是不是实际在Linux上使用Java的时候 O_DSYNC,和O_SYNC也是没有区别的呢?)。

在RandomAccessFile的其他方法中,我们基本上都比较熟悉,注意下面几个

<1>RandomAccessFile实际上是使用Channel来实现的,而Channel和Buffer是Java NIO中重要的一个内容,有时间继续写一些,getFilePointer()函数则返回当前的指针。

FileChannel getChannel() 

Returns the unique FileChannel object associated with this file.
FileDescriptor getFD() 

Returns the opaque file descriptor object associated with this stream.
long getFilePointer() 

Returns the current offset in this file.

<2>RandomAccessFile顾名思义,关键就在于要随机读写,不能制定位置怎么行,下面几个就可以用的到。

void seek(long pos) 

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

Sets the length of this file.
int skipBytes(int n) 

Attempts to skip over n bytes of input discarding the skipped bytes.

<3>read和readFullly的区别是什么,除了表面上的意思有什么什么细微的区别,

int read(byte[] b) 

Reads up to b.length bytes of data from this file into an array of bytes.
int read(byte[] b, int off, int len) 

Reads up to len bytes of data from this file into an array of bytes.
void readFully(byte[] b) 

Reads b.length bytes from this file into the byte array, starting at the current file pointer.
void readFully(byte[] b, int off, int len) 

Reads exactly len bytes from this file into the byte array, starting at the current file pointer.

看一下定义

public int read(byte[] b)
         throws IOException
Reads up to b.length bytes of data from this file into an array of bytes. This method blocks until at least one byte of input is available.Although RandomAccessFile is not a subclass of InputStream, this method behaves in exactly the same way as the InputStream.read(byte[]) method ofInputStream

虽然说RandomAccessFile不是InputStream的子类,但在read(byte[] b)使用上和InputStream中的该函数是一样的,调用该函数后会读取数据到数组中,如果没有数据便会一直阻塞,知道有数据过来。

public final void readFully(byte[] b)
                     throws IOException
Reads b.length bytes from this file into the byte array, starting at the current file pointer. This method reads repeatedly from the file until the requested number of bytes are read. This method blocks until the requested number of bytes are read, the end of the stream is detected, or an exception is thrown.

看到这里就明白了,该方法会一直阻塞,知道byte[] b这个数组读满了,看看源文件呢?里面调用的是本地库,我们现在主要是去学习Java,所以JVM里面怎么实现的我们就先不去关注了,好了,明白区别也就可以放心的使用了。

 

package InputStream;

import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.RandomAccessFile;

public class TestRandomAccessFile {
	public static void main(String[] args)
	{
		RandomAccessFile randomAccessFile;
		try
		{
			long startTime;
			File file=new File("TestRandomAccessFile.data");
			if(file.exists())
				file.delete();
			randomAccessFile=new RandomAccessFile("TestRandomAccessFile.data","rwd");

			startTime=System.currentTimeMillis();
			randomAccessFile.writeChar('A');
			System.out.println("打开同步,每个char类型,写入耗时:"+(System.currentTimeMillis()-startTime)+"ms");

			startTime=System.currentTimeMillis();
			for(char i=33;i<127;i++)
			{
				randomAccessFile.writeChar(i);
			}
			System.out.println("打开同步,写入127-33个字符耗时:"+(System.currentTimeMillis()-startTime)+"ms");
			startTime=System.currentTimeMillis();
			randomAccessFile.close();
			System.out.println("打开同步,关闭文件耗时:"+(System.currentTimeMillis()-startTime)+"ms");

			file=new File("TestRandomAccessFile.data");
			randomAccessFile=new RandomAccessFile(file,"rw");

			startTime=System.currentTimeMillis();
			randomAccessFile.writeChar('A');
			System.out.println("关闭同步,每个char类型,写入耗时:"+(System.currentTimeMillis()-startTime)+"ms");

			startTime=System.currentTimeMillis();
			for(char i=33;i<127;i++)
			{
				randomAccessFile.writeChar(i);
			}
			System.out.println("关闭同步,写入127-33个字符耗时:"+(System.currentTimeMillis()-startTime)+"ms");
			startTime=System.currentTimeMillis();
			randomAccessFile.close();
			System.out.println("关闭同步,关闭文件耗时:"+(System.currentTimeMillis()-startTime)+"ms");

			file=new File("TestRandomAccessFile.data");
			System.out.println("File length:"+file.length());

			randomAccessFile=new RandomAccessFile(file, "r");
			while(true)
			{
				char tmp=randomAccessFile.readChar();
				System.out.print(tmp);
			}
		}
		catch (FileNotFoundException e) {
			// TODO: handle exception
		}
		catch (EOFException e)
		{
			System.out.println("\n数据读取完毕!");
		}
		catch (IOException e)
		{
		}	
		finally{
			System.out.println("Finished!!!");
		}

	}
}

运行结果为

打开同步,每个char类型,写入耗时:69ms
打开同步,写入127-33个字符耗时:6582ms
打开同步,关闭文件耗时:0ms
关闭同步,每个char类型,写入耗时:0ms
关闭同步,写入127-33个字符耗时:0ms
关闭同步,关闭文件耗时:0ms
File length:190
A!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
数据读取完毕!
Finished!!!

在上面的程序中,主要完成了将数据写入某一个文件之后,重新打开文件,并从文件尾部追加数据。同时还统计了在打开同步和关闭同步模式的情况下,所消耗时间的统计。可以看出,当打开同步之后,每次写入数据都会同步到磁盘,该过程消耗了时间,是我们需要注意的,在条件不严格不苛刻的时候,没有必要打开同步功能。

三、System.in\System.out

在System中,有标准输入、标准输出、错误输出3个流,

static PrintStream err

The “standard” error output stream.
static InputStream in

The “standard” input stream.
static PrintStream out

The “standard” output stream.

而且这三个流我们都可以手动设置,也比较简单,留个标签,关于这边遇到好玩的过来填上。

四、获取其他进程的输入输出流

在Process类中,定义了3个方法

abstract InputStream getErrorStream()

Returns the input stream connected to the error output of the subprocess.
abstract InputStream getInputStream()

Returns the input stream connected to the normal output of the subprocess.
abstract OutputStream getOutputStream()

Returns the output stream connected to the normal input of the subprocess.

这样我们就能从相关的进程中拿到输入输出流,从而控制程序的运行,简单哈。ACM比赛评分的工作也可以用这个来完成。

 

 

About: happyhls


发表评论

电子邮件地址不会被公开。 必填项已用*标注