JAVA I/O系统
java的I/O系统是一个大块头,包含的东西太多了(就贴接口表格就能贴的让人头晕目眩),而且内容顺序不好安排,横切有字节流字符流,竖切有输入输出,再加新旧几轮迭代,三个维度纵横交错,真的不好写,以至于写这篇博客之前心里纠结了很久才下定的决心。
因为不系统的梳理出来仔仔细细的过一遍,就不算是理解了。
本篇博客只是自己整理,记录的是自己觉得有必要记录的东西,所以有一些偏冷门的类和用法是没有收录的,因为类实在太多了,记那么多根本没有必要,反倒让人学起来找不着东南西北。
总之一句话:走大路,看辅路,不用管岔路。岔路留着让百度去查。
I/O基础知识
流
java程序通过流执行I/O(I/O的意思就是 输入/输出)。
流,是一种抽象,要么产生信息,要么使用信息,流通过java的IO系统链接到物理设备。
尽管所有的流链接的物理设备是不同的,但是他们的行为方式都是相同的,因此,可以为任何类型的设备应用相同的IO类和方法。这意味着可以将许多不同类型的输入:磁盘文件、键盘或网络socket,抽象为输入流。与之对应,输出流可以引用控制台、磁盘文件或网络连接。
流是处理输入/输出的一种清晰方式,例如代码中所有的部分都不需要去理解键盘和网络之间的区别,只需要操作流就行了,也就是说,‘流’屏蔽了实际IO设备中处理数据的细节。
字节流和字符流
java定义了两种类型的流:字节流和字符流,两种流都有输入、输出两个内容。
InputStream和OutStream针对字节流和而设计,为处理字节的输入和输出提供了方法。
Reader和Writer针对字符流而设计,为处理字符的输入和输出提供了方便的方法。
最初版本的java 1.0并没有提供字符流,因此,所有IO都是面向字节的。字符流是java 1.1添加的,并且某些面向字节的类和方法不再推荐使用。
另外一点:在最底层,所有IO仍然是面向字节的,基于字符的流只是为处理字符提供了一种方便和高效的方法。
关闭流的两种方式
手动关闭
通常,当不需要时,流必须关闭,如果没有关闭,就可能会导致内存泄漏以及资源紧张。
从java最初发布以来就一直有关闭流的close()方法,对于这种方式,通常是在finally代码块中调用close()方法。
自动关闭
到JDK7的时候,官方增加了一个新特性,该特性提供了另外一种管理资源的方式,这种方式能自动关闭文件。
该特性以try语句的扩展版为基础:try(在这里生成流){}.
当try代码块结束时,资源会被自动关闭,它的优点是当不再需要文件或者其他资源时,可以防止无意中忘记释放他们。
下面是带资源的try语句的3个关键点:
1.由带资源的try语句管理的资源必须是实现了AutoCloseable接口的类的对象。
2.在try代码中声明的资源被隐式声明为final。
3.通过使用分号分割每个声明可以管理多个资源。
带资源的try语句流线化了释放资源的过程,并消除了可能在无意中忘记释放资源的风险,所以如果可能的话,尽量在开发中使用这种方式。
字节流
顶级抽象类
InputStream
字节流的顶层抽象类,定义了java的字节流输入模型。
方法列表:
方法 | 描述 |
int available() | 返回当前可读取的输入字节数 |
void close | 关闭输入流 |
boolean markSupported() | 当前流是否支持打标记 |
void mark(int limit) | 在输入流的当前位置放一个标记(并不是所有的流都支持这个特性),该标记在读入limit个字节之前一直都有效。 |
void reset() | 将输入流的指针重置为前面设置的标记,如果当前没有任何标记,则指针重置到开头。 |
int read() | 读入一个字节,并返回该字节。(这个read方法在碰到输入流的结尾时返回-1) |
int read(byte b[]) | 读入一个字节数组,返回实际读入的字节数。(在碰到数据流的结尾时返回-1,这个方法最多读入b.length个字节) |
int read(byte[] b,int off,int len) | 读入一个字节数组,从byte[off]开始读,最多读取len个字节,返回实际读入的字节数,碰到输入流的结尾时返回-1. |
long skip(long n) | 在输入流中跳过n个字节,返回实际跳过的字节数。 |
OutputStream
字节流的顶层抽象类,定义了java的字节流输出模型。
方法列表:
方法 | 描述 |
void write(int n) | 写出一个字节的数据 |
void write(byte[] b) | 写出一个字节数组 |
void write(byte[] b,int off,int len) | 写出一个字节数组,从b[off]开始写,最多写len个字节。 |
void flush() | 冲刷输出流,结束输出状态。 |
void close() | 冲刷并关闭输出流。 |
文件流
FileInputStream类
FileInputStream继承于InputStream,该对象可以用于从文件中读取字节。
构造函数:
FileInputStream(String filePath);//文件的完整路径 FileInputStream(File fileObj);//文件的File对象
这两个构造器都会抛出FileNotFoundException异常。
从流中读取内容的两种方式:
try(FileInputStream f = new FileInputStream("./src/bytes/en.txt")){ int size; System.out.println("总字节是:"+(size = f.available())); int num = 3; while(num>0){ System.out.println("单个字节的读:"+(char) f.read()); num--; } //注意:此时io流的指针已经到了第4个字节了,所以下面打印的内容不包含前3个字节。 byte b[] = new byte[size]; f.read(b); System.out.println("文件内容是:"+new String(b)); //System.out.println("文件内容是:"+new String(b,0,size));//也可以这样 }catch (IOException e){ e.printStackTrace(); }
FileOutputStream类
FileOutputStream继承于OutputStream,该对象可以将字节写入文件。
构造函数:
FileOutputStream(String filePath) FileOutputStream(String filePath,boolean append) FileOutputStream(File fileObj) FileOutputStream(File fileObj,boolean append)
和FileInputStream一样,不同的是有一个append参数,这个参数如果为true,则以追加的方式打开文件,否则,这个方法会删除同名的已有文件创建一个新的输出流。
另外,如果试图打开只读文件会抛出异常。
以下演示将数据写入文件:
try(FileOutputStream f1 = new FileOutputStream("./io/src/bytes/file1.txt"); FileOutputStream f2 = new FileOutputStream("./io/src/bytes/file2.txt"); FileOutputStream f3 = new FileOutputStream("./io/src/bytes/file3.txt") ){ String source = "1234567890"; byte[] buf = source.getBytes(); //每跳一个字节写 for(int i=0;i<buf.length;i+=2){ f1.write(buf[i]); } //写全部 f2.write(buf); //写最后四分之一 (写出来的是90,因为7.5就是第8个字节,也就是会从第9个字节开始写) f3.write(buf,buf.length-buf.length/4,buf.length/4); }catch (IOException e){ e.printStackTrace(); }
字节数组流
ByteArrayInputStream类
ByteArrayInputStream类继承于InputStream,是使用字节数组作为源的输入流的一个实现,这个类有两个构造函数,都需要一个字节数组来提供数据源。
ByteArrayInputStream(byte array[]) ByteArrayInputStream(byte array[],int start,int num)
构造函数演示:
String str = "1234567890"; byte[] b = str.getBytes(); ByteArrayInputStream in1 = new ByteArrayInputStream(b); ByteArrayInputStream in2 = new ByteArrayInputStream(b,0,3);//从第0个字节开始读 读3个
ByteArrayInputStream实现了mark()和reset()方法,用法如下:
String str = "1234567890"; byte[] b = str.getBytes(); ByteArrayInputStream in1 = new ByteArrayInputStream(b); int num = 5; for(int i=0;i<5;i++){ System.out.print((char) in1.read());//输出:12345 if(i == 2){ in1.mark(i); } } in1.reset(); System.out.println(); for(int i=0;i<5;i++){ System.out.print((char) in1.read());//输出:45678 }
如果没有mark标记,则调用reset()会将指针重置到开头。(如果没有in1.mark(i)这行,那么第二个输出也是123456)
ByteArrayOutputStream类
ByteArrayOutputStream是使用字节数组作为目标的输出流的一个实现。
它有两个构造函数,如下所示:
ByteArrayOutputStream() ByteArrayOutputStream(int num)
第一个构造函数,创建一个32字节的缓冲区。
第二个构造函数,创建一个num大小的缓冲区。
缓冲区会被保存在ByteArrayOutputStream中受保护的属性buf变量中。
如果需要的话,缓冲区的大小会自动增加,缓冲区能够保存的字节数量包含在ByteArrayOutputStream中受保护的属性count变量中。
下面演示ByteArrayOutputStream类的使用:
String source = "1234567890"; ByteArrayOutputStream out1 = new ByteArrayOutputStream(); byte[] b = source.getBytes(); try{ out1.write(b); }catch (IOException e){ e.printStackTrace(); } System.out.println(out1.toString());//输出:1234567890 byte[] bb = out1.toByteArray(); for(int i=0;i<bb.length;i++){ System.out.print((char)bb[i]); } //输出:1234567890 System.out.println(); try(FileOutputStream f = new FileOutputStream("./io/src/bytes/f.txt")){ out1.writeTo(f);//把out1的内容写入f }catch (IOException e){ e.printStackTrace(); } out1.reset(); for(int i=0;i<3;i++){ out1.write('X'); } System.out.println(out1.toString());//输出:XXX
注意,最后的输出是三个X,也就是说,它并不是在插入内容,而是把后面的擦掉了。
字节缓冲流
对于面向字节的流,缓冲流通过将内存缓冲区附加到IO系统来扩展过滤流。这种流允许java一次对多个字节执行多次IO操作,从而提升性能。
因为可以使用缓冲区,所以略过、标记、或重置流都是可能发生的,BufferedInputStream类、BufferedOutputStream类、PushbackInputStream类都实现了缓冲流。
BufferedInputStream类
缓冲IO是很常见的性能优化手段,BufferedInputStream类允许将任何InputStream对象封装到缓冲流中以提高性能,因为带缓冲区的输入流从流中读入字符时,不会每次都对设备访问。
构造函数:
BufferedInputStream(InputStream inputStream) BufferedInputStream(InputStream inputStream,int bufSize)
缓冲输入流除了任何InputStream都实现了的read()和skip()方法外,还支持mark()和reset()方法。
下面演示如何把一个字符串读两遍,第二遍略过前5个字节:
String s = "1234567890"; byte[] buf = s.getBytes(); ByteArrayInputStream in = new ByteArrayInputStream(buf); try(BufferedInputStream f = new BufferedInputStream(in)){ int c = f.available(); int limit = 4; for(int i=0;i<c;i++){ int r = f.read(); if(i == limit){ f.mark(limit); } System.out.print((char) r);//输出1234567890 } System.out.println(); f.reset(); for(int i=0;i<c-(limit+1);i++){ int r = f.read(); System.out.print((char) r);//输出67890 } }catch (IOException e){ e.printStackTrace(); }
BufferedOutputStream类
创建一个带缓冲区的输出流,带缓冲区的输出流在收集要写出的字符时,不会每次都对设备访问,当缓冲区填满或者当流被冲刷时,数据就被写出,因此调用flush()方法才是数据刷盘。
BufferedOutputStream(OutputStream outputStream) BufferedOutputStream(OutputStream outputStream,int bufSize)
使用示例如下:
try(FileOutputStream f = new FileOutputStream("./io/src/bytes/bufOutput.txt")){ String source = "1234567890"; BufferedOutputStream buf = new BufferedOutputStream(f); byte[] b = source.getBytes(); for(int i=0;i<b.length;i++){ if(i == 3){ buf.flush(); buf.write('-'); } if(i == 6){ buf.flush(); buf.write('-'); } buf.write(b[i]); } }catch (IOException e){ e.printStackTrace(); }
最后写入文件的内容是:123-456
可见最后的7890一直在缓冲输出流中,不调用flush()就不会写入文件。
PushbackInputStream类
一个可以预览字节的输入流,它读取字节,但并不破坏他们,读取后可以再将他们回推到输入流中,下次调用read()时可以再次被读取。
构造函数:
PushbackInputStream(InputStream inputStream) PushbackInputStream(InputStream inputStream,int num)
第一个构造函数创建的流对象允许将一个字节回推到输入流。
第二个构造函数创建的流对象具有一个长度为num的回推缓冲区,从而允许将多个字节回推到输入流中。
除了来自InputStream的方法外,PushbackInputStream类提供了回推方法:unread(int b)
String source = "12345"; byte[] b = source.getBytes(); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(b); PushbackInputStream push = new PushbackInputStream(byteArrayInputStream); int bytes; try{ int size = push.available(); for(int i=0;i<size;i++){ bytes = push.read(); if(i == 1){ push.unread(bytes); } if(i == 3){ push.unread(bytes); } System.out.print((char) bytes); } }catch (IOException e){ e.printStackTrace(); }
最后的输出结果是:12233
另外也可以回推字节数组unread(byte buf[])
注意:PushbackInputStream会使得标记重置功能无效,因此在使用mark()和reset()之前应该先用markSupported()检查。
打印流
PrintStream类
用的最多的System.out.println()就是PrintStream类里的方法。
PrintStream最初的目的是为了以可视化格式打印所有的基本数据类型以及String对象,它的最重要的两个方法:print()和println()可以打印出各种类型,如果参数不是基本类型,那么会自动调用对象的toString()方法并显示结果。
字符流
虽然字节流为了处理各种类型的IO操作提供了充足的功能,但是老的IO流继承层次结构仅支持8位字节流,并不能直接操作16位的Unicode字符。
因为java的一个主要目的就是实现代码的“一次编写,到处运行”,为了国际化的大业,需要为字符提供直接的IO支持,因此添加了Reader和Writer继承层次结构,另外,新类库的设计使得它的操作比旧类库更快。
顶级抽象类
Reader
字符流的顶级抽象类,定义了java字符流的输入模型。
方法列表:
方法 | 描述 |
int read() | 读取一个字符,返回表示该字符的证书,如果达到文件末尾,则返回-1 |
int read(char buff[]) | 读取buff.length个字符,返回成功读取的字符数,如果达到文件末尾,则返回-1 |
int read(CharBuffer buff) | 读取字符,返回成功读取的字符数,如果达到文件末尾,则返回-1 |
int read(char buff[],int offset,int num) | 从buff[offset]开始读,读取num个字符,如果文件达到末尾,则返回-1 |
long skip(long num) | 跳过num个字符,返回实际跳过的字符数 |
void close() | 关闭输入流,如果试图继续读取,会产生IO异常 |
boolean ready() | 如果下一个输入请求不等待,就返回true,否则返回false |
boolean markSupported() | 如果这个流支持mark或者reset,就返回true |
void mark(int num) | 在当前流的位置放置标记,该标记在reset()之前一直有效 |
void reset() | 将输入指针重新设置为前面设置的标记位置 |
Writer
字符流的顶级抽象类,定义了java字符流的输出模型。
方法列表:
方法 | 描述 |
Writer append(char cn) | 将cn追加到调用输出流的末尾,返回对调用流的引用 |
Writer append(CharSequence chars) | 将chars追加到调用输出流的末尾,返回对调用流的引用 |
Writer append(CharSequence chars,int begin,int end) | 将chars从begin到end-1之间的字符追加到输出流的末尾,返回对调用流的引用 |
void close() | 关闭输出流,如果试图继续向其中写入内容,将产生IO异常 |
void flush() | 完成输出状态,从而清空所有缓冲区,即刷新输出缓冲区 |
void write(int ch) | 写入一个字符到输出流中 |
void write(char buff[]) | 将整个字符数组写入输出流中 |
void write(char buff[],int offset,int num) | 将buff数组中从buff[offset]开始的num个字符写入输出流中 |
void write(String str) | 将str写到输出流中 |
void write(String str,int offset,int num) | 将字符串str中从offset开始写,写num个字符 |
文件流
FileReader类
FileReader类可以创建用于读取文件内容的Reader对象。
注意:FileReader 用于读取字符流。要读取原始字节流,请考虑使用 FileInputStream。
构造函数:
FileReader(String filePath)
FileReader(File fileObj)
下面演示如何从文件中读取数据并在标准输出设备上进行显示:
try(FileReader f = new FileReader("./io/src/chars/fileReader.txt")){ int c; while ((c = f.read()) != -1){ System.out.print((char) c); } }catch (IOException e){ e.printStackTrace(); }
FileWriter类
FileWriter类可以创建能够用于写入文件的Writer对象。
文件是否可用或是否可以被创建取决于底层平台。特别是某些平台一次只允许一个 FileWriter(或其他文件写入对象)打开文件进行写入。在这种情况下,如果所涉及的文件已经打开,则此类中的构造方法将失败。
FileWriter
用于写入字符流。要写入原始字节流,请考虑使用 FileOutputStream
。
构造函数:
FileWriter(String filePath) FileWriter(String filePath,boolean append) FileWriter(File fileObj) FileWriter(File fileObj,boolean append)
append参数代表是否追加到文件末尾。
以下演示使用FileWriter将字符串写入文件的用法:
try(FileWriter f1 = new FileWriter("./io/src/chars/file1.txt"); FileWriter f2 = new FileWriter("./io/src/chars/file2.txt"); FileWriter f3 = new FileWriter("./io/src/chars/file3.txt") ){ String source = "1234567890"; int length = source.length(); char[] buff = source.toCharArray(); for(int i=0;i<length;i+=2){ f1.write(buff[i]); } f2.write(source); //f2.write(buff);//等价 //写最后四分之一 (写出来的是90,因为7.5就是第8个字节,也就是会从第9个字节开始写) f3.write(buff,buff.length-buff.length/4,buff.length/4); }catch (IOException e){ e.printStackTrace(); }
字符数组流
CharArrayReader类
CharArrayReader类是使用字符数组作为源的一个输入流的实现,该类具有两个构造函数,每个构造函数都需要一个字符数组来提供数据源
CharArrayReader(char array[]) CharArrayReader(char array[],int start,int num)
第二个构造函数根据字符数组创建Reader对象,指定从start的索引位置的字符开始,一共num个字符。
以下演示使用字符数组来读取数据:
String source = "1234567890"; int length = source.length(); char[] c = source.toCharArray(); try(CharArrayReader r = new CharArrayReader(c)){ for(int i = 0;i < length; i++){ System.out.print((char) r.read()); } }catch (IOException e){ e.printStackTrace(); }
CharArrayWriter类
CharArrayWriter类是使用数组作为目标的一个输出流实现。
CharArrayWriter() CharArrayWriter(int num)
在第一种形式中,创建使用默认大小的缓冲区,在第二种形式中,创建由num指定大小的缓冲区。
缓冲区保存在CharArrayWtier类的buf属性变量中,如果需要,缓冲区的大小可以自动增加,缓冲区能够容纳的字符数量保存在CharArrayWriter类的count属性变量中,两个属性都是受保护类型。
写法都是一样的,以下演示CharArrayWriter把字符写入文件:
String source = "1234567890"; char[] c = source.toCharArray(); CharArrayWriter w = new CharArrayWriter(); try{ w.write(c); }catch (IOException e){ e.printStackTrace(); return; } try(FileWriter f = new FileWriter("./io/src/chars/charArray.txt")){ w.writeTo(f); }catch (IOException e){ e.printStackTrace(); } w.reset(); for(int i=0;i<3;i++){ w.write('X'); } System.out.println(w.toString());//输出XXX
缓冲流
BufferedReader类
BufferedReader类通过缓冲输入提高性能,该类具有两个构造函数:
BufferedReader(Reader inputStream) BufferedReader(Reader inputStream,int bufSize)
第二个构造函数指定创建bufSize大小的缓冲字符流。
与面向字节的流一样,缓冲的输入字符流也实现了mark()和reset()方法,并且markSupported()会返回true.
下面例子重写BufferedInputStream的示例,使之输出相同:
String s = "1234567890"; char[] buf = s.toCharArray(); CharArrayReader in = new CharArrayReader(buf); try(BufferedReader f = new BufferedReader(in)){ int c = buf.length; int limit = 4; for(int i=0;i<c;i++){ int r = f.read(); if(i == limit){ f.mark(limit); } System.out.print((char) r);//输出1234567890 } System.out.println(); f.reset(); for(int i=0;i<c-(limit+1);i++){ int r = f.read(); System.out.print((char) r);//输出67890 } }catch (IOException e){ e.printStackTrace(); }
BufferedWriter类
BufferedWriter是缓冲输出的Writer,使用BufferedWriter可以通过减少实际向输出设备物理的写入数据的次数来提高性能。
构造函数如下:
BufferedWriter(Writer outputStream) BufferedWriter(Writer outputStream,int bufSize)
第一种形式创建的缓冲流使用具有默认大小的缓冲区,在第二种形式中缓冲区的大小是由bufSize指定的。
同样的,改写BufferedOutputStream的示例:
try(FileWriter f = new FileWriter("./io/src/chars/bufOutput.txt")){ String source = "1234567890"; BufferedWriter buf = new BufferedWriter(f); byte[] b = source.getBytes(); for(int i=0;i<b.length;i++){ if(i == 3){ buf.flush(); buf.write('-'); } if(i == 6){ buf.flush(); buf.write('-'); } buf.write(b[i]); } }catch (IOException e){ e.printStackTrace(); }
写入文件的内容是:123-456
PushbackReader类
PushbackReader类允许将字符回推到输入流,下面是该类的两个构造函数:
PushbackReader(Reader inputStream) PushbackReader(Reader inputStream,int bufSize)
第一个构造函数创建的缓冲流允许回推一个字符,第二种形式回推缓冲区的大小由bufSize指定。
PushbackReader类提供了unread()方法,该方法实现了回推动作,有如下三种形式:
void unread(in ch); void unread(char buffer[]); void unread(char buffer[],int offset,int num);
第一种形式回推cn传递的字符,后续调用read()就将返回这个被回推的字符。
第二种形式返回buffer中的字符,第三种形式回推buffer中从offset位置开始的num个字符。
当回推缓冲区已满时,如果试图返回字符,则会抛出IO异常。
下面示例PushbackReader的使用方法:
String source = "12345"; char[] c = source.toCharArray(); CharArrayReader charArrayReader = new CharArrayReader(c); PushbackReader push = new PushbackReader(charArrayReader); int bytes; try{ int size = source.length(); for(int i=0;i<size;i++){ bytes = push.read(); if(i == 1){ push.unread(bytes); } if(i == 3){ push.unread(bytes); } System.out.print((char) bytes); } }catch (IOException e){ e.printStackTrace(); }
输出的结果是:12233
打印流
PrintWriter类
PrintWriter本质上是PrintStream的面向字符的版本,
构造函数如下:
PrintWriter(OutputStream outputStream) PrintWriter(OutputStream outputStream,boolean autoFlushingOn) PrintWriter(Writer outputStream) PrintWriter(Writer outputStream,boolean autoFlushingOn)
autoFlushingOn参数控制每次调用println()/printf()或format()方法时,是否自动刷新输出缓冲区,如果为true,就自动刷新,否则不自动刷新,没有指定的构造函数不自动刷新。
PrintWriter(File outputFile)
PrintWriter(File outputFile,String charSet)
PrintWriter(String outputFileName)
PrintWriter(String outputFileName,String charSet)
这几个构造函数允许从File对象或根据文件路径创建PrintWriter对象,对于每种形式,都会自动创建文件,所有之前存在的同名文件都会被销毁。一旦创建PrintWriter对象,就将所有输出定向到指定文件,可以通过charSet传递的名称来指定字符编码。
以下演示PrintWriter的简单用法:
String source = "123456"; try(FileWriter f = new FileWriter("./io/src/chars/print.txt")){ PrintWriter printWriter = new PrintWriter(f); printWriter.print(source); }catch (Exception e){ e.printStackTrace(); }
随机访问文件
RandomAccessFile类
RandomAccessFile是一个完全独立的类,从Object派生而来,它不属于InputStream/OutputStream也不属于Reader/Writer。
它拥有和别的IO类型本质不同的行为,因为我们可以在一个文件里将指针向前和向后移动。
构造函数:
RandomAccessFile(File fileObj,String mode)
RandomAccessFile(String filename,String mode)
第一个构造函数fileObj指定了作为File对象打开的文件。
第二个构造函数文件名称是通过filename传递的。
两种方式都有mode指定访问模式:
r 以只读方式打开 调用结果对象的任何write方法都将抛出IOException
rw以读写方式打开,如果该文件不存在,则创建该文件
rws以读写方式打开,如果该文件不存在,则创建该文件,并且对文件的内容或元数据的每个更新都将同步写入到底层的存储设备
rwd以读写方式打开,如果该文件不存在,则创建该文件,并且对文件的内容的每个更新都将同步写入到底层的存储设备
rws和rwd显然是不存在缓冲环节的,如果该文件位于本地存储设备上,那么可以保证调用对此文件所做的所有更新均被写入该设备,这对确保在系统崩溃时不会丢失重要信息特别有用,如果该文件不在本地设备上,则无法提供这样的保证。
其中rwd模式可用于减少执行IO的操作数量,仅要求更新写入设备。
很重要的方法:
void seek(long newPos) //设置指针位置 long getFilePointer()//返回当前指针位置 long length()//返回文件长度
控制台I/O
Console类
Console类可访问与当前 Java 虚拟机关联的基于字符的控制台设备,也就是说用于从控制台读取内容以及向控制台写入内容,该类主要是提供方便,因为大部分功能都可以通过System.in和System.out得到。
Console类没有提供构造函数,可以通过调用System.console()方法获取Console对象。
如果控制台可用,就返回对控制台的引用,否则返回null。并不是在所有情况下控制台都是可用的,如果返回null,就不能进行控制台I/O。(虚拟机是否具有控制台取决于底层平台,还取决于调用虚拟机的方式。如果虚拟机从一个交互式命令行开始启动,且没有重定向标准输入和输出流,那么其控制台将存在,并且通常连接到键盘并从虚拟机启动的地方显示。如果虚拟机是自动启动的(例如,由后台作业调度程序启动),那么它通常没有控制台。)
方法列表:
方法 | 描述 |
void flush() | 刷新控制台,并强制立即写入所有缓冲的输出。 |
Console format(String fmtString,Object...args) | 使用fmtString指定的格式将args写到控制台 |
Console printf(String fmtString,Object...args) | 使用fmtString指定的格式将args写到控制台的便捷方法 |
Reader reader() | 返回对连接到控制台的Reader对象的引用 |
String readLine() | 从控制台读取单行文,当用户按下回车键时,输入结束。 |
String readLine(String fmtString,Object...args) | 提供一个格式化提示,然后从控制台读取单行文本。 |
char[] readPassword() | 从控制台读取密码,禁用回显。 |
char[] readPassword(String fmtString,Object...args) | 提供一个格式化提示,然后从控制台读取密码,禁用回显。 |
PrintWriter writer() | 返回对连接到控制台的Writer对象的引用 |
注意:在IDE中启动时,控制台引用是null。
序列化
序列化的应用
序列化是将对象写入字节流的过程,可以实现将对象保存到文件中,并且可以通过反序列化过程来恢复这些对象。
实现远程方法调用也需要序列化,远程方法调用允许在一个机器上的java对象调用在另外一台机器上的java对象的方法,可以将对象作为参数提供给远程方法,发送机器序列化对象并且进行传递。
假设将要序列化的对象具有指向其他对象的引用,而这些对象又具有更多对象的引用。
这些对象以及他们之间的关系形成了一张对象网,在这个对象网中,还可能存在环形引用,对象也可能包含指向他们自身的引用,在这些情形中,对象的序列化和反序列化都能够正确的工作。
如果试图序列化位于对象图顶部的对象,那么所有其他引用的对象都会被递归的定位和序列化,类似的,在反序列化的过程中,能够正确的恢复所有这些对象以及对他们的引用。
对象的默认序列化机制写入的内容是:对象的类,类签名,以及非瞬态和非静态字段的值。其他对象的引用(瞬态和静态字段除外)也会导致写入那些对象。可使用引用共享机制对单个对象的多个引用进行编码,这样即可将对象的图形恢复为最初写入它们时的形状。
Serializable
只有实现了Serializable接口的类才能够通过序列化功能进行保存和恢复。
Serializable接口没有定义成员,只是简单的用于指示类可以被序列化,如果一个类是可序列化的,那么这个类的所有子类也都是可序列化的。
保存和加载序列化对象
ObjectOutputStream类
ObjectOutputStream类负责将对象写入流中,只能将支持 java.io.Serializable 接口的对象写入流中,每个 serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,以及从初始对象中引用的其他所有对象的闭包。
构造函数:
ObjectOutputStream(OutputStream outStream)
参数outStream是将向其中写入序列化对象的输出流,关闭ObjectOutputStream对象会自动关闭OutStream指定的底层流。
void writerObject()方法用于将对象写入流中。所有对象(包括 String 和数组)都可以通过 writeObject 写入。可将多个对象或基元写入流中。必须使用与写入对象时相同的类型和顺序从相应 ObjectInputstream 中读回对象。
ObjectInputStream类
ObjectInputStream 对以前使用 ObjectOutputStream 写入的基本数据和对象进行反序列化。
ObjectOutputStream 和 ObjectInputStream 分别与 FileOutputStream 和 FileInputStream 一起使用时,可以为应用程序提供对对象图形的持久存储。ObjectInputStream 用于恢复那些以前序列化的对象。其他用途包括使用套接字流在主机之间传递对象,或者用于编组和解组远程通信系统中的实参和形参。
只有支持 java.io.Serializable 或 java.io.Externalizable 接口的对象才能从流读取。
读取对象类似于运行新对象的构造方法。为对象分配内存并将其初始化为零 (NULL)。为不可序列化类调用无参数构造方法,然后从以最接近 java.lang.object 的可序列化类开始和以对象的最特定类结束的流恢复可序列化类的字段。
下面示例从文件存取对象:
try(FileOutputStream file = new FileOutputStream("./io/src/serializables/1.txt")){ ObjectOutputStream oos = new ObjectOutputStream(file); oos.writeInt(12345); oos.writeObject("Today2"); oos.writeObject(new Date()); oos.close(); }catch (Exception e){ e.printStackTrace(); } try(FileInputStream file = new FileInputStream("./io/src/serializables/1.txt")){ ObjectInputStream ois = new ObjectInputStream(file); int i = ois.readInt(); String name = (String) ois.readObject(); Date date = (Date) ois.readObject(); ois.close(); System.out.println("i:"+i+",name:"+name+",date:"+date); }catch (Exception e){ e.printStackTrace(); }
序列化的控制
Externalizable接口
java对序列化和反序列化的功能都可以自动进行,然而在有些情况,我们可能需要控制这些过程,比如可能希望使用压缩和加密技术,此时就需要Externalizable接口了。
在这些特殊情况下,可以通过实现Externalizable接口来代替实现Serializable接口,
它有两个方法WriterExternal()和ReadExternal()必须被实现,这两个方法会在序列化和反序列化的过程中被自动调用,以便执行一些特殊操作。
比如下面这个PersonExternalizableVo类,它的存取过程都必须手动实现:
public class PersonExternalizableVo implements Externalizable { private int id; private String name; @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(id); out.writeObject(name); } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { id = in.readInt(); name = (String)in.readObject(); } //getter... //setter... //toString... }
瞬态和静态
当我们队序列化进行控制时,可能某个特定的子对象不想让java的序列化机制自动保存与恢复,如果子对象表示的是我们不希望将其序列化的敏感信息(如密码),通常就会面临这种情况,有两种办法可以实现这种需求。
第一种是瞬态,关键字transient。
比如以下password字段将不会被序列化:
private transient String password;
第二种是静态,即static静态属性。
序列化机制在遇到瞬态和静态属性时候会自动忽略。
注意:实现Externalizable的对象在默认情况下是不会保存任何字段的,所以transient字段只能用再实现了Serializable接口的类中。
File类
文件操作
尽管java.io定义的大多数类都是操作流,但是File类却不是,File类直接处理文件和文件系统,也就是说,File类没有指定如何从文件检索信息以及如何向文件中存储信息。
精确的说,File类是文件和目录路径名的抽象表示形式。
File类的实例是不可变的;也就是说,一旦创建,File 对象表示的抽象路径名将永不改变。
构造方法:
File(String directoryPath)
File(String directoryPath,String filename)
File(File dirObj,String filename)
File(URI uriObj)
其中,directoryPath是文件的路径名,filename是文件或者子目录的名称,dirObj是指定目录的File对象,uriObj是描述文件的URI对象。
下面的示例创建了3个File对象:
File f1 = new File("/"); File f2 = new File("/","1.txt") File f3 = new File(f1,"1.txt")
一般的方法如下:
File file = new File("./io/src/files/1.txt"); System.out.println("名称:"+file.getName()); System.out.println("路径名称:"+file.getPath()); System.out.println("绝对路径名称:"+file.getAbsolutePath()); System.out.println("父目录的路径名称:"+file.getParent()); System.out.println("是否存在:"+file.exists()); System.out.println("是否可写:"+file.canWrite()); System.out.println("是否可读:"+file.canRead()); System.out.println("是否隐藏:"+file.isHidden()); System.out.println("是否是目录:"+file.isDirectory()); System.out.println("是否是文件:"+file.isFile()); System.out.println("是否是绝对路径:"+file.isAbsolute()); System.out.println("最后一次修改时间:"+file.lastModified()); System.out.println("文件大小:"+file.length()+"bytes");
当File表示的路径是一个目录时,可以调用list()方法获取目录下的路径列表
如:
File file1 = new File(file.getParent()); String[] s = file1.list(); for(String str : s){ System.out.println(str); }
还有一些有用的方法:
boolean renameTo(File newName);//重命名 boolean mkdir();//创建该名称 boolean mkdirs();//递归创建该路径 boolean delete();删除文件,如果目录为空的话可以删除目录。
文件筛选
list()方法还有一个使用形式
String[] list(FilenameFilter fiter);
在这种形式中,fiter是一个实现了FilenameFilter接口的对象,该对象中实现了accept(File dir, String name)方法,该方法返回boolean值,会针对每个文件调用一次,返回true代表选择,返回false代表过滤掉。
使用示例如下 只返回.txt后缀的文件:
public class OnlyExt implements FilenameFilter { protected String ext; public OnlyExt(String str){ ext = "."+str; } @Override public boolean accept(File dir, String name) { return name.endsWith(ext); } }
FilenameFilter filenameFilter = new OnlyExt("txt"); String[] strs = file1.list(filenameFilter); for(String t : strs){ System.out.println(t); }
文件列表对象
list()方法还有一种形式:
File[] listFiles()
File[] listFiles(FilenameFiter fiter)
File[] listFiles(FileFiter fiter)
它返回的是File对象数组。
第三个构造函数的实现方法是accept(File path),也是对每个文件调用一次
以下改写OnlyExt类,使之筛选出同样的文件:
public class FilterExt implements FileFilter { protected String ext; public FilterExt(String str){ ext = "."+str; } @Override public boolean accept(File pathname) { String name = pathname.getName(); return name.endsWith(ext); } }
FileFilter fileFilter = new FilterExt("txt"); File[] files = file1.listFiles(fileFilter); for(File f :files){ System.out.println(f.getName()); }
写在最后
源码分享:https://gitee.com/zhao-baolin/java_learning/tree/master/io
io只是一部分,后面还会整理新io的知识,系统的知识储备是非常非常非常重要的事情。
学习这条路,永无止境。
你好,再见。