------- android培训、java培训、期待与您交流! ----------
第一节--IO流概述
设备(如硬盘、内存)之间需要进行数据传输。按java面向对象的思想,这需要对象去操作数据,即IO流对象,这些对象都存在于IO包中。
按操作的数据可以分为字符流和字节流:
数据都是以字节码形式存储,所以字节流可以操作所有类型的数据。字符流融合了字符编码表,所以可以操作文本数据。
按数据的流向可以分为输入流和输出流:
IO流对数据的操作只有两种:读取和写入。读取是指将指定数据读取到流对象中,写入是指将数据写入到指定的位置。
字节流的两个基类:InputStream、OutputStream
这两个基类描述了对字节码数据进行读写的共性方法。
字符流的两个基类:Reader、Writer
这四个基类的子类特点:类名基本上都是以父类名作为后缀,前缀体现子类的特有功能。这两个基类描述了对文本数据进行读写的共性方法。
第二节--字符流
数据存在的最常见形式:文件。以对文件的读写操作为例,学习IO流的基本操作。
对文件进行操作的字符流的具体子类是:FileReader、FileWriter
FileWriter的使用:对字符文件进行写入操作
public class FileWriterDemo { public static void main(String[] args) throws IOException { //1.创建一个FileWriter对象,一初始化就要明确写入到哪个文件 //在指定目录下创建了一个demo.txt文件,如果该路径下有同名 //文件,同名文件会被覆盖 FileWriter fw = null; try{ fw = new FileWriter("e:\\demo.txt"); //2.调用write方法,将数据写入流中 fw.write(" hello"); //3.调用flush方法,将流中数据刷进目的地 fw.flush(); //flush后流还存在,可以继续接受数据 fw.write(" world"); fw.flush(); } catch(IOException e){ System.out.println("文件写入失败"); } //java本身不能写入数据,而是调用系统资源进行写入操作, //使用完毕,要调用close方法,关闭资源关闭前会先刷新 finally{ try{ if(fw!=null) fw.close(); } catch(IOException e){ System.out.print("关闭失败"); } } } }
注意:演示中的数据写入是覆盖性的,即每次写入都会将文件中的数据覆盖,如果想在原来数据后续写,需要在初始化流对象时使用此构造方法FileWriter(File file, boolean append),传递参数true。创建流对象时指定的目的地可能不存在或创建失败,所以需要进行异常处理,流对象创建失败,关流时也会失败,所以关闭资源也需要进行异常处理。
FileReader的使用:对字符文件进行读取操作
读取的第一种方式:使用read()方法,一次读取一个字符,返回int类型的字符,并自动往下读取,读到末尾返回-1.
ublic class FileReaderDemo { public static void main(String[] args) { FileReader fr = null; try{ //创建字符读取流对象,关联要读取的文件 fr = new FileReader("e:\\demo.txt"); //read方法返回的是读取的一个字符,int类型,定义一个变量存储 //当数据被读取完会返回-1 int ch = 0; while((ch=fr.read())!=-1){ System.out.println("ch="+(char)ch); } }catch(IOException e){ } finally{ try { if(fr!=null) fr.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
读取的第二种方式:使用read(char [] cbuff)方法,将字符读入字符数组,返回的是读取的字符个数,并自动往下读,读到末尾返回-1.
public class FileReaderDemo { public static void main(String[] args) { FileReader fr = null; int len = 0; char[]buf = new char[2]; try { fr = new FileReader("e:\\demo.txt"); //fr.read()返回的是读取的字符个数 while((len=fr.read(buf))!=-1) System.out.print(new String(buf,0,len)); } catch (Exception e) { // TODO: handle exception } finally{ try { if(fr!=null) fr.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
注意:将字符读入数组,数组相当于一个缓冲区,每一次读取都会将数组中的数据。如果最后一次读取字符的个数小于数组长度,那么数组中的数据只有前半部分被覆盖。所以使用数组中的数据时,注意读取到的字符个数。尤其写入数组时,要使用写入数组一部分的方法write(char[] cbuf, int off, int len),off=0,len=读取的字符个数。
练习:使用字符流复制文件
/* * 需求:将D盘下的demo.txt文件复制到D盘下的demo_copy.txt中 * 分析:复制,其实就是在D盘下创建文件demo_copy.txt,读取demo.txt文件,写入到demo_copy.txt中 * 读取方式有两种:一次读取一个字符和将字符读取到字符数组中 * 写入方式有两种:一次写入一个字符和一次写入一个数组 */ public class FileCopy { public static void main(String[] args) { // TODO Auto-generated method stub copy_2(); } //方式一:一次读取一个字符 //将复制文件的操作封装成方法 public static void copy_1(){ //创建操作数据的字符流对象的引用 FileReader fr = null; FileWriter fw = null; int ch = 0; try { fr =new FileReader("e:\\demo.txt"); fw = new FileWriter("e:\\demo_copy.txt"); while((ch = fr.read())!=-1) fw.write(ch); } catch (Exception e) { // TODO: handle exception } finally{ try { if(fr!=null) fr.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { if(fw!=null) fw.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } //读取到数组中,然后一次写入一个数组 public static void copy_2(){ //创建操作数据的字符流对象的引用 FileReader fr = null; FileWriter fw = null; char[]buf = new char[1024]; int len = 0; try { fr =new FileReader("e:\\demo.txt"); fw = new FileWriter("e:\\demo_copy.txt",true); while((len = fr.read(buf))!=-1) fw.write(buf,0,len); } catch (Exception e) { // TODO: handle exception } finally{ try { if(fr!=null) fr.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { if(fw!=null) fw.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
文件复制过程:读取流对象和写入流对象之间不能直接进行数据传输,需要中间变量。第一种方式是一次读取一个字符,使用int型变量存储,然后写入字符。第二种方式是将数据读取到字符数组中,使用数组存储,然后写入数组。在这里,数组其实实现了缓冲区的功能。
第三节--字符流缓冲区
为了提高字符数据读写效率,java提供了字符流缓冲区
BufferedReader、BufferedWriter
字符写入流缓冲区BufferedWriter的使用:
练习:利用缓冲区技术向文件:e:\\demo.txt写入文本数据
public class BufferedWriteDemo { public static void main(String[] args)throws IOException { //创建字符写入流对象 FileWriter fw = new FileWriter("e:\\demo.txt"); //创建字符写入流缓冲区,将流对象作为实际参数传递给字符写入流构造函数,即被缓冲对象 BufferedWriter bw = new BufferedWriter(fw); //调用缓冲区写入方法,将数据写进缓冲区 for(int x = 0;x < 10;x++){ bw.write("avdjfaksljf"); //缓冲区特有的换行方法 bw.newLine(); //将数据从缓冲区flush进目的地 bw.flush(); } bw.close(); } }
注意:字符写入流缓冲区的缓冲原理是底层封装了数组。特有的换行方法newLine(),底层封装了换行符,跨平台。
字符读取流缓冲区BufferedReader的使用:
//练习字符读取流缓冲区,使用其一次读取一行的方法(未做异常处理) public class BufferReaderDemo { public static void main(String[] args)throws IOException { //创建字符读取流对象 FileReader fr = new FileReader("e:\\demo.txt"); //创建字符读取流对象,指定被缓冲的流对象 BufferedReader br = new BufferedReader(fr); //使用一次读取一行的方式读取数据,返回字符串,读到文件末尾时,返null String line; while((line=br.readLine())!=null){ System.out.println(line); } } }
注意:字符读取流缓冲区BufferedReader提供了一次读取一个文本行的readLine()方法,返回该文本行字符,但不包含任何行终止符。这个方法其实是基于read()方法,通过下列字符之一即可认为某行已终止:换行 ('\n')、回车 ('\r') 或回车后直接跟着换行。读到数据末尾返回null。
练习:使用字符流缓冲区复制文件 将E盘下demo.txt文件复制到E盘下demo_copy.txt中 public class BufferedCopy { public static void main(String[] args) { //创建文本读写流缓冲区对象的引用 BufferedReader bufr = null; BufferedWriter bufw = null; try { bufr = new BufferedReader(new FileReader("e:\\demo.txt")); bufw = new BufferedWriter(new FileWriter("e:\\demo_copy.txt")); String line = null; while((line=bufr.readLine())!=null){ bufw.write(line); bufw.newLine(); bufw.flush(); } } catch (IOException e) { System.out.print("读写失败"); } finally{ try { if(bufr!=null) bufr.close(); } catch (Exception e2) { System.out.print("数据读取失败"); } try { if(bufw!=null) bufw.close(); } catch (Exception e2) { System.out.print("数据写入失败"); } } } }
自定义字符读取流缓冲区,定义一次读取一个文本行的方法
public class MyBufferedReader { private FileReader r; MyBufferedReader(FileReader r) { this.r = r; } public static void main(String[] args) { // TODO Auto-generated method stub } public String myReadLine() throws IOException{ //定义字符串缓冲区,存储读取到的字符 StringBuilder sb = new StringBuilder(); int ch; while((ch=r.read())!=-1){ //读到'\r'时跳过 if(ch=='\r') continue; //读到'\n'说明到了一行末尾,结束函数,返回此行字符 if(ch=='\n') return sb.toString(); else //否则就将读取的字符添加到字符串缓冲区 sb.append((char)ch); } /* *一行结束的标志是读取到了行终止符,但最后一行一般没有行终止符, *也就是说,最后一行读取结束并添加到字符串缓冲区后,也必须返回 */ if(sb.length()!=0) return sb.toString(); return null; } }
字符流缓冲区其实就是对字符流的功能进行了增强,提高了读写效率,运用了装饰设计思想。
装饰设计模式:
为了使已有对象的功能更加强大,可以定义类传入该对象,基于已有功能,提供增强功能,定义的类就是装饰类。
装饰类的构造函数一般接收被增强的对象,增强其功能。
装饰和继承的区别:
对某一个类的对象增加功能时,可以自定义类继承这个类,添加特有的功能。对另一个类增加功能时,也需要自定义另一个类继承另一个类并添加特有功能,如此一来继承体系会越来越臃肿。
装饰类可以对一组类进行功能增强,比如,设计一个装饰类,对一个类基于已有功能进行增强,需要装饰类继承该类,将被增强类的对象传给装饰类的构造函数,根据多态思想,也可以传递被增强类的子类对象。也就是说,装饰类可以增强一个类及其所有子类的对象,只要把对象传递给装饰类。一般的,装饰类和被增强的类属于同一个类的体系,降低了类与类之间的关系。所以装饰设计模式比继承更灵活。
注意:装饰类需要复写父类所有抽象方法,比如自定义字符读取流缓冲区
class MyBufferedReader extends Reader { private Reader r; MyBufferedReader(Reader r) { this.r = r; } //可以一次读一行数据的方法。 public String myReadLine()throws IOException { //定义一个临时容器。原BufferReader封装的是字符数组。 //为了演示方便。定义一个StringBuilder容器。因为最终还是要将数据变成字符串。 StringBuilder sb = new StringBuilder(); int ch = 0; while((ch=r.read())!=-1) { if(ch=='\r') continue; if(ch=='\n') return sb.toString(); else sb.append((char)ch); } if(sb.length()!=0) return sb.toString(); return null; } /* 覆盖Reader类中的抽象方法。 */ public int read(char[] cbuf, int off, int len) throws IOException { return r.read(cbuf,off,len) ; } public void close()throws IOException { r.close(); } public void myClose()throws IOException { r.close(); } } class MyBufferedReaderDemo { public static void main(String[] args) throws IOException { FileReader fr = new FileReader("buf.txt"); MyBufferedReader myBuf = new MyBufferedReader(fr); String line = null; while((line=myBuf.myReadLine())!=null) { System.out.println(line); } myBuf.myClose(); } }
BufferedReader的子类:LineNumberReader。
LineNumberReader多了个行号属性及访问该属性的方法,并复写了父类的readLine方法,方法中添加了行号计数器。