个人小结:数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。根据处理数据类型的不同可分为:字符流和字节流。本篇主要介绍IO流中的字符流。
一、IO(Input Output)流
1、IO流用来处理设备间的数据传输。
2、Java对数据的操作是通过流的方式。
3、Java用于操作流的对象都在IO包中。
4、流按操作数据分为两种:字节流和字符流。
5、流按流向分为:输入流、输出流。
二、IO流常用基类
1、字节流的抽象基流:InputStream ,OutputStream
2、字符流的抽象基流:Reader ,Writer
3、由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀。
如:InputStream子类 FileInputStream; Reader子类 FileReader。
三、字符流
特点:
既然IO流是用于操作数据的,数据的最常见的体现形式是:文件。那么先以操作文件为主来演示
需求:
在硬盘上,创建一个文件并写入一些文字。找到一个专门用于操作文件的writer子类对象。FileWriter.后缀名是父类名,前缀名是该流对象的功能。
import java.io.*;
class FileWriterDemo
{
public static void main(String[] args) throws IOException
{
//创建一个FileWriter对象,该对象一被初始化就必须要明确被操作的文件。
//而且该文件会被创建到指定目录下。如果该目录下已有同名文件,将被覆盖。
//其实该步就是在明确数据要存放的目的地。
FileWriter fw = new FileWriter("demo.txt");
//调用write方法,将字符串写入到流中。
fw.write("abcde");
//刷新流对象中的缓冲中的数据
//将数据刷到目的地中。
fw.flush();
fw.write("haha");
fw.flush();
//关闭流资源,但是关闭之前会刷新一次内部缓冲中的数据
//将数据刷到目的地中,和flush的区别:
//flush刷新后,流可以继续使用,close刷新后,会将流关闭。
fw.close();
fw.write("hhehe");
}
}
运行结果:
四、IO异常的处理方式
注意:关闭资源动作一定要有,因此要放在finally代码块里,并且要单独try处理。
import java.io.*;
class FileWriterDemo2
{
public static void main(String[] args)
{
FileWriter fw = null;
try
{
fw = new FileWriter("k:\\demo.text");
fw.write("abcdefg");
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally
{
try
{
if(fw!=null)//为了健壮性,这个判断一定要有!
fw.close();
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
}
}
五、对已知文件的续写
/*
需求:
演示对已有文件的数据续写
*/
import java.io.*;
class FileWriterDemo3
{
public static void main(String[] args) throws IOException
{
//传递一个true参数,代表不覆盖已有的文件,并在已有文件的末尾处进行数据续写。
FileWriter fw = new FileWriter("demo.txt",true);
char[] cha = {'a','a','b','c'};
fw.write(cha);
fw.write("\r\nhhahhahhahah");
fw.close();
}
}
运行结果:
六、文本文件读取方式一
通过FileReader 对象的 read()方法,该方法 一次读一个字符,而且会自动往下读。
画图解释原理:
代码演示:
import java.io.*;
class FileReaderDemo
{
public static void main(String[] args) throws IOException
{
//创建一个文件读取流对象,和指定名称的文件相关联。
//要保证该文件是已经存在的,若不存在,会发生异常:FileNotFoundException.
FileReader fr = new FileReader("demo.txt");
//调用读取流对象的read方法
/*
int ch1 = fr.read();
System.out.println("ch1="+(char)ch1);
int ch2 = fr.read();
System.out.println("ch2="+(char)ch2);
*/
/*
while (true)
{
int ch = fr.read();
if(ch==-1)
break;
System.out.println("ch="+(char)ch);
}
*/
int ch=0;
while ((ch=fr.read())!=-1)
{
System.out.println("ch="+(char)ch);
}
fr.close();
}
}
运行结果:
七、文本文件读取方式二
通过字符数组进行读取。
代码演示:
import java.io.*;
class FileReaderDemo2
{
public static void main(String[] args) throws IOException
{
FileReader fr = new FileReader("demo.txt");
//定义一个字符数组,用于存储读到的字符。
//该read(char[])返回的是读到字符个数。
char[] buf = new char[1024];
int num = 0;
while ((num=fr.read(buf))!=-1)
{
System.out.println(new String(buf,0,num));
}
fr.close();
}
}
练习:读取一个.java文件,并打印在控制台上。
import java.io.*;
class FileReaderTest
{
public static void main(String[] args) throws IOException
{
FileReader fr = new FileReader("FileReaderDemo.java");
char[] cha = new char[1024];
int num = 0;
while ((num = fr.read(cha))!=-1)
{
System.out.print(new String(cha,0,num));
}
fr.close();
}
}
运行结果:
练习:将c盘的一个文本文件复制到D盘。
原理:
其实就是将c盘下的文件数据存储到d盘的一个文件中。
步骤:
1、在d盘创建一个文件,用于存储c盘文件中的数据。
2、定义读取流和c盘文件关联。
3、通过不断的读写完成数据存储。
4、关闭资源。
代码:
import java.io.*;
class CopyText
{
public static void main(String[] args) throws IOException
{
copy_1();
copy_2();
}
//方法一:从C盘读一个字符,就往D盘写一个字符。
public static void copy_1() throws IOException
{
//创建目的地
FileWriter fw = new FileWriter("runtime_copy.txt");
FileReader fr = new FileReader("RuntimeDemo.java");//创建读取流对象
int ch = 0;
while ((ch=fr.read())!=-1)//边读边写
{
fw.write(ch);
}
fw.close();
fr.close();
}
//<span style="font-family: 微软雅黑;">方法二:</span>从C盘读所有字符,放到数组,然后往D盘写所有的字符。
public static void copy_2() throws IOException
{
FileWriter fw = null;
FileReader fr = null;
try
{
fw = new FileWriter("d:\\demo.txt");
fr = new FileReader("c:\\yuanwenjian.txt");
char[] buff = new char[1024];
int len=0;
while ((len=fr.read(buff))!=-1)
{
fw.write(buff,0,len);
}
}
catch (IOException e)
{
throw new RuntimeException ("读写失败");
}
finally
{
if(fr!=null)
try
{
fr.close();
}
catch (IOException e)
{
}
if(fw!=null)
try
{
fw.close();
}
catch(IOException e)
{
}
}
}
}
八、字符流的缓冲区
1、缓冲区的出现提高了对数据(流)的读写效率。
所以在创建缓冲区之前,必须要现有流对象。该缓冲区中提供了一个跨平台的换行符:newLine();
2、对应类:BufferedWriter,BufferedReader
3、缓冲区要结合流才可以使用
4、在流的基础上对流的功能进行了增强。
代码示例:用 BufferedWriter向一个文本写入数据
import java.io.*;
class BufferedWriterDemo
{
public static void main(String[] args) throws IOException
{
//创建一个字符写入流对象。
FileWriter fw = new FileWriter("buf.txt");
//为了提高字符写入流效率,加入了缓冲技术。
//只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数即可。
BufferedWriter bufw = new BufferedWriter(fw);
for (int x=0;x<5 ;x++ )
{
bufw.write("abcde"+x);
bufw.newLine();
bufw.flush();
}
//记住,只要用到缓冲区,就要记得刷新。
//bufw.flush();
//其实关闭缓冲区,就是在关闭缓冲区中的流对象。
bufw.close();
//fw.close();不用写了
}
}
运行结果:
代码示例:用字符读取流缓冲区 BufferedReader 来读取文件中的数据
注:该缓冲区提供了一个一次读一行的方法readLine(),方便于对文本数据的获取,当返回null时,表示读到文件末尾。
readLine()方法返回的时候,只返回回车符之前的数据内容,并不返回回车符。
import java.io.*;
class BufferedReaderDemo
{
public static void main(String[] args) throws IOException
{
//创建一个读取流对象和文件相关联。
FileReader fr = new FileReader("buf.txt");
//为了提高效率,加入缓冲技术,将字符读取流对象作为参数传递给
//缓冲对象的构造函数。
BufferedReader bufr = new BufferedReader(fr);
//String s1 = bufr.readLine();
//System.out.println("s1:"+s1);
String line = null;
while ((line=bufr.readLine())!=null)
{
System.out.println(line);
}
bufr.close();
}
}
运行结果:
练习:通过缓冲区复制一个.java文件
import java.io.*;
class CopyTextByBuf
{
public static void main(String[] args)
{
BufferedReader bufr = null;
BufferedWriter bufw = null;
try
{
//创建读取缓冲区
bufr = new BufferedReader(new FileReader("BufferedReaderDemo.java"));
//创建写入缓冲区
bufw = new BufferedWriter(new FileWriter("BufferedReaderCopy.txt"));
//边读边写
String line =null;
while ((line=bufr.readLine())!=null)
{
bufw.write(line);
bufw.newLine();//换行
bufw.flush();//刷新写入流
}
}
catch (IOException e)
{
throw new RuntimeException("读写失败");
}
finally
{
try
{
if (bufr!=null)
bufr.close();//关闭资源
}
catch (IOException e)
{
throw new RuntimeException("读写失败");
}
if (bufw!=null)
try
{
bufw.close();//关闭资源
}
catch (IOException e)
{
System.out.println("写入异常");
}
}
}
}
九、自定义一个缓冲区BufferedReader
明白了BufferedReader类中特有的方法readLine的原理后,可以自定义一个类中包含一个功能和readLine一致的方法,来模拟一下BufferedReader。
import java.io.*;
class MyBufferedReader
{
private Reader r;//定义一个接收流对象
MyBufferedReader(Reader r)
{
this.r = r;
}
//可以一次读一行数据的方法
public String myReadLine() throws IOException
{
//首先定义一个临时容器,原BufferedReader封装的是字符数组。
//为了演示方便,这里定义一个StringBuiler容器,因为最终还是
//要将数据返回成字符串。
StringBuilder sb = new StringBuilder();
int ch = 0;
while ((ch=r.read())!=-1)
{
if(ch=='\r')//如果遇到\r,则继续
continue;
if(ch=='\n')//如果遇到\n,则把改行转成字符串
return sb.toString();
else
sb.append((char)ch);//否则一直将读取到的字符添加到容器
}
if(sb.length()!=0)//如果读取结束,容器中还有字符,就返回字符串
return sb.toString();
return null;
}
//复写父类close方法
public void myClose() throws IOException
{
r.close();
}
}
//测试一下MyBufferedReader可行性
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();
}
}
运行结果:
十、装饰设计模式
当想要对已有的对象进行功能增强时,可以定义类,将已有对象传入,基于已有功能,并提供加强功能。那么自定义的该类称为 装饰类。
装饰类通常会通过构造方法接收被装饰的对象。并基于被装饰的对象的功能,提供更强的功能。
MyReader//专门用于读取数据的类。
|--MyTextReader
|--MyBufferTextReader
|--MyMediaReader
|--MyBufferMediaReader
|--MyDataReader
|--MyBufferDataReader
class MyBufferReader
{
MyBufferReader(MyTextReader)
{
}
MyBufferReader(MyMediaReader)
{
}
}
上面这个类扩展性很差,
找到其参数的共同类型,通过多态的形式,可以提高扩展性。
class MyBufferReader extends MyReader
{
MyBufferReader(MyReader r)
{
}
}
优化后的体系为:
MyReader//专门用于读取数据的类。
|--MyTextReader
|--MyMediaReader
|--MyDataReader
|--MyBufferReader
总结:
装饰模式比继承要灵活,避免了继承体系的臃肿,而且降低了类与类之间的关系。装饰类因为增强已有对象,具备功能和已有的是相同的,只不过提供了更强功能,所以装饰类和被装饰类通常都属于一个体系中。
代码示例:
class Person
{
public void chifan()
{
System.out.println("吃饭");
}
}
class SuperPerson
{
private Person p;
SuperPerson(Person p)
{
this.p = p;
}
public void superChifan()
{
System.out.println("开胃酒");
p.chifan();
System.out.println("甜点");
System.out.println("来一根");
}
}
class PersonDemo
{
public static void main(String[] args)
{
Person p = new Person();
//p.chifan();
SuperPerson sp = new SuperPerson(p);
sp.superChifan();
}
}
运行结果:
十一、LineNumberReader
LineNumberReader是 BufferedReader 的子类,它有获取和设置行号的方法: getLineNumber()和setLineNumber();
代码示例:
import java.io.*;
class LineNumberReaderDemo
{
public static void main(String[] args) throws IOException
{
FileReader fr = new FileReader("LineNumberReaderDemo.java");
LineNumberReader lnr = new LineNumberReader(fr);
String line= null;
lnr.setLineNumber(100);
while ((line=lnr.readLine())!=null)
{
System.out.println(lnr.getLineNumber()+":"+line);
}
lnr.close();
}
}
运行结果: