一 ,IO流概述
java把所有传统的流类型都放在java.io包中,用以实现输入/输出功能。
1.1,输出流和输入流
输入流:只能从中读取数据,而不能向其写入数据。输出流:只能向其写入数据,而不能从中读取数据。
java的输入流主要由InputStream和Reader作为基类,而输出流则主要由OutputStream和Writer作为基类,他们都是抽象基类,无法直接创建对象。
1.2,字节流和字符流
字节流和字符流的用法几乎一样,区别在于字节流和字符流所操作的数据单元不同。字节流主要由InputStream和OutputStream作为基类,而字符流则主要由Reader和Writer作为基类。
字符流两个基类:融合了编码表,可以指定编码表。
通过以上分类,可知IO流有4个基类,OutputStream,InputStream,Reader,Writer,
此四个类派生出来的子类名称都是以父类名作为子类名的后缀,以前缀为其功能;如InputStream子类FileInputStream;Reader子类FileReader
二,字符流
2.1在Reader里主要有读的3个方法。
public int read():读取单个字符返回:如果已到达流的末尾,则返回 -1。
public int read(char[] cbuf) :将字符读入数组
返回:读取的字符数,如果已到达流的末尾,则返回 -1。
public abstract int read(char[] cbuf,int off,int len):将字符读入数组的某一部分
返回:读取的字符数,如果已到达流的末尾,则返回 -1 。
方法使用实例:
class FileReadDemo
{
public static void main(String[] args)
{
}
public static void method_1()
{
//创建一个文件读取流对象,和指定名称的文件相关联。
//要保证该文件是已经存在的,如果不存在,会发生异常,FileNotFoundException
FileReader fr = new FileReader("demo.txt");
int ch = 0;
while((ch=fr.read())!=-1)//已到末尾返回-1
{
System.out.println("ch="+(char)ch);
}
fr.close();
}
public static void method_2()
{
FileReader fr = new FileReader("demo.txt");
char[] buf = new char[1024];//定义一个字符数组,用于存储读到的字符
int num = 0;
while((num=fr.read(buf))!=-1)
{ //无论buf数组里多少字符,长度都是1024
System.out.print(new String(buf,0,num));//new String(buf,0,num) String类的构造器,可将数组封装成字符串
}
fr.close();
}
}
注意:程序里打开的文件IO资源不属于内存里的资源,垃圾回收机制无法回收该资源,所以应该显式关闭文件IO资源。
2.2Writer主要有写的3个方法。
void write(int c):写入单个字符。
public void write(char[] buf): 将字符数组中的数据输出到指定流中。
public void write(char[] buf,int off,int len): 将字符数组中从off位置开始,长度为len的字符输出到输出流中。
注意:因为字符流直接以字符作为操作单位,所以Writer可以用字符串来代替字符数组,即以String对象作为参数。
Writer里还包含如下两个方法:
void write(String str);将str字符串里包含的字符输出到指定输出流中。
void write(String str,int off,int len): 将str字符串里从off位置开始,长度为len的字符输出到指定输出流中。
方法使用实例:
class FileWriterDemo
{
public static void main(String[] args)
{
//创建一个FileWriter对象,该对象一被初始化,就必须要明确被操作的文件
//而且该文件会被创建到指定的目录下。如果该目录下已有同名文件,将被覆盖。
//其实该步就是在明确数据要存放的目的地
FileWriter fw = new FileWriter("demo.txt");
//传递一个true参数。代表不覆盖已有的文件,并在已有文件的末尾处进行数据续写。
//FileWriter fw = new FileWriter("demo.txt",true);
//调用writer方法,将字符串写入到流中。
fw.write("asdasd");
//刷新流对象中的缓冲中的数据。
//将数据刷到目的地中、
//fw.flush();
//关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据。
//将数据刷到目的地中
//和flush区别;flush刷新后,流可以继续使用,close刷新后,会将流关闭。
fw.close();
}
}
class FileWriterDemo2
{
public static void main(String[] args)
{
FileWriter fw = null;//在代码块外创建引用才能在所有代码块中使用引用。
try
{
fw = new FileWriter("demo.txt");
fw.write("ddsffdhgjf");
}
catch (IOException e)
{
System.out.println("catch:"+e.toString());
}
finally
{
try
{
if(fw!=null)//如果没有判断,可能产生空指针异常
fw.close();//也会抛出异常
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
}
}
注意:如果想要对一个已有文件进行续写的话,则应该在构造流的时候,再传一个参数,具体代码如下:
//传递一个true参数。代表不覆盖已有的文件,并在已有文件的末尾处进行数据续写。
FileWriter fw = new FileWriter("demo.txt",true);
2.3字符流的缓冲区
2.3.1概述
(1)缓冲区的出现提高了对数据的读写效率
(2)缓冲技术原理:此对象中封装了数组,将数据存入,再一次性取出。
(3)对应类
BufferedWriter
BufferedReader
(4)缓冲区要结合流才可以使用。
2.3.2,BufferedWriter
1)构造函数
BufferedWriter(Writer out):创建一个使用默认大小输出缓冲区的缓冲字符输出流。
BufferedWriter(Writer out, int sz): 创建一个使用给定大小输出缓冲区的新缓冲字符输出流。
2)BufferedWriter使用实例
注意:BufferedWriter缓冲区中提供了一个跨平台的换行符,newLine()
class BufferedWriterDemo
{
public static void main(String[] args) throws IOException
{
//创建一个流对象
FileWriter fw = new FileWriter("buf.txt");
//为了提高字符写入流的效率。加入了缓冲技术。
//只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。
BufferedWriter bufw = new BufferedWriter(fw);
for(int x=1; x<5; x++)
{
bufw.write("abcd"+x);
//newLine();可以在不同操作系统上调用,用作数据换行
bufw.newLine();
bufw.flush();
}
//在写入的时候可以用缓冲区的方法。
//bufw.write("asdfee");
//记住只要用到缓冲区,就要刷新。
bufw.flush();
//其实关闭缓冲区就在关闭缓冲区的流对象。
bufw.close();
}
}
2.3.3,BufferedReader
1)构造函数
BufferedReader(Reader in) :创建一个使用默认大小输入缓冲区的缓冲字符输入流。
BufferedReader(Reader in, int sz):创建一个使用指定大小输入缓冲区的缓冲字符输入流。
2)readLine()
该缓冲区区提供了一个一次读一行的方法readLine(),方便于对文本数据的获取。
当返回null时,表示读到文件末尾。readLine方法返回的时候只返回回车符之前的数据内容,并不返回回车符。
readLine()原理:
无论是读一行,或者读取多个字符。其实最终都是在在硬盘上一个一个读取,所以最终使用的还是read方法一次读一个的方法。
根据原理:实现一个BufferedReader与readLine()
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 BufferedReaderDemo
{
public static void main(String[] args) throws IOException
{
//创建一个读取流对象和文件相关联。
FileReader fr = new FileReader("buf.txt");
//为了提高效率。加入缓冲技术,将字符读取对象作为参数传递给缓冲对象的构造函数。
BufferedReader bufr = new BufferedReader(fr);
String line = null;
while((line=bufr.readLine())!=null)
{
System.out.println(line);
}
bufr.close();
}
}
2.3.4练习(字符流缓冲区)
/*
通过缓冲区复制一个.java文件。
*/
import java.io.*;
class CopyTextByBuf
{
public static void main(String[] args)
{
BufferedReader bufr = null;
BufferedWriter bufw = null;
try
{
bufr = new BufferedReader(new FileReader("BufferedWriterDemo.java"));
bufw = new BufferedWriter(new FileWriter("bufwriter_Copy.txt"));
String line = null;
while((line=bufr.readLine())!=null)
{
bufw.write(line);
bufw.newLine();//因为readLine()不能返回换行符,所以写的时候要写入换行符。
bufw.flush();
}
}
catch (IOException e)
{
throw new RuntimeException("读写失败");
}
finally
{
try
{
if(bufr!=null)
bufr.close();
}
catch (IOException e)
{
throw new RuntimeException("读取关闭失败");
}
try
{
if(bufw!=null)
bufw.close();
}
catch (IOException e)
{
throw new RuntimeException("写入关闭失败");
}
}
}
}
三,装饰模式
1,概述:
当想要对已有的对象进行功能增强时,可以定义类。将已有对象传入,基于已有的功能,并提供加强功能。那么自定义的该类就称为装饰类。
装饰类通常会通过构造方法接收被装饰对象,并基于被装饰对象的功能,提供更强的功能。
例如:BufferedReader的readLine()的出现,是增强了read()方法的功能,这个功能的实现通过把被增强的对象传给一个增强类。这种方式称之为装饰设计模式。
2,装饰和继承的区别:
装饰模式比继承要灵活,避免了继承体系的臃肿,而且降低了类与类之间的关系。假设有一个继承体系,如下:
MyReader//专门用于读取数据的类。
|--MyTextReader
|--MyBufferTextReader
|--MyMediaReader
|--MyBufferMediaReader
|--MyDataReader
因为要对每个进行功能增强,就要定义多个类来继承父类,从而增强父类的功能,但是这个体系很复杂,臃肿。|--MyBufferDataReader
那么可以换个思考方式
可以定义一个BufferReader继承基类,从而属于这个继承体系。当要增强哪个类,就将它作为参数传给修饰类的构造函数。
class MyBufferReader extends MyReader
{
MyBufferReader(MyReader)
}
改为如下体系:
MyReader//专门用于读取数据的类。
|--MyTextReader
|--MyMediaReader
|--MyDataReader
|--MyBufferReader
装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了更强的功能,所以装饰类和被装饰类同属于同一个体系。
3,LineNumberReader(BufferedReader 的子类)
LineNumberReader也是一个修饰类。
1)方法
setLineNumber(int number);设置行号
getLineNumber();获取行号
2)自定义LineNumberReader
class MyLineNumberReader extends MyBufferedReader
{
private int lineNumber;
MyLineNumberReader(Reader r)
{
super(r);
}
public String myReadLine()throws IOException
{
lineNumber++;
return super.myReadLine();
}
public void setLineNumber(int lineNumber)
{
this.lineNumber = lineNumber;
}
public int getLineNumber()
{
return lineNumber;
}
}
四,字节流
1,字节流方法
与字符流的方法差不多,只不过字节流操作的是字节。所以字符流里的参数是字符数组一般改为字节数组,便是字节流的读写方法。
注意:
1)BufferedWriter的Write(int b)方法是向输出流写入一个字节要写入的字节是参数 b
的八个低位,b
的 24 个高位将被忽略
2)BufferedReader的read()方法会将字节byte型值提升为int型值
2,练习(字节流)
复制一个图片
思路:
1,用字节读取流对象和图片关联。
2,用字节写入流对象创建一个图片文件,用于存储获取到的图片数据。
3,通过循环读写,完成数据的存储。
4,关闭资源。
字符流复制,可能打开不了。
*/
import java.io.*;
class CopyPic
{
public static void main(String[] args)
{
FileOutputStream fos = null;
FileInputStream fis = null;
try
{
fos = new FileOutputStream("c:\\1.jpg");
fis = new FileInputStream("c:\\3.jpg");
byte[] buf = new byte[1024];
int len = 0;
while((len=fis.read(buf))!=-1)
{
fos.write(buf,0,len);//不用刷新也有数据: 。
} //<span style="font-family: Arial, Helvetica, sans-serif;">一个中文有两个字节,读一个字节后不能立刻操作,需要再读一个字节,临时存起来,查表。所以需要刷新</span>
}
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("写入关闭失败");
}
}
}
}
3,字节流缓冲区
1)字节读取流缓冲区基本原理
1,将字节流封装到缓冲区里
2,通过字节流的read(byte[] buf)方法,将数据存储到buf数组中。
3,通过BufferedInputStreaem的read()方法,从数组里一个一个取出来。
现根据原理自定义一个字节流缓冲区和read()方法:
import java.io.*;
class MyBufferedInputStream
{
public static void main(String[] args)
{
}
}
class MyBufferedInputStream
{
private InputStream in;
private byte[] buf = new byte[1024];
private int pos = 0;count = 0;
MyBufferedInputStream(InputStream in)
{
this.in = in;
}
//一次读一个字节,从缓冲区(字节数组)获取、
public int myRead()throws IOException //为什么返回的不是byte?被提升。由4个字节存放
{ //在读到8个1时为了避免和判断结束标记-1相同,可以在保留8个1的情况下,在前面补0,那就不是-1是255
//通过in对象读取硬盘上的数据,并存储 buf中。
if(count==0)
{
count = in.read(buf);
if(count<0)
return -1;
pos = 0;
byte b = buf[pos];
count--;
pos++;
return b&255;
}
else if(count>0)
{
byte b = buf[pos];
count--;
pos++;
return b&255;
}
return -1;
}
public void myClose()throws IOException
{
in.close();
}
}
五,转换流
转换流是字符和字节之间的桥梁。通常,涉及到字符编码转换时,需要用到转换流。
5.1转换流应用
readLine方法是字符流BufferedReader类中方法。
而键盘录入的read方法是字节流InputStream的方法。
那么能不能将字节流转成字符流在使用字符流缓冲区的readLine方法呢?通过转换流可以实现
具体用法见以下代码:
import java.io.*;
class TransStreamDemo
{
public static void main(String[] args) throws IOException
{
//获取键盘录入对象,字节流
InputStream in = System.in;
//将字节流对象转成字符流对象,使用转换流。InputStreamReader
InputStreamReader isr = new InputStreamReader(in);
//为了提高效率,将字符流进行缓冲技术高效操作。使用BufferedReader
BufferedReader bufr = new BufferedReader(isr);
//BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
//键盘录入就输以上语句,
//字符流通向字节流的桥梁
OutputStream out = System.out;
OutputStreamWriter osw = new OutputStreamWriter(out);
BufferedWriter bufw = new BufferedWriter(osw);
String line = null;
while((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
//System.out.println(line.toUpperCase());
bufw.write(line.toUpperCase()+);//要刷新,数据才能出来。
bufw.newLine();//换行。
bufw.flush();
}
bufr.close();
}
}
5.2流操作规律
1)通过三个明确来完成。
1,明确源和目的。
源:输入流。
目的:输出流。
2,明确操作的数据是否是纯文本。
是:用字符流。
不是:字节流
3,当体系明确后,在明确要使用哪个具体的对象。
通过设备进行区分:
源设备:内存,硬盘,键盘。
目的设备:内存,硬盘,控制台。
例子:
1,将一个文本文件中数据存储到另一个文件中,复制文件。
1)源:因为是源所以选择读取流,InputStream ,Reader
操作文本文件:Reader
设备是硬盘上的文件:FileReader
BufferedReader bufr = new BufferedReader(new FileReader("a.txt"));
2)目的:OutputStream ,Writer
操作文本文件:Writer
设备是硬盘上的文件:FileWriter
BufferedWriter bufw = new BufferedWriter(new FileWriter("b.txt"));
2,将键盘录入的数据保存到一个文件中。
1)源:InputStream ,Reader
操作纯文本:Reader
设备是键盘对应对象:System.in
System.in对应的是字节流,为了操作键盘的文本数据方便, 转成字符流按照字符串操作是最方便的。所以既然明确了Reader。那么将System.in转换成Reader,用Reader体系中转换流,InputStreamReader
InputStreamReader isr = new InputStreamReader(System.in);
加入缓冲区提高效率。
BufferedReader bufr = new BufferedReader(isr);
2)目的:OutputSteam ,Writer
操作存文本: Writer
设备是硬盘(一个文件:FileWriter
加入缓冲区提高效率
BufferedWriter bufw = new BufferedWriter(fw);
扩展,想要把录入的数据按照指定的编码表(utf-8),将数据存到文件中
目的:OutputSteam Writer
操作存文本 Writer
设备:硬盘(一个文件)FileWriter
但是存储时,需要加入指定编码表,而指定的编码表只有转换流可以指定。
所以要使用的对象时OutputStreamWriter.
而该转换流对象要接收一个字节输出流,而且还可以操作的文件的字节输出流,FileOutputStream
OutputStreamWriter osw = new OutputStream(new FileOutputStream("d.txt"),"utf-8");
练习(自己完成三个明确)
1,将一个将一个图片文件中数据存储到另一个文件中。
1)源:InputStream,Reader
操作字节,InputStream
源设备:硬盘上的文件,FileInputSteam
要提高效率:BufferedInputStream
BufferedInputSteambis=newBufferedInputStream(newFileInputStream("a.jpg"));
2)目的:输出流,OutputStream,Writer
操作字节,OutputStream
设备:硬盘上的文件,FileOutputStream
要提高效率:BufferedOutputStream
BufferedOutputStreambos=newBufferedOutputStream(newFileOutputStream("b.jpg"));
2,将一个文本数据打印在控制台上。
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.in默认的输入设备是键盘
System .out默认的输出设备是控制台
通过System类的setIn(InputStream)和setOut(OutputStream)和改变标准输入输出设备,
比如:
System.setIn(new FileInputStream("PersonDemo.java"));
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
这样标准输入设备就是一个文件。