【JAVA IO流之字符流】
一、概述。
java对数据的操作是通过流的方式。
java用于操作流的对象都在IO包中。
流按照操作数据不同分为两种,字节流和字符流。
流按照流向分为输入流,输出流。
输入输出的“入”和“出”是相当于内存来说的。
字符流:字节流读取文字字节数据后,不直接操作,而是先查指定的编码表,获取对应的文字,再对这个文字进行操作。简单来说就是字节流+码表。
在IO流中,字节流的顶层父类是Writer和Reader。
二、java.io.FileWriter类。
public class FileWriterextends OutputStreamWriter |
Writer
|--OutputStreamWriter
|--FileWriter
该类是操作字符文件的流,用于将数据写入到文件中。
1.方法摘要
(1).构造方法。
有两个重要的构造方法:
FileWriter(File file) 根据给定的 File 对象构造一个 FileWriter 对象。 |
FileWriter(File file,
boolean append) 根据给定的 File 对象构造一个 FileWriter 对象。 |
后者相对于前者来说多了一个boolean型的参数,该参数的作用是决定写入文件的方式是追加方式还是覆写方式。
默认的构造方法(前者)构造的FileWriter流对象向文件中写入的时候会默认的将文件的内容清空然后再写入,如果使用后者并将true传入该构造方法,则写入的方式就变成了追加方式。
(2).write方法。
该类没有自己的write方法,全部从父类或者超类中继承而来的write方法。
从OutputStreamWriter中继承而来的方法:
void |
write(char[] cbuf, int off, int len) 写入字符数组的某一部分。 |
void |
write(int c)
写入单个字符。 |
void |
write(String str,
int off, int len) 写入字符串的某一部分。 |
从Writer中继承而来的write方法:
void |
write(char[] cbuf) 写入字符数组。 |
void |
write(String str) 写入字符串。 |
(3).flush方法。
void |
flush() 刷新该流的缓冲。 |
该方法是从OutputStreamWriter中继承而来的,作用是将流中的数据数据刷到文件中。文件关闭前会默认调用此方法。如果不调用此方法,则当缓冲区满了也会自动调用该方法。
(4).close方法。
void |
close() 关闭此流,但要先刷新它。 |
2.flush与close比较
使用flush方法和close方法均可以保存文件,使用这两者各有什么好处?
举一个形象的例子:我们在使用记事本软件的时候,会常常使用“保存”按钮保存住当前内容到文件,如果没有保存,关闭文件的时候就会出现提示信息“是否要保存文件内容?”,然后再关闭文件。在这个例子中,“保存”相当于flush的功能,而关闭文件则相当于close的功能,经常点保存按钮的目的就是为了防止断电丢失和节约内存。
因此,使用flush的目的就是为了防止断电丢失和节约内存,因此在写程序的时候,尽量写入一句刷新一句;如果文件不关闭,最明显的影响就是“删不掉文件”。
3.FileWriter细节:换行和续写
在Windows字符文件中,文件的换行符是\r\n,在linux中则为\n,这样很有可能导致同一个java程序在linux中的运行结果和在windows中的运行结果不一致。解决方法是使用System类中的方法getProperty,得到当前系统中的行分隔符。具体用法:String line_sparator=System.getProperty("line.separator");
如果想要在已存在的文件末尾添加新内容,则需要使用第二种构造方法。
4.异常处理标准模板。
1 package p02.FileWriterDemo.p01.ExceptionCaptureDemo; 2 3 import java.io.FileReader; 4 import java.io.FileWriter; 5 import java.io.IOException; 6 7 public class ExceptionHandleDemo { 8 9 public static void main(String[] args) { 10 standarExceptionHandleDemo(); 11 } 12 13 private static void standarExceptionHandleDemo() { 14 FileWriter fw=null; 15 FileReader fr=null; 16 try 17 { 18 fr=new FileReader("c:\\source.txt");//可能会抛出FileNotFindException 19 fw=new FileWriter("c:\\aim.txt");//可能会抛出IOException 20 char buf[]=new char[1024]; 21 int length=fr.read(buf); 22 fw.write(buf,0,length); 23 fw.flush(); 24 } 25 catch(IOException e) 26 { 27 System.out.println("读写失败!"); 28 } 29 finally 30 { 31 if(fr!=null) 32 { 33 try 34 { 35 fr.close(); 36 } catch (IOException e) 37 { 38 //异常处理程序 39 } 40 } 41 if(fw!=null) 42 { 43 try 44 { 45 fw.close(); 46 } catch (IOException e) 47 { 48 //异常处理程序 49 } 50 } 51 } 52 } 53 54 }
二、FileReader类。
public class FileReaderextends InputStreamReader |
Reader
|--InputStreamReader
|--FileReader
在IO流中,字符输入流的顶层父类是Reader类。
1.方法摘要。
(1)构造方法。
FileReader(File file) 在给定从中读取数据的 File 的情况下创建一个新 FileReader。 |
FileReader(FileDescriptor fd)
在给定从中读取数据的 FileDescriptor 的情况下创建一个新 FileReader。 |
FileReader(String fileName)
在给定从中读取数据的文件名的情况下创建一个新 FileReader。 |
构造方法必须传递一个参数,如果参数不正确,则会抛出FileNotFindException异常。
(2)read方法。
该流不具备自己特有的read方法,其读取方法全部继承自父类或者其超类。
从InputStreamReader中继承的方法:
int |
read() 读取单个字符。 |
int |
read(char[] cbuf,
int offset, int length) 将字符读入数组中的某一部分。 |
从Reader类中继承的方法:
int |
read(char[] cbuf) 将字符读入数组。 |
int |
read(CharBuffer target) 试图将字符读入指定的字符缓冲区。 |
经常使用的方法只有两个:read()与read(char[] cbuf);前者返回字符的编码值,后者返回字符的长度。
(3)close方法。略。
2.读取字符使用的两种方法。
方法1:使用read()读取一个字符。
1 private static void function1() throws IOException { 2 FileReader fr=new FileReader("c:\\source.java"); 3 int ch; 4 while((ch=fr.read())!=-1) 5 { 6 System.out.println((char)ch); 7 } 8 fr.close(); 9 }
方法2:使用read(buf)读取到缓冲数组。
1 private static void function2() throws IOException { 2 FileReader fr=new FileReader("c:\\source.java"); 3 char buf[]=new char[1024]; 4 int length=0; 5 while((length=fr.read(buf))!=-1) 6 { 7 System.out.print(new String(buf,0,length)); 8 } 9 fr.close(); 10 }
3.复制文本文件小练习。
第一种方法:一次读取一个字符。
1 private static void withoutParameterFunction() { 2 FileReader fr=null; 3 FileWriter fw=null; 4 try 5 { 6 fr=new FileReader("c:/source.java"); 7 fw=new FileWriter("c:/aim.java"); 8 int ch; 9 while((ch=fr.read())!=-1) 10 { 11 fw.write(ch); 12 fw.flush(); 13 } 14 } 15 catch(IOException e) 16 { 17 throw new RuntimeException("读写失败!"); 18 } 19 finally 20 { 21 if(fr!=null) 22 { 23 try 24 { 25 fr.close(); 26 } catch (IOException e) 27 { 28 e.printStackTrace(); 29 } 30 } 31 if(fw!=null) 32 { 33 try 34 { 35 fw.close(); 36 } catch (IOException e) 37 { 38 e.printStackTrace(); 39 } 40 } 41 } 42 }
第二种方法:一次读取多个字符。
1 private static void withParameterFunction() { 2 FileReader fr=null; 3 FileWriter fw=null; 4 try 5 { 6 fr=new FileReader("c:/source.java"); 7 fw=new FileWriter("c:/aim.java"); 8 char buf[]=new char[1024]; 9 int len; 10 while((len=fr.read(buf))!=-1) 11 { 12 fw.write(buf, 0, len); 13 fw.flush(); 14 } 15 } 16 catch(IOException e) 17 { 18 throw new RuntimeException("读写失败!"); 19 } 20 finally 21 { 22 if(fr!=null) 23 { 24 try 25 { 26 fr.close(); 27 } catch (IOException e) 28 { 29 e.printStackTrace(); 30 } 31 } 32 if(fw!=null) 33 { 34 try 35 { 36 fw.close(); 37 } catch (IOException e) 38 { 39 e.printStackTrace(); 40 } 41 } 42 } 43 }
三、BufferedWriter缓冲流。
public class BufferedWriterextends Writer |
Reader
|--BufferedWriter
1.方法摘要。
(1)构造方法
BufferedWriter(Writer out) 创建一个使用默认大小输出缓冲区的缓冲字符输出流。 |
BufferedWriter(Writer out,
int sz) 创建一个使用给定大小输出缓冲区的新缓冲字符输出流。 |
可以看出,构造方法中必须传入一个Writer对象,该对象是被缓冲的对象,如果该对象不存在则缓冲流就没有了存在的意义。
该缓冲流使用了装饰设计模式,是对Writer类的功能增强。
(2)write方法。
该类中的write方法和FileWriter中的方法一致,不赘述。
但是应当注意,虽然方法相同,但是底层实现却完全不同。该类的write方法是将数据写入缓冲区中,而FileWriter的write方法是将数据写入文件(先写入流中)。
(3)flush方法和close方法。
void |
close() 关闭此流,但要先刷新它。 |
void |
flush()
刷新该流的缓冲。 |
特别需要注意的是,close方法调用的是Writer的close方法,所以关闭流的时候一旦关闭了缓冲流就会关闭Writer的流,所以只需要关闭缓冲流就可以了。
(4)特有的方法:newLine。
public void newLine()throws IOException
|
该方法封装了System.getProperty("line.separator");的动作,使得程序员提高了工作效率。
四、BufferedReader类。
public class BufferedReaderextends Reader |
Reader
|--BufferedReader
该类是字符输入缓冲流,用于缓冲Reader类的流对象,以提高输入效率。
1.方法摘要。
(1)构造方法。
BufferedReader(Reader in) 创建一个使用默认大小输入缓冲区的缓冲字符输入流。 |
BufferedReader(Reader in,
int sz) 创建一个使用指定大小输入缓冲区的缓冲字符输入流。 |
该构造方法和BufferedWriter类似,也有两个,其中一个可以指定缓冲区的大小。
(2)read方法。
该read方法和FileReader中的read方法相同,不赘述。但是应当注意,虽然方法名完全相同,但是底层的实现却完全不同,该流对象操作的对象是内存(从内存中读,所以速度更快),而FileReader操作的是文件。
(3)特有方法:readLine()
public String readLine()throws IOException
|
由于该流是针对字符的流,所以对于“行”有着特殊的方法,它是根据回车符来判断一行是否结束的(在文件中体现为“\r\n”),而不是根据肉眼观察到底有几行。
(4)close方法。
不赘述。
五、是用缓冲区的方式复制文本文件。
第一种方法:一次读取一个字符
1 private static void CopyTextFileUseRead() { 2 BufferedReader br=null; 3 BufferedWriter bw=null; 4 FileReader fr=null; 5 FileWriter fw=null; 6 try 7 { 8 fr=new FileReader("c:/source.java"); 9 fw=new FileWriter("c:/aim.java"); 10 br=new BufferedReader(fr); 11 bw=new BufferedWriter(fw); 12 int ch; 13 while((ch=br.read())!=-1) 14 { 15 bw.write(ch); 16 bw.flush(); 17 } 18 } 19 catch(IOException e) 20 { 21 throw new RuntimeException("读写失败!"); 22 } 23 finally 24 { 25 if(fr!=null) 26 try 27 { 28 fr.close(); 29 } 30 catch(IOException e) 31 { 32 } 33 if(fw!=null) 34 { 35 try 36 { 37 fw.close(); 38 } catch (IOException e) 39 { 40 } 41 } 42 43 } 44 }
第二种方法:使用readLine方法,一次读取“一行”
1 private static void CopyTextFileUseReadLine() { 2 BufferedReader br=null; 3 BufferedWriter bw=null; 4 FileReader fr=null; 5 FileWriter fw=null; 6 try 7 { 8 fr=new FileReader("c:/source.java"); 9 fw=new FileWriter("c:/aim.java"); 10 br=new BufferedReader(fr); 11 bw=new BufferedWriter(fw); 12 String buf=null; 13 while((buf=br.readLine())!=null) 14 { 15 bw.write(buf); 16 bw.newLine(); 17 bw.flush(); 18 } 19 } 20 catch(IOException e) 21 { 22 throw new RuntimeException("读写失败!"); 23 } 24 finally 25 { 26 if(fr!=null) 27 try 28 { 29 fr.close(); 30 } 31 catch(IOException e) 32 { 33 } 34 if(fw!=null) 35 { 36 try 37 { 38 fw.close(); 39 } catch (IOException e) 40 { 41 } 42 } 43 } 44 }
六、模拟BufferedReader类。
对于BufferedReader类,有三个个方法经常被使用:read、readLine、close,现在自定义类MyBufferedReader,模拟三个方法,以加深对BufferedReader的理解。
首先read与reanLine原理图:
MyBufferedReader类:
1 class MyBufferedReader 2 { 3 private Reader r; 4 private int size=1024;//默认的缓冲区大小 5 private char buf[];//存放字符的缓冲数组 6 private int pos=0;//记录当前的指针 7 private int count=0;//记录数组的长度 8 public MyBufferedReader(Reader r) 9 { 10 this.r=r; 11 buf=new char[size]; 12 } 13 public MyBufferedReader(Reader r,int size) 14 { 15 this.r=r; 16 this.size=size; 17 buf=new char[size]; 18 } 19 public int read() throws IOException 20 { 21 if(count==0) 22 { 23 count=r.read(buf); 24 pos=0; 25 } 26 if(count<0) 27 return -1; 28 char ch=buf[pos]; 29 pos++; 30 count--; 31 return ch; 32 } 33 public String readLine() throws IOException 34 { 35 StringBuilder sb=new StringBuilder(); 36 int ch; 37 while((ch=read())!=-1) 38 { 39 if(ch=='\r') 40 continue; 41 if(ch=='\n') 42 return sb.toString(); 43 sb.append((char)ch); 44 } 45 if(sb.length()!=0)//这里进行健壮性判断是必不可少的,否则很有可能丢失最后一行 46 return sb.toString(); 47 return null; 48 } 49 public void close() throws IOException 50 { 51 r.close(); 52 } 53 }
此类中,有一句应当特别注意:
在readLine方法中:
if(sb.length()!=0)//这里进行健壮性判断是必不可少的,否则很有可能丢失最后一行 return sb.toString();
这句不能丢,因为丢了这个判断很有可能会丢失最后一行(如果最后一行没有回车)。
通过模拟,可以发现,BufferedReader封装了一个缓冲数组,该缓冲数组缓存r中的数据,以提高程序对数据读取效率。该数组当然也可以自定义,之前已经演示,不赘述。
六、BufferedReader和BufferedWriter类使用了装饰模式
所谓的装饰模式是针对某一类进行功能增强的类。在这里,BufferedReader类对Reader(及其子类)类进行了功能增强,BufferedWriter类对Writer类(及其子类)进行了功能增强。
使用装饰模式和使用继承都能达到增强某一类的功能的目的,但是使用装饰模式更加灵活。装饰模式是针对某一类进行功能增强,而如果使用继承设计模式,则只能针对某个特别的类进行功能增强。比如,如果使用继承达到功能增强的目的,则继承层次将会变成这样:
Reader
|--InputStreamReader
|--FileReader
|--BufferedFileReader
|--BufferedInputStreamReader
相对于原本的继承层次:
Reader
|--InputStreamReader
|--FileReader
|--BufferedReader
来说,前者很明显多了一个缓冲流,但是绝不仅仅是多了一个,因为Reader的子类很多,如果针对每个具体的类进行功能增强,则整个继承体系将会变得非常臃肿。但是如果使用装饰模式进行功能增强,则可以几乎不需要改变原本的继承层次,只需要多出一个缓冲流即可。由此可见,使用装饰模式要灵活很多,因此,如果想要针对某一类进行功能增强,最好使用装饰模式而不是使用继承的方式。