Java I/O 笔记

java

1. Java常用I/O类概述

2. 文件I/O

  你可以根据该文件是二进制文件还是文本文件来选择使用FileInputStream(FileOutputStream)或者FileReader(FileWriter)。

  这几个类允许你从文件开始到文件末尾一次读取一个字节或者字符,或者将读取到的字节写入到字节数组或者字符数组。你不必一次性读取整个文件,相反你可以按顺序地读取文件中的字节和字符。

  如果你需要跳跃式地读取文件其中的某些部分,或者想同时进行读写,可以使用RandomAccessFile。

  你可能需要读取文件的信息而不是文件的内容,比如文件大小、属性、文件夹下的文件列表,可以使用File。

3. 管道I/O

  Java IO中的管道为运行在同一个JVM中的两个线程提供了通信的能力。

  不能利用管道与不同的JVM中的线程通信(不同的进程)。在概念上,Java的管道不同于Unix/Linux系统中的管道。在Unix/Linux中,运行在不同地址空间的两个进程可以通过管道通信。在Java中,通信的双方应该是运行在同一进程中的不同线程。

   通过Java IO中的PipedOutputStream和PipedInputStream创建管道。一个PipedInputStream流应该和一个PipedOutputStream流相关联。一个线程通过PipedOutputStream写入的数据可以被另一个线程通过相关联的PipedInputStream读取出来。

  

  也可以使用两个管道共有的connect()方法使之相关联。PipedInputStream和PipedOutputStream都拥有一个可以互相关联的connect()方法。

  请记得,当使用两个相关联的管道流时,务必将它们分配给不同的线程。read()方法和write()方法调用时会导致流阻塞,这意味着如果你尝试在一个线程中同时进行读和写,可能会导致线程死锁。

4. 网络I/O

  当两个进程之间建立了网络连接之后,他们通信的方式如同操作文件一样:利用InputStream读取数据,利用OutputStream写入数据。换句话来说,Java网络API用来在不同进程之间建立网络连接,而Java IO则用来在建立了连接之后的进程之间交换数据。

5. 字节、字符数组

  用ByteArrayInputStream或者CharArrayReader封装字节或者字符数组,从数组(类似于文件)中读取数据。通过这种方式字节和字符就可以以数组的形式读出了。

byte[] bytes = new byte[1024];

<把数据写入字节数组>

InputStream input = new ByteArrayInputStream(bytes);

int data = input.read();

while(data != -1) {

<操作数据>

data = input.read();

}

  同样,也可以把数据写到ByteArrayOutputStream或者CharArrayWriter中。当所有的数据都写进去了以后,只要调用toByteArray()或者toCharArray,所有写入的数据就会以数组的形式返回。

ByteArrayOutputStream output = new ByteArrayOutputStream();

output.write("asasasa".getBytes());

System.out.println(output.toByteArray());

6.  System.in, System.out, System.err

  System.in是一个典型的连接控制台程序和键盘输入的InputStream流。

  System.out是一个PrintStream流。System.out一般会把你写到其中的数据输出到控制台上。

  System.err是一个PrintStream流。System.err与System.out的运行方式类似,但它更多的是用于打印错误文本。一些类似Eclipse的程序,为了让错误信息更加显眼,会将错误信息以红色文本的形式通过System.err输出到控制台上。

  System.out.print() 调用原理:

public final static PrintStream out = nullPrintStream();

............

private static PrintStream nullPrintStream() throws NullPointerException {

if (currentTimeMillis() > 0) {

return null;

}

throw new NullPointerException();

}

  可以看出out是system类的静态成员,所以可以通过system.out直接访问到。刚开始out是null,后面会调用initializeSystemClass()方法给out赋值。

private static void initializeSystemClass() {

props = new Properties();

initProperties(props);

sun.misc.Version.init();

FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);

.................

setOut0(new PrintStream(new BufferedOutputStream(fdOut, 128), true));

public static final FileDescriptor out = standardStream(1);

...

