第一讲 IO概述
1. 流的概念
IO流即InputOutput的缩写,在Java中IO流用来处理设备之间的数据传输,Java对数据的操作是通过IO流的方式,我们可以把IO流抽象的当作一根管道,而
数据的传输就好比流水,在管道中我们能更加轻松的实现数据的传输。
Java用于操作流的对象都在IO包中。
2. 流的分类
① 按流向分,可分为: 输入流和输出流
注意:输入和输出的参照物是本设备,向里传输数据称为输入,向外传输数据称为输出,所以程序可以从输入流中读取数据,输出流中输入数据
② 按操作数据分为两种: 字节流和字符流
字节流以InputStream和OutputStream为后缀,表示操作的最小数据单元为字节
字符流以Reader和Writer为后缀,表示操作的最小数据单元为字符
注意: 字符流是对字节流进行了封装,方便操作。在最底层,所有的输入输出都是字节形式的。
③ 按功能分类,可分为:节点流和处理流
节点流:可以直接从或向一个特定的地方(节点)读写数据,常用节点流有:
处理流:是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。 如BufferedReader。处理流的构造方法总是要带一个 其他的流对
象做参数。一个流对象经过 其他流的多次包装,称为流的链接。相当与在一个管道上接了另一个管道,以实现更通用的功能
3. IO流的常用基类:
1)字节流的抽象基流:InputStream和OutputStream
2)字符流的抽象基流:Reader和Writer
注意: 此四个类派生出来的子类名称都是以父类名作为子类名的后缀,以前缀为其功能;如InputStream子类FileInputStream;Reader子类FileReader
第二讲 字符流
继承体系
1. 简述
1、字符流中的对象融合了编码表。使用的是默认的编码,即当前系统的编码。
2、字符流只用于处理文字数据,而字节流可以处理媒体数据。
3、既然IO流是用于操作数据的,那么数据的最常见体现形式是文件。查看API,找到一个专门用于操作文件的Writer子类对象FileWriter。后缀是父类名。前缀
名是流对象的功能。该流对象一被初始化,就必须有被操作的文件存在。
2. 字符流的读写(以FileWriter和FileReader为例)
a、创建一个FileWriter对象,该对象一被初始化,就必须要明确被操作的文件。且该目录下如果已有同名文件,则同名文件将被覆盖。其实该步就是在明确数
据要存放的目的地。
b、调用write(String s)方法,将字符串写入到流中。
c、调用flush()方法,刷新该流的缓冲,将数据刷新到目的地中。
d、调用close()方法,关闭流资源。但是关闭前会刷新一次内部的缓冲数据,并将数据刷新到目的地中。
注意:① close()和flush()区别:
close()和flush()都能刷新流中的缓存,但是flush()刷新后流可以继续使用,但是close()关闭流之后,不能在写入字符串
② 文件的数据的续写是通过构造函数 FileWriter(Strings,boolean append),在创建对象时,传递一个true参数,代表不覆盖已有的文件。并在已有
文件的末尾处进行数据续写,默认覆盖
③ 在写入数据时,\n 为Linus系统下换行,在Windows下一般需加入转义字符即:\r\n ,,,
例子1:
1 import java.io.FileWriter; 2 import java.io.IOException; 3 4 /* 5 * 在硬盘上创建一个文件,并读入一些数据 6 */ 7 public class FileWriterDemo { 8 public static void main(String[] args) { 9 //1. 创建一个FileWriter文件流对象,该对象一初始化就要明确操作的文件 10 //该文件被创建到指定目录下,如果该目录下已经有同名文件,则被覆盖 11 //如果需要所写文件追加到已有文件后面,则使用构造方法 12 //FileWriter(String fileName, boolean append) 13 FileWriter fw = null; 14 try { 15 fw = new FileWriter("F:/javaee/text.txt"); 16 //2. 调用write方法,将字符写入流中 17 fw.write("abcdefg"); 18 //3. 刷新缓存,将流中的数据传输到目的地文件中 19 //fw.flush(); 21 } catch (IOException e) { 22 e.printStackTrace(); 23 }finally{ 24 try { 25 fw.close(); //4.记得在finally代码块中关闭流资源 26 } catch (IOException e) { 27 e.printStackTrace(); 28 } 29 } 30 } 31 }
1)创建一个文件读取流对象,和指定名称的文件相关联。要保证该文件已经存在,若不存在,将会发生异常FileNotFoundException。
2)调用读取流对象的read()方法。
第一种方式 read():读取单个字符,返回读取字符的Int型ascll码,如果已到达流的末尾,则返回 -1。
第二种方式 read(char[] c):将字符读入数组c,返回读取的字符数,如果已到末尾,返回-1。
3)读取后要调用close方法将流资源关闭。
例子2:读取硬盘上文件的数据
1 import java.io.*; 2 3 /* 4 * 读取硬盘上文件的数据 5 */ 6 public class FileReaderDemo { 7 8 public static void main(String[] args) { 9 singleCharReader(); // 第一种读取方式:单个字符读取 10 arrayCharReader(); // 第二种读取方式 :通过字符数组进行读取 11 } 12 13 /** 14 * 通过单个字符进行读取 15 */ 16 private static void arrayCharReader() { 17 FileReader fr = null; 18 int ch ; 19 try { 20 // 1. 创建文件流读取对象,文件名必须存在,否则会报异常 21 fr = new FileReader("F:/javaee/text.txt"); 22 // 2.调用read()方法读取字符 23 while((ch = fr.read()) != -1) { 24 System.out.print((char) ch); 25 } 26 } catch (IOException e) { 27 throw new RuntimeException("读取失败"); 28 } finally { 29 try { 30 fr.close(); // 3.最后记得关闭流资源 31 } catch (IOException e) { 32 e.printStackTrace(); 33 } 34 } 35 } 36 37 /** 38 * 通过字符数组读取数据 39 */ 40 private static void singleCharReader() { 41 FileReader fr = null; 42 int len = 0; 43 char[] arr = new char[1024];// 定义一个字符数组,用于临时存储读取的字符 44 try { 45 // 1. 创建文件流读取对象,文件名必须存在,否则会报异常 46 fr = new FileReader("F:/javaee/text.txt"); 47 // 2.调用read(char[] c)方法读取字符,返回读取成功的字符个数 48 while((len = fr.read(arr)) != -1){ 49 System.out.print(new String(arr, 0, len)); 50 } 51 } catch (IOException e) { 52 throw new RuntimeException("读取失败"); 53 } finally { 54 try { 55 fr.close(); // 3.最后记得关闭流资源 56 } catch (IOException e) { 57 e.printStackTrace(); 58 } 59 } 60 } 61 }
例子3:(综合案例)文件文本拷贝(c盘文件拷贝到d盘)
import java.io.*; /* * 功能实现: 将e盘一个文本文件复制到d盘 * 复制的原理: * 1. 在d盘创建一个文件。用于存储e盘文件中的数据。 * 2. 定义读取流和e盘文件关联。 * 3. 通过不断的读写完成数据存储。 * 4. 关闭资源 */ public class CopyDemo { /** * @param args */ public static void main(String[] args) { copy_1(); //复制方式1:1个1个字符的复制 copy_2(); //复制方式2:利用数组一次存取多个字符 } public static void copy_1() { FileWriter fw=null; FileReader fr=null; int ch = 0; try{ //1. 关联读取和写入的文件 //FileReader文件名必须存在,FileWriter文件名不存在会创建文件 fw=new FileWriter("d:/text.txt"); fr=new FileReader("F:/javaee/text.txt"); while((ch=fr.read())!=-1){ fw.write(ch);//2. 一个字符一个字符写入 } }catch(IOException e){ throw new RuntimeException("读写失败"); }finally{ try { fr.close(); //3.关闭流资源 fw.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void copy_2() { FileWriter fw=null; FileReader fr=null; char[] arr=new char[1024]; int len =0 ; try{ //1. 关联读取和写入的文件 //FileReader文件名必须存在,FileWriter文件名不存在会创建文件 fw=new FileWriter("d:/text.txt"); fr=new FileReader("F:/javaee/text.txt"); while((len = fr.read(arr))!=-1){ fw.write(arr,0,len);//2.利用数组一次存入多个字符 } }catch(IOException e){ throw new RuntimeException("读写失败"); }finally{ try { fr.close(); //3.关闭流资源 fw.close(); } catch (IOException e) { e.printStackTrace(); } } } }
要点: 1.缓冲区的出现提高了对数据的读写效率
2.缓冲区对节点流的功能进行了增强,属于处理流
3.在缓冲区创建前,要先创建流对象。即先将流对象初始化到构造函数中
原理:此对象中封装了数组,将数据存入,再一次性取出。
我们在使用时注重缓冲区的两点:1.效率 2.BufferWriter的newLine()换行方法以及BufferReader的readLine()读一行方法
1)创建一个字符写入流对象。
如:FileWriter fw=newFileWriter("text.txt");
2)为了提高字符写入流效率,加入缓冲技术。只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。
如: BufferedWriter bufw =new BufferedWriter(fw);
3)调用write方法写入数据到指定文件
如:bufw.write("adfg");
记住,只要用到缓冲区,就要记得刷新。(关闭流同样会刷新,但为了排除意外事故,保证数据存在,建议写入一次就刷新一次)
如:bufw.flush();
4)其实关闭缓冲区,就是在关闭缓冲区中的流对象。
如: bufw.close();
2. 读取流缓冲区BufferedReade
1)创建一个读取流对象和文件相关联
如: FileReader fr=new FileReader("text.txt");
2)为了提高效率。加入缓冲技术。将字符读取流对象作为参数传递给缓冲区对象的构造函数。
如: BufferedReader bufr=new BufferedReader(fr);
3)调用该缓冲区提供的readLine方法一行一行读取,如果到达文件末尾,则返回null
如: String s=bufr.readLine();
4)关闭流资源
如: bufr.close();
例子4:(综合案例)利用缓冲区实现文件文本拷贝(c盘文件拷贝到d盘)
import java.io.*; /* * 功能实现: 利用缓冲技术,将e盘一个文本文件复制到d盘 */ public class CopyDemo { public static void main(String[] args) { BufferedWriter bfw=null; BufferedReader bfr=null; String line = null; try{ //1. 创建读写缓冲流对象,将节点流以参数形式包含 bfw=new BufferedWriter(new FileWriter("D:/copy.txt")); bfr=new BufferedReader(new FileReader("F:/javaee/text.txt")); //2. 利用bufferReader的readLine()方法一次读取一行 while((line=bfr.readLine())!=null){ // 3. bufferWriter写入,用 newLine()换行 bfw.write(line); bfw.newLine(); bfw.flush();//将缓冲流中的数据刷新到指定文件中 } }catch(IOException e){ throw new RuntimeException("读写失败"); }finally{// 4.最后关闭流资源,输出和输入流分开关闭,关闭缓冲流就会关闭节点流资源 if(bfw!=null) try { bfw.close();//关闭写入流 } catch (IOException e) { throw new RuntimeException("写入流关闭失败"); } if(bfr!=null) try { bfr.close();//关闭读取流 } catch (IOException e) { throw new RuntimeException("读取流关闭失败"); } } } }
3. 带行号的缓冲区对象 —LineNumberReader 小注意点!!
在BufferedReader中有个直接的子类LineNumberReader,其中有特有的方法获取和设置行号:
setLineNumber();//设置初始行号
getLineNumber();//获取行号
3. 装饰设计模式
a、简述
当想对已有对象进行功能增强时,可定义类:将已有对象传入,基于已有对象的功能,并提供加强功能,那么自定义的该类称之为装饰类。
b、特点
装饰类通常都会通过构造方法接收被装饰的对象,并基于被装饰的对象的功能,提供更强的功能。
c、装饰和继承的区别:
1)装饰模式比继承要灵活。避免了继承体系的臃肿,且降低了类与类之间的关系。
2)装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强的功能,所以装饰类和被装饰的类通常都是属于一个体系。
3)从继承结构转为组合结构。
注:在定义类的时候,不要以继承为主;可通过装饰设计模式进行增强类功能。灵活性较强,当装饰类中的功能不适合,可再使用被装饰类的功能。
示例:上面讲到的MyBufferedReader的例子就是最好的装饰设计模式的例子。
第三讲 字节流
一. 概述
1.关于字符流和字节流:
a.字符流底层的读写是通过字节实现的,所以字节流能操作的数据类型比字符流广
b.通常在处理文本时优先使用字符流,其他的用字节流(如媒体数据,图片等)
2. 在对字节流进行写入(输出outputStream)操作时,不用进行刷新流操作
字符流之所以进行刷新流操作是因为在字符流写入字符数据时,需参照编码表编码成字节数据,然后flush进目的地。
而字节流直接操作字节数据所以直接写入目的地
3. InputStream特有方法:int available();//返回文件中的字节个数
注意: 可以利用此方法来指定读取方式中传入数组的长度,从而省去循环判断。但是如果文件较大,而虚拟机启动分配的默认内存一般为64M。当文件过大时,此数组长度所占内存空间就会溢出。所以,此方法慎用,当文件不大时,可以使用。
练习1: 利用字节流复制图片
import java.io.*; /* * 利用字节流复制图片 * 思路: * 1.用字节读取流(输入流)关联图片 * 2.用字节写入流(输出流)创建一个图片文件,用于获取接收到的图片 * 3.通过循环读写,完成数据的存储 * 4.关闭流资源 */ public class CopyPic { public static void main(String[] args) { FileOutputStream fos = null; FileInputStream fis = null; byte[] buf = new byte[1024]; int len = 0; try{ //1.文件字节输入流关联本地存在的图片 fis = new FileInputStream("F:/Pictures/icon.png"); //2.文件字节输出流 创建位置接收复制而来图片资源 fos = new FileOutputStream("F:/1.png"); //3.循环读写操作 while((len=fis.read(buf))!=-1){ fos.write(buf,0,len); } }catch(Exception e){ throw new RuntimeException("复制文件失败"); }finally{//4.最后记得分别关闭流资源 if(fis!=null) try { fis.close(); } catch (IOException e) { throw new RuntimeException("写入流关闭失败"); } if(fos!=null) try { fos.close(); } catch (IOException e) { throw new RuntimeException("读取流关闭失败"); } } } }
二. 字节流的缓冲区-bufferInputStream和bufferOutputStream
第四讲 流操作规律
一. 键盘录入
1.使用字节输入流,只能一个字节一个字节的录入,要录入整行可以使用一个数组或者StringBuilder,将录入的字节先存储起来,当录入完一行之后再一行显示出来
2.我们注意到这种正行录入的方式,和字符流读一行数据的原理是一样的,也就是readLine方法。所以我们可以将字节流转成字符流再使用字符流缓冲区的readLine方法,所以这种方法需要用到转换流,先看一个例子
例子1:使用Systen.in实现整行录入(字节流和转换流两种方法实现)
import java.io.*; /* * 实现:通过键盘录入整行数据,并大写输出打印 * 如果录入的是over,则停止录入 */ public class ReadLineDemo { public static void main(String[] args) throws IOException { readLine1(); //利用字节流,缓存数组,实现整行录入 readLine2(); //利用转换流inputStreamReader } /** * 利用字节流,缓存数组,实现整行录入 * @throws IOException */ private static void readLine1() throws IOException { InputStream is = System.in;//获取键盘录入对象 StringBuffer sb = new StringBuffer();//用于存储临时数据 while(true){ int ch = is.read(); // 在键盘录入时,换行对应两个字节'\r'和'\n',这两个字节不该 // 算在录入数据中 if(ch=='\r'){ continue; } if(ch=='\n'){//表示录入已经结束 if("over".equals(sb.toString())){ break; }else{ System.out.println(sb); sb.delete(0, sb.length()); } }else{ sb.append((char)ch); } } } /** * 利用转换流inputStreamReader->Reader->bufferReader * 最后使用readLine()方法实现 * @throws IOException */ private static void readLine2() throws IOException { //获取键盘录入对象 InputStream is = System.in; //将字节流转换为字符流,使用转换流InputStreamReader InputStreamReader isr = new InputStreamReader(is); //使用bufferReader提高效率,并使用readLine方法 BufferedReader bufr = new BufferedReader(isr); String line = null; while((line=bufr.readLine())!=null){ System.out.println(line.toUpperCase()); } bufr.close(); } }
1. 作用:①转换流是字符流与字节流之间的桥梁 ②方便了字符流与字节流之间的操作(比如上例中readLine操作)
2. InputStreamReader是字节流通向字符流的桥梁,它使用指定的 charset
读取字节并将其解码为字符
a、获取键盘录入对象。
InputStream in=System.in;
b、将字节流对象转成字符流对象,使用转换流。
InputStreamReaderisr=new InputStreamReader(in);
c、为了提高效率,将字符串进行缓冲区技术高效操作。使用BufferedReader
BufferedReaderbr=new BufferedReader(isr);
//键盘录入最常见写法
BufferedReaderin=new BufferedReader(new InputStreamReader(System.in));
3. OutputStreamWriter是字符流通向字节流的桥梁,使用指定的 charset
将要写入流中的字符编码成字节
* 字符通向字节:录入的是字符,存到硬盘上(本例中为控制台)的是字节。步骤和InputStreamReader转换流一样。
//BufferedWriter bw =new BufferedWriter(new OutputStreamWriter(System.out)
二. 流操作规律
通过三个明确来完成IO流的传输
① 明确源和目的
源:输入流 是InputStream? Reader?
目的:输出流 是OutputStream? Writer
② 操作的文本是否是文本数据?
是:字符流 否:字节流
③ 当体系明确后,再明确要使用哪个具体的对象。通过设备来进行区分:
源设备:内存,硬盘,键盘
目的设备:内存,硬盘,控制台
④ 是否需要提升效率?是否需要指定编码表
需要提升效率:加缓冲区 Buffer
需要指定编码表:用转换流
练习1: 将一个文本文件中数据存储到另一个文件中。复制文件
1)源: 因为是源,所以使用读取流:InputStream和Reader
明确体系:是否操作文本:是,Reader
明确设备:明确要使用该体系中的哪个对象:硬盘上的一个文件。Reader体系中可以操作文件的对象是FileReader
是否需要提高效率:是,加入Reader体系中缓冲区 BufferedReader.
FileReader fr = new FileReader("a.txt");
BufferedReader bufr = new BufferedReader(fr);
2) 目的:输出流:OutputStream和Writer
明确体系:是否操作文本:是,Writer
明确设备:明确要使用该体系中的哪个对象:硬盘上的一个文件。Writer体系中可以操作文件的对象FileWriter。
是否需要提高效率:是,加入Writer体系中缓冲区 BufferedWriter
FileWriter fw = new FileWriter("b.txt");
BufferedWriter bufw = new BufferedWriter(fw);
练习2:将一个图片文件中数据存储到另一个文件中。复制文件。要按照以上格式自己完成三个明确。
1)源:输入流,InputStream和Reader
是否是文本?否,InputStream
源设备:硬盘上的一个文件。InputSteam体系中可以操作文件的对象是FileInputSteam
是否需要提供效率:是,BufferedInputStream
BufferedInputSteambis=newBufferedInputStream(newFileInputStream("c:/users/asus/desktop/1.jpg"));
2)目的:输出流,OutputStream和Writer
是否是文本?否,OutputStream
源设备:硬盘上的文件,FileOutputStream
是否需要提高效率:是,加入BufferedOutputStream
BufferedOutputStreambos=newBufferedOutputStream(newFileOutputStream("c:/users/asus/desktop/2.jpg"));
练习3:将键盘录入的数据保存到一个文件中。
1)源:输入流:InputStream和Reader
是不是纯文本?是,Reader
设备:键盘。对应的对象是System.in。——为了操作键盘的文本数据方便。转成字符流按照字符串操作是最方便的。
所以既然明确了Reader,那么就将System.in转换成Reader。用Reader体系中转换流,InputStreamReader
InputStreamReaderisr = new InputStreamReader(System.in);
需要提高效率吗?需要,BufferedReader
BufferedReaderbufr = new BufferedReader(isr);
2) 目的:OutputStream Writer
是否是存文本?是!Writer。
设备:硬盘。一个文件。使用 FileWriter。
FileWriter fw = newFileWriter("c.txt");
需要提高效率吗?需要。
BufferedWriter bufw = new BufferedWriter(fw);
练习4(扩展):想要把键盘录入的数据按照指定的编码表(UTF-8)(默认编码表是GBK),将数据存到文件中。
1)源:同练习3
2)目的:OutputStream Writer
是否是存文本?是!Writer
设备:硬盘上的一个文件。使用 FileWriter。——但是FileWriter是使用的默认编码表:GBK。
而存储时,需要加入指定编码表utf-8。而指定的编码表只有转换流可以指定。所以要使用的对象是OutputStreamWriter。
该转换流对象要接收一个字节输出流,而且还可以操作的文件的字节输出流:FileOutputStream
OutputStreamWriter osw =new OutputStreamWriter(newFileOutputStream("d.txt"),"UTF-8");
需要高效吗?需要,BufferedWriter
BufferedWriter bufw = new BufferedWriter(osw);
注意:除了作为字节与字符转换的桥梁,涉及到字符编码转换时,也需要用到转换流。
练习5:将一个文本数据打印在控制台上。
1)源:InputStream Reader
是文本?是:Reader
设备:硬盘。上的文件:FileReader
是否需要提高效率?是:BufferedReader
BufferedReader br=new BufferedReader(newFileReader("1.txt"));
2)目的:OutputStream Writer
是文本?是:Writer
设备:控制台。对应对象System.out。由于System.out对应的是字节流,所以利用OutputSteamWriter转换流
是否提高效率?是:BufferedWriter
BufferedWriter bw =new BufferedWriter(newOutputStreamWriter(system.out));
补充小知识①:异常的日志信息
* 当程序在执行的时候,出现的问题是不希望直接打印给用户看的,是需要作为文件存储起来,方便程序员查看,并及时调整的
补充:通过System类的setIn,setOut方法可以对默认设备进行改变
System.setIn(newFileInputStream(“1.txt”));//将源改成文件1.txt。
System.setOut(newFileOutputStream(“2.txt”));//将目的改成文件2.txt
import java.io.*; import java.text.*; import java.util.*; /* * 需求:出现的异常以文件形式打印 * 异常条目:前面加时间,后面是异常信息 */ public class ExceptionInfo { public static void main(String[] args) { try { int[] arr = new int[2]; System.out.println(arr[3]); } catch (Exception e) { try { Date d = new Date();// 创建时间对象 // 时间模块格式对象 SimpleDateFormat sdf = new SimpleDateFormat( "yyyy年MM月dd日 HH:mm:ss"); String s = sdf.format(d); PrintStream ps = new PrintStream("E:/info.log");// 打印流对象 System.setOut(ps);// 修改输出流设备 ps.println(s); // 输出时间 } catch (IOException ex) { throw new RuntimeException("文件创建失败"); } e.printStackTrace(System.out); // 将异常信息输出指定输出流 } } }