java中的IO基础
java中的I/O类库设计可谓是比较丰富的,在我们平时的编程中也经常接触到,往往大部分的系统都有对IO操作的一些封装代码,平时要用到往往翻翻api或者找个写好的方法复制就搞定,由此带来的是对java本身提供的这些方法不熟悉,平时不好好梳理下,对java的io包下面这些常用类也就比较凌乱了。所以这里通过api文档和java.io下面的源码去整理下。
1、表示字节输入输出流的所有类的超类(InputStream/OutputStream)
构造方法:
InputStream() 创建一个输入的stream流
方法:
int available():返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数。
void close():关闭此输入流并释放与该流关联的所有系统资源。
abstract int read():从输入流中读取数据的下一个字节。
int read(byte[] b):从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中。
int read(byte[] b, int off, int len):将输入流中最多 len 个数据字节读入 byte 数组。
long skip(long n):跳过和丢弃此输入流中数据的 n 个字节。
boolean mark(int readlimit):在此输入流中标记当前的位置。
boolean markSupported():测试此输入流是否支持 mark 和 reset 方法。
void reset():将此流重新定位到最后一次对此输入流调用 mark 方法时的位置。
从上面看到,read()方法是abstract修饰的,是依靠子类去实现的,当到达流的末尾时,返回-1。
markSupported,mark,reset一般配合使用。
InputStream的available()方法返回0,close()方法是空实现,默认markSupported()方法返回false。
1.1 ByteArrayInputStream 包含一个内部缓冲区的输入流。
ByteArrayInputStream继承了InputStream,提供了两个以字节为输入的构造函数。
ByteArrayInputStream实现了自己的read方法,同时重写了markSupported()方法返回true,表示支持标记,也就支持mark()和reset()方法。
另一方面,ByteArrayInputStream的close()方法是空实现的,是没有作用的,关闭流后任然可以被调用,不会抛出IOException。
1.2 FileInputStream 从文件系统中的某个文件中获得输入字节。
FileInputStream实现了InputStream的部分方法,提供三个构造方法来构造文件输入流。
并且增加了两个额外的方法:
FileChannel getChannel():返回与此文件输入流有关的唯一 FileChannel 对象,使用java的NIO就会经常用到这个方法。
FileDescriptor getFD():返回表示到文件系统中实际文件的连接的 FileDescriptor 对象,该文件系统正被此 FileInputStream 使用。
1.3 PipeInputStream 管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。
管道输入流应该连接到管道输出流;管道输入流提供要写入管道输出流的所有数据字节。通常,数据由某个线程从 PipedInputStream 对象读取,并由其他线程将其写入到相应的 PipedOutputStream。不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。管道输入流包含一个缓冲区,可在缓冲区限定的范围内将读操作和写操作分离开。
增加了两个方法:
void connect(PipedOutputStream src):使此管道输入流连接到管道输出流 src。
protected void receive(int b):接收数据字节。
1.4 StringBufferInputStream 已过时。 此类未能正确地将字符转换为字节。
1.5 SequenceInputStream 表示其他输入流的逻辑串联。
SequeneceInputStream从输入流的有序集合开始,并从第一个输入流开始读取,直到到达文件末尾,接着从第二个输入流读取,依次类推,直到到达包含的最后一个输入流的文件末尾为止。
1.6 ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
ObjectInputStream在继承了InputStream的基础上,实现了ObjectInput接口,而ObjectInput接口继承了DataInput接口,提供了对java对象和基本数据类型的读取操作。
ObjectInputStream(InputStream in):创建从指定 InputStream 读取的 ObjectInputStream。
注意:只有支持 java.io.Serializable 或 java.io.Externalizable 接口的对象才能从流读取。
1.7 FilterInputStream 包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。
java中的IO再设计上使用了装饰器模式,FilterInputStream便是装饰角色,而它的子类则是具体装饰角色,FilterInputStream 类本身只是简单地重写那些将所有请求传递给所包含输入流的 InputStream 的所有方法。FilterInputStream 的子类可进一步重写这些方法中的一些方法,并且还可以提供一些额外的方法和字段。
1.7.1 DataInputStream 数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。
DataInputStream是FilterInputStream 的子类,同样,它也实现了DataInput接口,提供了对对象和基本类型的读取操作。
DataInputStream对于多线程访问不一定是安全的。
1.7.2 BufferedInputStream 为另一个输入流添加一些功能,即缓冲输入以及支持 mark 和 reset 方法的能力。
BufferedInputStream继承了FilterInputStream,在创建 BufferedInputStream 时,会创建一个内部缓冲区数组。在读取或跳过流中的字节时,可根据需要从包含的输入流再次填充该内部缓冲区,一次填充多个字节。
1.7.3 PushbackInputStream 为另一个输入流添加性能,即“推回 (push back)”或“取消读取 (unread)”一个字节的能力。
在实现FilterInputStream部分方法的同时,提供了三个额外的方法,实现“推回”或“取消读取”
void unread(byte[] b):推回一个 byte 数组:将其复制到推回缓冲区之前。
void unread(byte[] b, int off, int len):推回 byte 数组的某一部分:将其复制到推回缓冲区之前。
void unread(int b):推回一个字节:将其复制到推回缓冲区之前。
2. 用于读取和写入字符流的抽象类(Reader/Writer)
构造方法:
protected Reader():创建一个新的字符流 reader,其重要部分将同步其自身的 reader。
protected Reader(Object lock):创建一个新的字符流 reader,其重要部分将同步给定的对象。
方法:
abstract void close():关闭该流并释放与之关联的所有资源。
int read():读取单个字符。
int read(char[] cbuf):将字符读入数组。
abstract int read(char[] cbuf, int off, int len):将字符读入数组的某一部分。
int read(CharBuffer target):试图将字符读入指定的字符缓冲区。
boolean ready():判断是否准备读取此流。
void reset():重置该流。
void mark(int readAheadLimit):标记流中的当前位置。
boolean markSupported():判断此流是否支持 mark() 操作。
long skip(long n):跳过字符。
可以看出,Reader和InputStream的提供的方法基本类似,Reader子类必须实现的方法只有 read(char[], int, int) 和 close(),ready()方法默认返回false,默认不支持markSupported,子类都可以重写。
Reader的已知子类基本和InputStream有一一对应的关系,这里就不列举说明了。
关于InputStream和Reader需要注意的几点:
1) 对于流的装饰,字节流使用FilterInputStream和FilterOutputStream来修改流以满足特殊需求,而字符流的类继承结构继续沿用相同思想,但并不完全相同。BufferedWriter并不是FilterWriter的子类。FilterWriter是抽象类但没有任何子类,把它放在那里只作为占位符。
2)DataInputStream也提供了readLine()方法,但是这个方法已经被废弃,读取文本行的首选方法是使用 BufferedReader.readLine() 方法。
使用 DataInputStream 类读取文本行的程序可以改为使用BufferedReader 类,只要将以下形式的代码:DataInputStream d = new DataInputStream(in);
替换为:BufferedReader d = new BufferedReader(new InputStreamReader(in));
3. 字节流通向字符流的桥梁(InputStreamReader)和字符流通向字节流的桥梁(OutputStreamWriter)
InputStreamReader 是字节流通向字符流的桥梁,它使用的字符集可以由名称指定或显式给定,或者可以接受平台默认的字符集。
每次调用 InputStreamReader 中的一个 read() 方法都会导致从底层输入流读取一个或多个字节。要启用从字节到字符的有效转换,可以提前从底层流读取更多的字节,使其超过满足当前读取操作所需的字节。
为了达到最高效率,可要考虑在 BufferedReader 内包装 InputStreamReader。例如:
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
OutputStreamReader和InputStreamWriter类似,这里不加赘述。
public static void readFile(String fileName) throws IOException { InputStream inputStream = new FileInputStream(fileName); final int length = inputStream.available();//可以不受阻塞地从此输入流读取(或跳过)的估计剩余字节数 System.out.println("流大小>>>>>" + length); System.out.println("FileInputStream是否支持markSupported()方法:" + inputStream.markSupported()); System.out.println("========================================================"); //将文件流装饰成BufferedInputStream,它为我们提供了mark和reset方法 BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); System.out.println("bufferedInputStream是否支持markSupported()方法:" + bufferedInputStream.markSupported()); bufferedInputStream.mark(0);//在第0处标记 char firstChar = (char)bufferedInputStream.read();//读取下一个字节数据,如果到达流末尾,则返回 -1 System.out.println("文件的第一个字节为:" + firstChar); char SecChar = (char)bufferedInputStream.read();//读取下一个字节数据,如果到达流末尾,则返回 -1 System.out.println("文件的第二个字节为:" + SecChar); bufferedInputStream.reset();//回到标记位置 char afterResetChar = (char)bufferedInputStream.read(); System.out.println("reset方法后读取字节为:" + afterResetChar); bufferedInputStream.reset(); System.out.println("========================================================"); //再进行一次包装,用来读取字符//使用DataInputStream包装,提供java基本类型和对象的读取功能
//DataInputStream一般和DataOutputStream配套使用,DataInputStream读取通过DataOutputStream写入的数据
//要读取文件中的中文字符,使用Reader相关类 //DataInputStream dataInputStream = new DataInputStream(bufferedInputStream); //String firstUTF = dataInputStream.readUTF();//读取第一个中文字符 //System.out.println("文件第一个字符为:" + firstUTF); //dataInputStream.reset();//由BufferedInputStream支持 System.out.println("========================================================"); InputStreamReader inputStreamReader = new InputStreamReader(dataInputStream, "UTF-8");
//进一步装饰成Reader,提供字符相关操作功能,提供readLine() BufferedReader bufferedReader = new BufferedReader(inputStreamReader); String firstLine = bufferedReader.readLine();//读取第一行 System.out.println("第一行内容为:" + firstLine); System.out.println("========================================================"); LineNumberReader lineNumberReader = new LineNumberReader(bufferedReader); System.out.print("当前行号:" + lineNumberReader.getLineNumber()); }
4. 一个自我独立的类(RandomAccessFile)支持对随机访问文件的读取和写入。
先看一下RandomAccessFile的方法定义:
public class RandomAccessFile implements DataOutput, DataInput, Closeable{}
可以看到,RandomAccessFile不使用InputStream和OutputStream中已有的任何功能,是一个独立的类。它适用于由大小已知的记录组成的文件,可以用seek()方法将记录一处转移到另一处,然后读取或修改记录,它实现了DataOutput和DataInput,可以提供读写功能。
先看一下DataInput接口的基本方法(DataOutput对应不赘述):
public interface DataInput { void readFully(byte[] var1) throws IOException; void readFully(byte[] var1, int var2, int var3) throws IOException; int skipBytes(int var1) throws IOException; boolean readBoolean() throws IOException; byte readByte() throws IOException; int readUnsignedByte() throws IOException; short readShort() throws IOException; int readUnsignedShort() throws IOException; char readChar() throws IOException; int readInt() throws IOException; long readLong() throws IOException; float readFloat() throws IOException; double readDouble() throws IOException; String readLine() throws IOException; String readUTF() throws IOException; }
RandomAccessFile的构造函数:
RandomAccessFile(File file, String mode):创建从中读取和向其中写入(可选)的随机访问文件流,该文件由 File 参数指定。
RandomAccessFile(String name, String mode):创建从中读取和向其中写入(可选)的随机访问文件流,该文件具有指定名称。
RandomAccessFile在继承和实现了DataInput和DataOutput的基础上增加了自己的方法:
FileChannel getChannel():返回与此文件关联的唯一 FileChannel 对象。
FileDescriptor getFD():返回与此流关联的不透明文件描述符对象。
long getFilePointer():返回此文件中的当前偏移量。
void seek(long pos):设置到此文件开头测量到的文件指针偏移量,在该位置发生下一个读取或写入操作。
void setLength(long newLength):设置此文件的长度。
public static void randomAccessFileTest() throws IOException{ //RandomAccessFile使用于大文件的读取 RandomAccessFile randomAccessFile = new RandomAccessFile("D:\\testIo2.txt","rw"); randomAccessFile.writeBytes("hello world"); randomAccessFile.writeBoolean(false); randomAccessFile.writeUTF("你好!"); randomAccessFile.seek(12);//跳过hello world boolean booleanRead = randomAccessFile.readBoolean(); System.out.print("读取boolean:" + booleanRead); randomAccessFile.seek(0); String firstLine = randomAccessFile.readLine(); System.out.print("指针移动到第一个字节读取一行:" + firstLine); randomAccessFile.readBoolean(); String utfRead = randomAccessFile.readUTF(); System.out.print("读取字符:" + utfRead); }