private static FileDescriptor standardStream(int fd) {

FileDescriptor desc = new FileDescriptor();

desc.handle = set(fd);

return desc;

}

  这时候返回了 handle为1的FileDescriptor; 在传统的unix的系统中,文件描述符为0,1,2分别表示为标准输入,标准输出和错误输出。最后调用了setout0方法。

  到这里,setout0是native方法。(http://www.cnblogs.com/zr-714/archive/2012/03/22/2411926.html)

  另外,在system类中有setout方法,用来重置输出流对象。

public static void setOut(PrintStream out) {

checkIO();

setOut0(out);

}

  尽管System.in, System.out, System.err这3个流是java.lang.System类中的静态成员,并且已经预先在JVM启动的时候初始化完成,你依然可以更改它们。只需要把一个新的InputStream设置给System.in或者一个新的OutputStream设置给System.out或者System.err,之后的数据都将会在新的流中进行读取、写入。

  可以使用System.setIn(), System.setOut(), System.setErr()方法设置新的系统流。

OutputStream output = new FileOutputStream("c:\\data\\system.out.txt");

PrintStream printOut = new PrintStream(output);

System.setOut(printOut);

  现在所有的System.out都将重定向到”c:\\data\\system.out.txt”文件中。请记住,务必在JVM关闭之前冲刷System.out(译者注:调用flush()),确保System.out把数据输出到了文件中。

7. 流

  流和数组不一样,不能通过索引读写数据。在流中,你也不能像数组那样前后移动读取数据,除非使用RandomAccessFile处理文件。流仅仅只是一个连续的数据流。

  某些类似PushbackInputStream流的实现允许你将数据重新推回到流中,以便重新读取。然而你只能把有限的数据推回流中,并且你不能像操作数组那样随意读取数据。流中的数据只能够顺序访问。

  InputStream的read()方法返回一个字节,意味着这个返回值的范围在0到255之间(当达到流末尾时,返回-1),Reader的read()方法返回一个字符,意味着这个返回值的范围在0到65535(16位)之间(当达到流末尾时,同样返回-1)。这并不意味着Reade只会从数据源中一次读取2个字节,Reader会根据文本的编码,一次读取一个或者多个字节。

public static void main(String[] args) throws IOException {

char s = '我';

System.out.println("unicode:" + (int)s);

     

OutputStream output = new FileOutputStream("test.txt");

output.write("我".getBytes()); //windows下默认为gbk格式编码

output.close();

InputStream input = new FileInputStream("test.txt");

System.out.print("InputStream:");

int data = input.read();

while(data != -1){

System.out.print(data+" ");

data = input.read();

}

System.out.println();

input.close();

Reader input2 = new FileReader("test.txt");

System.out.print("Reader:");

data = input2.read();

while(data != -1){

System.out.print(data);

data = input2.read();

}

input2.close();

}

  运行结果:

unicode:25105

InputStream:206 210

Reader:25105

  即Java内部采用Unicode编码,char类型固定占2个字节(16)位。

  需要注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。比如,汉字"严"的unicode是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。

  互联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种Unicode的实现方式。UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。(http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html)

  "我"这个中文字符的unicode就是2个字节。String.getBytes(encoding)方法是获取指定编码的byte数组表示,通常gbk/gb2312是2个字节,utf-8是3个字节。如果不指定encoding则取系统默认的encoding。

  在编译的时候,如果我们没有用-encoding参数指定我们的JAVA源程序的编码格式,则javac.exe首先获得我们操作系统默认采用的编码格式,也即在编译java程序时,若我们不指定源程序文件的编码格式,JDK首先获得操作系统的file.encoding参数(它保存的就是操作系统默认的编码格式,如WIN2k,它的值为GBK),然后JDK就把我们的java源程序从file.encoding编码格式转化为JAVA内部默认的UNICODE格式放入内存中。然后,javac把转换后的unicode格式的文件进行编译成.class类文件,此时.class文件是UNICODE编码的,它暂放在内存中,紧接着,JDK将此以UNICODE编码的编译后的class文件保存到我们的操作系统中形成我们见到的.class文件。对我们来说,我们最终获得的.class文件是内容以UNICODE编码格式保存的类文件,它内部包含我们源程序中的中文字符串,只不过此时它己经由file.encoding格式转化为UNICODE格式了。

  从这个意义上可以看出,面向字符的I/O类,也就是Reader和Writer类,实际上隐式做了编码转换,在输出时,将内存中的Unicode字符使用系统默认编码方式进行了编码,而在输入时,将文件系统中已经编码过的字符使用默认编码方案进行了还原。这里要注意,Reader和Writer只会使用这个默认的编码来做转换,而不能为一个Reader和Writer指定转换时使用的编码。这也意味着,如果使用中文版WindowsXP系统,其中存放了一个UTF8编码的文件,当采用Reader类来读入的时候,它还会用GBK来转换,转换后的内容当然不对。这其实是一种傻瓜式的功能提供方式,对大多数初级用户(以及不需要跨平台的高级用户,windows一般采用GBK,linux一般采用UTF8)来说反而是一件好事。

  如果用到GBK编码以外的文件,就必须采用编码转换:一个字符与字节之间的转换。因此,Java的I/O系统中能够指定转换编码的地方,也就是在字符与字节转换的地方,那就是InputStremReader与OutputStreamWriter。这两个类是字节流和字符流的适配器类,它们承担编码转换的任务。

8. I/O异常处理

try{

... //doSomethingWithData()

}catch(IOException e){

...

} finally {

try{

if(input != null)

input.close();

} catch(IOException e){

...

}

}

  这段解决了InputStream(或者OutputStream)流关闭的问题的代码,确实是有一些不优雅,尽管能够正确处理异常。如果你的代码中重复地遍布了这段丑陋的异常处理代码,这不是很好的一个解决方案。如果一个匆忙的家伙贪图方便忽略了异常处理呢?

  此外,想象一下某个异常最先从doSomethingWithData方法内抛出。第一个catch会捕获到异常,然后在finally里程序会尝试关闭InputStream。但是如果还有异常从close()方法内抛出呢?这两个异常中得哪个异常应当往调用栈上传播呢?

  从Java7开始,一种新的被称作“try-with-resource”的异常处理机制被引入进来(类似python的with关键字)。这种机制旨在解决针对InputStream和OutputStream这类在使用完毕之后需要关闭的资源的异常处理。

private static void printFileJava7() throws IOException {

try(FileInputStream input = new FileInputStream("file.txt")) {

int data = input.read();

while(data != -1){

System.out.print((char) data);

data = input.read();

}

}

}

9.  InputStream

  • read()方法,返回从InputStream流内读取到的一个字节内容。

  InputStream包含了2个从InputStream中读取数据并将数据存储到缓冲数组中的read()方法,他们分别是:

  • int read(byte[])
  • int read(byte, int offset, int length) --> 不常用

  read(byte[])方法会尝试读取与给定字节数组容量一样大的字节数,返回值说明了已经读取过的字节数。如果InputStream内可读的数据不足以填满字节数组,那么数组剩余的部分将包含本次读取之前的数据。记得检查有多少数据实际被写入到了字节数组中。

  read(byte, int offset, int length)方法同样将数据读取到字节数组中,不同的是,该方法从数组的offset位置开始,并且最多将length个字节写入到数组中。

10. OutputStream

  • write()
  • write(byte[])  把字节数组中所有数据写入到输出流中。
  • write(byte[], int offset, int length)
  • flush()  将所有写入到OutputStream的数据冲刷到相应的目标媒介中。即使所有数据都写入到了OutputStream,这些数据还是有可能保留在内存的缓冲区中。通过调用flush()方法,可以把缓冲区内的数据刷新到磁盘(或者网络,以及其他任何形式的目标媒介)中。

11. FileInputStream

  FileInputStream也有其他的构造函数,允许你通过不同的方式读取文件。其中一个FileInputStream构造函数取一个File对象替代String对象作为参数。

File file = new File("c:\\data\\input-text.txt");

InputStream input = new FileInputStream(file);

  至于你该采用参数是String对象还是File对象的构造函数,取决于你当前是否已经拥有一个File对象,也取决于你是否要在打开FileOutputStream之前通过File对象执行某些检查(比如检查文件是否存在)。

12. FileOutputStream

OutputStream output = new FileOutputStream("c:\\data\\output-text.txt", true); //appends to file

OutputStream output = new FileOutputStream("c:\\data\\output-text.txt", false); //overwrites file

13. File

Java IO API中的FIle类可以让你访问底层文件系统,通过File类,你可以做到以下几点:

  • 检测文件是否存在
  • 读取文件长度
  • 重命名或移动文件
  • 删除文件
  • 检测某个路径是文件还是目录
  • 读取目录中的文件列表

请注意:File只能访问文件以及文件系统的元数据。如果你想读写文件内容,需要使用FileInputStream、FileOutputStream或者RandomAccessFile。如果你正在使用Java NIO,并且想使用完整的NIO解决方案,你会使用到java.nio.FileChannel(否则你也可以使用File)。

14.  FilterInputStream

  这个类是装饰器模式在Java的io包中的一个应用。它为所有“装饰器”类提供了一个基类。感觉它的作用就仅限于这里。它的子类有:DataInputStream、BufferedInputStream、LineNumberInputStream、PushbackInputStream。

  FilterInputStream 包含其他一些输入流,它将这些流用作其基本数据源,它可以直接传输数据或提供一些额外的功能。FilterInputStream 类本身只是简单地重写那些将所有请求传递给所包含输入流的 InputStream 的所有方法。 FilterInputStream 的子类可进一步重写这些方法中的一些方法,并且还可以提供一些额外的方法和字段。

15. BufferedInputStream

  The BufferedInputStream class provides buffering to your input streams. Buffering can speed up IO quite a bit. Rather than read one byte at a time from the network or disk, the BufferedInputStream reads a larger block at a time. This is typically much faster, especially for disk access and larger data amounts.

InputStream input = new BufferedInputStream(

new FileInputStream("c:\\data\\input-file.txt"),

8 * 1024 //8KB

);

  一些inputstream流提供以下方法,如BufferedInputStream:

  • mark(int readlimit)

  在该输入流中标记当前位置。 后续调用 reset 方法重新将流定位于最后标记位置,以便后续读取能重新读取相同字节。readlimit 参数给出当前输入流在标记位置变为非法前允许读取的字节数。

  • reset()

   将此流重新定位到最后一次对此输入流调用 mark 方法时的位置。

  就是说mark就像书签一样,用于标记,以后再调用reset时就可以再回到这个mark过的地方。mark方法有个参数,通过这个整型参数,你告诉系统,希望在读出这么多个字符之前,这个mark保持有效。比如说mark(10),那么在read()10个以内的字符时,reset()操作后可以重新读取已经读出的数据,如果已经读取的数据超过10个,那reset()操作后,就不能正确读取以前的数据了,因为此时mark标记已经失效。   

 16. BufferedOutputStream

  you may need to call flush() if you need to be absolutely sure that the data written until now is flushed out of the buffer and onto the network or disk.  

17. PushbackInputStream 

  The PushbackInputStream is intendended to be used when you parse data from an InputStream. Sometimes you need to read ahead a few bytes to see what is coming, before you can determine how to interprete the current byte. The PushbackInputStream allows you to do that. Well, actually it allows you to push back the read bytes into the stream. These bytes will then be read again the next time you call read().

PushbackInputStream input = new PushbackInputStream(

new FileInputStream("c:\\data\\input.txt"),

8);

This example sets an internal buffer of 8 bytes. That means you can unread at most 8 bytes at a time, before reading them again.

18. SequenceInputStream 

The SequenceInputStream combines two or more other InputStream's into one. First all bytes from the first input stream is iterated and returned, then the bytes from the second input stream. 

InputStream input1 = new FileInputStream("c:\\data\\file1.txt");

InputStream input2 = new FileInputStream("c:\\data\\file2.txt");

InputStream combined = new SequenceInputStream(input1, input2);

The combined input stream can now be used as if it was one coherent stream.

19. DataInputStream 

The DataInputStream class enables you to read Java primitives from InputStream's instead of only bytes. You wrap an InputStream in a DataInputStream and then you can read primitives from it.

  

DataInputStream input = new DataInputStream(

new FileInputStream("binary.data"));

int aByte = input.read();

int anInt = input.readInt();

float aFloat = input.readFloat();

double aDouble = input.readDouble();

//etc.

input.close();

This is handy if the data you need to read consists of Java primitives larger than one byte each, like int, long, float, double etc.

20. PrintStream 

以上是 Java I/O 笔记 的全部内容, 来源链接: utcz.com/z/390314.html

回到顶部