IO流是Javase中比较重要的一部分,也是我们所编写的代码和文件相关联的一种体现,能够熟练的理解和运用IO流技术,可以让我们的编程能力有质一般的飞跃。今天,一起走进IO流的世界,体验一下IO的神奇色彩。
*IO流
Java对数据的操作是通过流的方式,而IO流是用来处理设备之间的数据传输,java用于操作流对象都在IO包中,流对象操作数据分为两种:字节流和字符流,而流按照流向分为输入流和输出流。
一、IO流常用的基类:
字节流的抽象基类:InputStream,OutputStream
字符流的抽象基类:Reader,Writer
注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。如InputStream的子类FileInputStream,Reader的子类FileReader。
二、字符流:
既然IO流是用于操作数据的,那么数据的最常见的体现形式是文件。那么先一文件演示为主。
字符流的几个常用类:
FileReader FileWriter
BufferedReader BufferedWriter
需求:在硬盘上,创建一个文件并写入一些文字数据。
思路:找到一个专门用于操作文件的Writer子类对象,FileWriter,后缀名是父类名,前缀名是该流对象的功能。
示例代码如下:
import java.io.*;
class FileWriterDemo
{
public static void main(String[] args) throws IOException
{
//创建一个FileWriter对象,该对象一被初始化就必须要有被操作的文件。
//而且该文件会被创建下指定的目录下。如果该目录下已有同名文件,将覆盖原文件。
//其实该布就是在明确数据要存放的目的地。
FileWriter fw = new FileWriter("d:\\1.txt");
//调用write方法将字符串写入到流中。
fw.write("abcde");
//刷新流对象中的缓冲中的数据,将数据刷到目的地中。
// fw.flush();
//关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据,将数据刷到目的地中。
//和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭。
fw.close();
// fw.write("fghijk"); //流关闭以后将不再能够写入,但是刷新之后还可以写入。
}
}
三、IO异常的处理:
IO异常的处理并非是我们日常中的throws抛出方法,因为IO有专业的try catch处理方式,其方式如下:
import java.io.*;
class FileWriterDemo
{
public static void main(String[] args) throws IOException
{
FileWriter fw = null; //此处创建引用为null是为让finally代码块中使用到fw对象
try
{
fw = new FileWriter("k:\\demo.txt"); //将fw进行文件的关联
fw.write("abcdefg"); //写入相应的数据
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally //关流是必要的操作 所以定义在finally代码块中
{
try
{
if(fw!=null) //判断是否为null是为了增强程序的健壮性。
fw.close(); //关流动作是会发生异常的,所以需要处理
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
}
}
import java.io.*;
class FileReaderDemo
{
public static void main(String[] args) throws IOException
{
//创建一个文件读取流对象,和指定名称的文件相关联。
//要保证该文件是已经存在的,如果不存在,会发生异常FileNotFoundException
FileReader fr = new FileReader("d:\\a.txt");
/*
第一种方式,通过字符读取
//调用读取流对象的read方法,read方法一次读一个字符,并且自动往下读
int i = 0;
while((i = fr.read())!=-1){
System.out.println((char)i);
}
*/
//第二种方式:通过字符数组进行读取
//该read ( char [ ] )返回的是读到字符的个数
char[] buf = new char[1024];
int num = 0;
while((num = fr.read(buf))!=-1){
System.out.println(buf,0,num);
}
//关闭流
fr.close();
}
}
缓冲区的出现是为了提高流的操作效率而出现的,所以在创建缓冲区之前,必须要现有流对象。
BufferedReader该缓冲区中提供了一个跨平台的换行符,就是newLine( ) 方法
BufferedWriter 该缓冲区提供了一次读取一行的方法,方便于对文本数据的获取。 readLine ( ) 方法,当返回nul时,表示读到文件末尾。需要注意的是,readLine方法返回的是回车符之前的数据内容,并不返回回车符,需要自行加入回车符。
使用字符流缓冲区进行字符文件的复制代码:
import java.io.*;
class CopyTextByBuf
{
public static void main(String[] args)
{
BufferedReader bufr = null;
BufferedWriter bufw = null;
try
{
//将字符读取流对象作为参数传递给缓冲对象的构造函数。
bufr = new BufferedReader(new FileReader("a.txt"));
bufw = new BufferedWriter(new FileWriter("b.txt"));
//使用缓冲区特有的读取一行的方法
String line = null;
while((line = bufr.readLine())!=null){
//此时写上的为读取到的有效数据
bufw.write(line);
//此处应加上换行
bufw.newLine();
bufw.flush();
}
}
catch (IOException e)
{
throw new RuntimeException("读写失败");
}
finally
{
//关闭流动作是必须执行的
if(bufr!=null)
try
{
bufr.close();
}
catch (IOException e)
{
throw new RuntimeException("关流失败");
}
if(bufw!=null)
try
{
bufw.close();
}
catch (IOException e)
{
throw new RuntimeException("关流失败");
}
}
}
}
六、装饰设计模式
当想要对已有对象进行功能增强时,可以定义类,将已有对象传入,基于已有的功能,并提供加强功能,那么可以自定义的类称为装饰类。
装饰类通常会通过构造方法接收被装饰的对象,并基于被装饰对象的功能提供更强的功能。
装饰设计模式的通俗理解代码如下:
class Person
{
private void eat()
{
System.out.println("something eat");
}
}
//没有使得Person和SuperPerson产生相应的关系,降低耦合性
class SuperPerson
{
private Person p;
SuperPerson(Person p){
this.p = p;
}
public void superEat()
{
System.out.println("something drink");
p.eat();
System.out.println("something somke");
}
}
class PersonDemo
{
public static void main(String[] args)
{
Person p = new Person();
//p.eat();
SuperPerson sp = new SuperPerson(p);
sp.supereat();
}
}
装饰模式的特点:
装饰模式比继承更为灵活,避免了体系的臃肿。而且降低了类与类之间的关系。
装饰类因为增强已有对象,具备的功能和已有的相同,只不过提供了增强功能,所以装饰类和被装饰类通常是都属于一个体系中的。
七:字节流
基类: InputStream OutputStream
缓冲区: BufferedInputStream BufferedOutputStream
使用字节流对图片的复制代码如下:
import java.io.*;
class CopyPicTest
{
public static void main(String[] args)
{
FileInputStream fis = null;
FileOutputStream fos = null;
try
{
//用字节读取流对象和图片文件关联
fis = new FileInputStream("c:\\a.jpg");
//用字节读取流对象创建一个图片文件,用于存储获取到的图片数据
fos = new FileOutputStream("c:\\b.jpg");
//通过循环读写,完成数据的存储
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf))!=-1){
fos.write(buf);
}
}
catch (IOException e)
{
throw new RuntimeException("复制文件失败");
}
finally
{
try
{
//关闭资源
if(fis != null)
fis.close();
}
catch (IOException e)
{
throw new RuntimeException("资源关闭失败");
}
try
{
//关闭资源
if(fos != null)
fos.close();
}
catch (IOException e)
{
throw new RuntimeException("资源关闭失败");
}
}
}
}
字节流缓冲区:
和字符流一样,字节流也有相应的缓冲区,使用缓冲区使得效率更为高效,下为使用字节流缓冲区复制mp3,并计算出复制mp3所使用的时间的代码:
import java.io.*;
class CopyMP3Test
{
public static void main(String[] args)
{
long start = System.currentTimeMillis();
copy_1();
long end = System.currentTimeMillis();
System.out.println((end-start)+"毫秒");
}
public static void copy_1(){
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try
{
//建立缓冲区对象并关联相关的mp3文件
bis = new BufferedInputStream(new FileInputStream("c:\\a.mp3"));
bos = new BufferedOutputStream(new FileOutputStream("c:\\b.mp3"));
//进行高效的读写动作
int by = 0;
while((by = bis.read())!=-1){
bos.write(by);
}
}
catch (IOException e)
{
throw new RuntimeException("复制失败");
}
finally
{
//关闭流的动作
if(bis!=null)
try
{
bis.close();
}
catch (IOException e)
{
throw new RuntimeException("关闭失败");
}
if(bos!=null)
try
{
bos.close();
}
catch (IOException e)
{
throw new RuntimeException("关闭失败");
}
}
}
}
八、转换流:
转换流:
InputStreamReader:字节到字符的桥梁,解码
OutputStreamWriter:字符到字节的桥梁,编码
例题:通过转换流将键盘的录入数据进行在控制台上的打印动作,代码如下:
import java.io.*;
class TransStreamDemo
{
public static void main(String[] args) throws IOException
{
//获取键盘录入
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//关联输出为控制台
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
//对源和目的进行读写操作
String line = null;
while((line = bufr.readLine())!=null){
if("over".equals(line)){
break;
}
bufw.write(line.toUpperCase());
bufw.newLine();
bufw.flush();
}
//关闭流资源
bufr.close();
bufw.close();
}
}
上述例子中,只要控制好源和目的,就能将数据进行相应的操作,如果控制目的为文件,即为将键盘录入写入到文件中去。
九、流的操作规律:
开发中最痛苦的事情莫过于流对象很多,但是不知道用哪一个。
通过两个明确来完成
1,明确源和目的
源:输入流。 InputStream Reader
目的:输出流。OutputStream Writer
2,操作的数据是否为纯文本。
是:字符流。
不是:字节流。
3,当体系明确后,再明确要使用哪个具体的对象。
通过设备来进行区分:
源设备:
硬盘:File
键盘:System.in
内存:数组
网络:socket流
目的设备:
硬盘:File
控制台:System.out
内存:数组
网络:Socket流
4,是否需要其他额外功能。
1,需要高效:
是,添加缓冲区Buffered
2,需要转换
是,使用转换流
实际需求:
1,将一个文本文件中数据存储到另一个文件中,复制文件。
源:因为是源,所以使用读取流,InputStream Reader
是不是操作文本文件
是,这时就可以选择Reader
这样体系就明确了
接下来明确要使用该体系中的哪个对象。
明确设备:硬盘。
Reader体系中可以操作文件的对象是 FileReader。
是否需要提高效率:
是,加入Reader体系中的缓冲区BufferedReader
FileReader fr = new FileReader("a.txt");
BufferedReader bufr = new BufferedReader(fr);
目的: OutputStream Writer
是否为纯文本
是:Writer。
设备:硬盘,一个文件
Writer体系中可以操作文件的对象FileWriter
是否需要提高效率,是,加入Writer体系中的缓冲区,BufferedWriter
FileWriter fw = new FileWriter("b.txt");
BufferedWriter bufw = new BufferedWriter(fw);
2,将键盘录入的数据保存到一个文件中
源:InputStream Reader
是不是纯文本: 是,Reader
设备:键盘。对应的对象是System.in
为了操作键盘的文本数据方便,需要将字节流转成字符流,按照字符串操作是最方便的。
所以既然明确了Reader,那么就将System.in转成字符流。用到了Reader体系中的转换流。InputStreamReader
InputStreamReader isr = new InputStream(System.in);
需要提高效率么?需要,使用BufferedReader
BufferedReader bufr = new BufferedReader(isr);
目的:OutputStream Writer
是否为纯文本? 是,使用Writer
设备:硬盘,一个文件 。 使用FileWriter
FileWriter fw = new FileWriter("c.txt");
FileWriter使用的是默认的编码表,GBK,不满足下述要求
*扩展:想要把录入的数据按照指定的编码表(UTF-8)将数据存入到文件中。
存储时,需要加入指定的编码表,而指定的编码表只有转换流可以指定,所以需要使用OutputStreamWriter
而该转换流需要接受一个字节输出流,而且还可以操作文件的字节输出流。 FileOutputStream,所以代码应如下:
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF-8");
需要提高效率么?需要
BufferedWriter bufw = new BufferedWriter(osw);
所以,需要注意的是:转换流什么时候使用,字符和字节之间的桥梁,通常涉及到字符编码转换时,需要用到转换流。
十、异常信息及日志文件的建立
如何将异常信息写入到相应的日志文件中呢?要注意的细节很多,同时也运用到了IO流技术。那么异常信息的书写方式如下:
import java.io.*;
import java.util.*;
import java.text.*;
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("exception.log");
//将时间信息添加到日志文件中
ps.println(s);
//将目的设置为日志文件中
System.setOut(ps);
}
catch (Exception ee)
{
//此处抛出日志创建未成功异常
throw new RuntimeException("日志文件创建失败");
}
finally
{
//关流动作
ps.close();
}
//将异常信息写入到日志文件中
e.printStackTrace();
}
}
}