---------------------- ASP.Net+Unity开发、.Net培训、期待与您交流! ----------------------
本文是在整理、综合以下几篇文章后得到的,
1、JAVA IO详解,包括讲解IO中各种流及其用法;
2、java中的io系统详解;
一、IO流概述:
javaIO流用于处理设备之间的数据传输;
java对数据的操作是通过流的方式;
java用于操作流的对象都在IO包中;
二、IO流分类:
1、根据处理的数据类型不同:字节流和字符流。
2、根据流向不同:输入流和输出流。
字符流的由来:
因为文件编码的不同,而有了对字符进行高效操作的字符流对象。
原理:其实就是基于字节流读取字节时,去查了指定的码表。
字节流和字符流的区别:
1)字节流读取的时候,读到一个字节就返回一个字节;字符流使用了字节流读到一个或多个字节(中文对应的字节数是两个,在UTF-8码表中是3个字节)时,先去查指定的编码表,将查到的字符返回。
2)字节流可以处理所有类型数据,如图片、MP3、avi;而字符流只能处理字符数据。
结论:只要是处理纯文本数据,就要优先考虑使用字符流,除此之外都用字节流。
IO流对象继承关系:
其他常用的与流有关的对象:
名称 | 对应的对象 |
文件类 | File |
属性类 | Properties |
打印流 | PrintStream、PrintWriter |
管道流 | PipedInputStream、PipedOutputStream |
序列流 | SequenceInputStream |
对象序列化流 | ObjectInputStream、ObjectOutputStream |
随机存取文件类 | RandomAccessFile |
基本数据流 | DataInputStream、DataOutputStream |
字节数组流 | ByteArrayInputStream、ByteArrayOutputStream |
字符数组流 | CharArrayReader、CharArrayWriter |
字符串流 | StringReader、StringWriter |
三、字符流
字符流继承体系:
|--Reader
|--BufferedReader
|--InputStreamReader
|--FileReader
|--Writer
|--BufferedWriter
|--OutputStreamWriter
|--FileWriter
字符流常见方法:
Reader中常见的方法:
1) int read():读取一个字符。返回的是读到的那个字符。如果读到流的末尾,返回-1。
2))int read(char[] cbuf):将读到的字符存入指定的数组中,返回的是读到的字符个数,也就是往数组里装的元素的个数。如果读到流的末尾,返回-1。
3) void close():读取字符其实用的是window系统的功能,就希望使用完毕后,进行资源的释放。
Writer中常见的方法:
1) void write(int ch):将一个字符写入到流中。
2) void write(char[] cbuf):将一个字符数组写入到流中。
3) void write(String str):将一个字符串写入到流中。
4) void flush():刷新流,将流中的数据刷新到目的地中,流还存在。
5) void close():关闭资源,在关闭前会先调用flush(),将流中的数据刷新到目的地,然后关闭流。
FileWriter/FileReader:
Reader与Writer都是抽象类,不能建立对象。接下来,我们将介绍它们的子类。既然IO流是用于操作数据的,而数据最常见的体现形式是:文件。因此,我们先以操作文件的流来演示。
FileWriter:
该类没有特别的方法,只有自己的构造函数。
特点:
1)用于处理文本文件。2)该类中有默认的编码表。3)该类中有临时缓冲。
构造函数:在写入流对象初始化时,必须要有一个存储数据的目的地。
FileWriter(String fileName):
该构造函数做了什么事情:
1)调用系统资源;2)在指定位置创建了一个文件(若文件已经存在,将会被覆盖)。
FileWriter(String fileName,boolean append):
当传入的boolean类型值为true时,会在指定文件末尾处进行数据的续写。
FileReader:
特点:
1)用于读取文本文件的流对象;2)用于关联文本文件。
构造函数:在读取流对象初始化的时候,必须要指定一个被读取的文件。如果该文件不存在,会发生FileNotFoundException。
FileReader(String fileName);
例1:在硬盘上,创建一个文件并写入一些文字数据
/*
*例1:在硬盘上,创建一个文件并写入一些文字数据
*/
import java.io.*;
class Test
{
public static void main(String[] args)
{
//创建一个FileWriter对象。该对象初始化时要求必须明确被操作的文件。
//该文件会被创建在指定目录下。如果该目录下已有同名文件,将被覆盖。
FileWriter fw=new FileWriter("demo.txt");
//调用wirte方法,将字符串写入流中。
fw.write("abcd");
//刷新流对象的缓冲中的数据到目的地中。
fw.flush();
//关闭流资源。但在关闭前,会刷新一次内部的缓冲中的数据到目的地中。
//与flush()的区别:flush()刷新后,流可以继续使用;close()刷新后,会将流关闭。
fw.close();
}
}
对于读取或者写入流对象的构造函数,以及读写方法,还有刷新关闭功能都会抛出IOException或其子类,所以都要进行处理,或者throws抛出,或者try...catch处理。
例2:完整的IO异常处理方式。
/*
*例2:完整的异常处理方式。
*/
import java.io.*;
class Test
{
public static void main(String[] args)
{
FileWriter fw=null;
try
{
fw=new FileWriter("demo.txt");
fw.write("abcd");
fw.flush();
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally
{
if(fw!=null)
try
{
fw.close();
}
catch (IOException e)
{
System.out.println("close:"+e.toString());
}
}
}
}
另一个小细节:
当指定绝对路径时,定义目录分隔符有三种方式:
1)两个斜杠\\。new FileWriter("c:\\demo.txt"); 2)一个反斜杠/。new FileWriter("c:/demo.txt");3)File.separator。new FileWriter("c:"+File.separator+"demo.txt");
例3:读取一个已有的文本文件,并将其打印至控制台
方法一:一次读取一个字符就打印出来
/*
*例3:读取一个已有的文本文件,并将其打印至控制台
*方法一:一次读一个字符就打印出来,效率低。
*/
import java.io.*;
class Test
{
public static void main(String[] args)
{
FileReader fr=null;
try
{
//创建一个文件读取流对象,和指定名称的文件相关联。要保证该文件是
//已经存在的,否则会发生FileNotFoundException。
fr=new FileReader("demo.txt");
int ch=0;
while ((ch=fr.read())!=-1) //read()方法一次读一个字符,而且会自动往下读。
{
System.out.println((char)ch);
}
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally
{
if(fr!=null)
try
{
fr.close();
}
catch (IOException e)
{
System.out.println("close:"+e.toString());
}
}
}
}
方法二:读一个字符就存入字符数组里,待读完1Kb后再打印。
/*
*例3:读取一个已有的文本文件,并将其打印至控制台
*方法二:读一个字符就存入字符数组里,待读完1Kb后再打印。。
*/
import java.io.*;
class Test
{
public static void main(String[] args)
{
FileReader fr=null;
try
{
fr=new FileReader("demo.txt");
char[] cbuf=new char[1024];
int num=0;
while ((num=fr.read(cbuf))!=-1) //read(cbuf)方法读一个字符就存入字符数组里,待读完1Kb后再打印。
{
System.out.println(new String(cbuf,0,num));
}
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally
{
if(fr!=null)
try
{
fr.close();
}
catch (IOException e)
{
System.out.println("close:"+e.toString());
}
}
}
}
/*
*例4:将C盘的一个文本文件复制到D盘
*/
import java.io.*;
class Test
{
public static void main(String[] args)
{
FileReader fr=null;
FileWriter fw=null;
try
{
fr=new FileReader("c:\\demo.txt");
fw=new FileWriter("d:\\demo_copy.txt");
char[] cbuf=new char[1024];
int num=0;
while ((num=fr.read(cbuf))!=-1)
{
fw.write(cbuf,0,num);
}
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally
{
if(fr!=null)
try
{
fr.close();
}
catch (IOException e)
{
System.out.println("close:"+e.toString());
}
if(fw!=null)
try
{
fw.close();
}
catch (IOException e)
{
System.out.println("close:"+e.toString());
}
}
}
}
字符流的缓冲区:
缓冲区的出现提高了对流的操作效率。
原理:将数组进行了封装。
对应的对象:
BufferedWriter:
特有方法:newLine():跨平台的换行符。
BufferedReader:
特有方法:readLine():一次读一行,到行标记时,将行标记之前的字符数据作为字符串返回。当读到末尾时,返回null。
在使用缓冲区对象时,要明确缓冲的存在是为了增强流的功能而存在,所以在建立缓冲区对象时,要先有流对象存在。其实缓冲区内部就是在使用流对象的方法,只不过加入了数组对数据进行了临时存储,来提高操作数据的效率。
例5:通过缓冲区将C盘的一个文本文件复制到D盘
/*
*例5:通过缓冲区将C盘的一个文本文件复制到D盘
*/
import java.io.*;
class Test
{
public static void main(String[] args)
{
BufferedReader bufr=null;
BufferedWriter bufw=null;
try
{
//建立缓冲区对象必须把流对象作为参数传给缓冲区对象的构造函数
bufr=new BufferedReader(new FileReader("c:\\demo.txt"));
bufw=new BufferedWriter(new FileWriter("d:\\demo_copy.txt"));
String line=null;
while ((line=bufr.readLine())!=null) //readLine()方法按照行的形式取出数据(不包括回车符)
{
bufw.write(line);
bufw.newLine(); //newLine()方法,换行
bufw.flush();
}
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally
{
if(bufr!=null)
try
{
bufr.close(); //关闭缓冲区,其实关闭的是被包装在内部的流对象。
}
catch (IOException e)
{
System.out.println("close:"+e.toString());
}
if(bufw!=null)
try
{
bufw.close();
}
catch (IOException e)
{
System.out.println("close:"+e.toString());
}
}
}
}
readLine()方法原理:
其实该方法,用的还是与缓冲区相关联的流对象的read()方法。只不过,每一次读到一个字符,先不进行具体操作,而是进行临时存储。当读取到回车标记时,才将临时容器中存储的数据一次性返回。
既然明白了原理,我们也可以实现一个类似功能的方法。
例6:模拟readLine()方法
/*
*例6:模拟readLine()方法
*/
import java.io.*;
class BufferedDemo
{
public static void main(String[] args)
{
MyBufferedReader mbufr=null;
try
{
mbufr=new MyBufferedReader(new FileReader("c:\\demo.txt"));
String line=null;
while ((line=mbufr.myReadLine())!=null)
{
System.out.println(line);
}
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally
{
if(mbufr!=null)
try
{
mbufr.myClose();
}
catch (IOException e)
{
System.out.println("close:"+e.toString());
}
}
}
}
class MyBufferedReader
{
private Reader r;
MyBufferedReader(Reader r)
{
this.r=r;
}
public String myReadLine()throws IOException
{
//定义一个临时容器,原BufferedReader封装的是字符数组,
//为了演示方便,在此定义一个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;
}
public void myClose()throws IOException
{
r.close();
}
}
装饰设计模式:
缓冲区基于流并增强了流的功能,这体现了一种设计模式:装饰设计模式。
当想要对已有的对象进行功能增强时,可以定义类,将已有的对象传入,基于已有的功能,并提供加强功能。那么,自定义的该类称为装饰类。
装饰类通常会通过构造方法接收被装饰的对象,并基于被装饰的对象的功能,提供更强的功能。
通常装饰类和被装饰类都属于同一个父类或者接口。
//装饰设计模式的演示
class Person
{
public void eat()
{
System.out.println("吃饭");
}
}
class SuperPerson
{
private Person p;
SuperPerson(Person p) //通过构造函数接收被装饰的对象
{
this.p=p;
}
public void superEat()
{
System.out.println("开胃酒"); //提供更强的功能
p.eat();
System.out.println("甜点");
}
}
装饰与继承的区别:
装饰比继承有更好的灵活性。举例来说,
Writer
|--MediaWriter
|--TextWriter
(注:实际JDK中无这两个类,只为了更形象的举例说明而“创建”的)
需求:想要提高对数据的操作效率,用到了缓冲技术。
方法一:通过继承
继承体系变为,
Writer
|--MediaWriter
|--BufferedMediaWriter
|--TextWriter
|--BufferedTextWriter
建立子类复写父类中的方法即可满足需求。可是,当Writer中子类对象过多,那么为了提高每一个对象的操作效率,每一个对象都有一个自己的子类Buffered。这样,虽然可以实现功能,但是继承体系变得十分臃肿。
方法二:通过装饰
每个子类都是在使用缓冲技术,则可以对缓冲技术进行描述,将需要增强的对象传给缓冲区即可。
class BufferedWriter extends Writer
{
private Writer w;
BufferedWriter(Writer w) //通过多态,Writer w可以引用它的子类对象
{
this.w=w;
}
...
}
继承体系变为,
Writer|--MediaWriter
|--TextWriter
|--BufferedWriter
由此看见,装饰设计模式,优化增强功能的部分,比继承要灵活许多。
LineNumberReader:
BufferedReader的子类,可以在读一行的基础上添加一个行号。
例7:LineNumberReader
/*
*例7:LineNumberReader
*/
import java.io.*;
class Test
{
public static void main(String[] args)
{
LineNumberReader lnr=null;
try
{
lnr=new LineNumberReader(new FileReader("c:\\demo.txt"));
String line=null;
lnr.setLineNumber(100); //设定初始行号
while ((line=lnr.readLine())!=null)
{
System.out.println(lnr.getLineNumber()+":"+line);//返回行号
}
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally
{
if(lnr!=null)
try
{
lnr.close();
}
catch (IOException e)
{
System.out.println("close:"+e.toString());
}
}
}
}
例8:模拟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;
}
}
四、字节流
字节流的继承体系:
|--InputStream
|--FileInputStream
|--FilterInputStream
|--BufferedInputStream
|--OutputStream
|--FileOutputStream
|--FilterOutputStream
|--BufferedOutputStream
InputStream/OutputStream常见方法:
InputStream中常见的方法:
1) int read():读取一个字节。返回的是读到的那个字节(被提升为int型)。如果读到流的末尾,返回-1。
2))int read(byte[] buf):将读到的字节存入指定的数组中,返回的是读到的字节个数,也就是往数组里装的元素的个数。如果读到流的末尾,返回-1。
3) void close():读取字符其实用的是window系统的功能,就希望使用完毕后,进行资源的释放。
OutputStream中常见的方法:
1) void write(int b):将一个字节(被提升为int型)写入到流中。
2) void write(byte[] buf):将一个字节数组写入到流中。
3) void flush():刷新流,将流中的数据刷新到目的地中,流还存在。
4) void close():关闭资源,在关闭前会先调用flush(),将流中的数据刷新到目的地,然后关闭流。
InputStream与OutputStream都是抽象类,不能建立对象。接下来,我们将介绍它们的子类。既然IO流是用于操作数据的,而数据最常见的体现形式是:文件。因此,我们先以操作文件的流来演示。
FileOutputStream\FileInputStream:
FileOutputStream\FileInputStream与FileWriter\FileReader类似,只是后者只能操作字符数据,而前者则可以操作任何数据。字节流使用的数组是字节数组,byte[] b;字符流使用的数组是字符数组,char[] c。
下面展示FileOutputStream\FileInputStream的功能,
write:
FileOutputStream fos=new FileOutputStream("fos.txt");
fos.write("abcd".getBytes());
fos.close();
Read:
①
FileInputStream fis=new FileInputStream("fos.txt");
int ch=0;
while ((ch=fis.read())!=-1)
{
System.out.println((char)ch);
}
fis.close();
②
FileInputStream fis=new FileInputStream("fos.txt");
//int num=fis.available(); //返回读取流从文件中可读取的字节数,即文件大小
//byte[] buf=new byte[num]; //定义一个和文件一样大的缓冲区,这样不用循环。文件过大
//文件过大时,这种方法不好,会造成内容溢出
int num=0;
byte[] buf=new byte[1024];
while ((num=fis.read(buf))!=-1)
{
System.out.println(new String(buf));
}
fis.close();
下面演示字节流的缓冲区
例9:通过缓冲区复制mp3文件
/*
*例9:通过缓冲区复制mp3文件
*/
import java.io.*;
class BufferedTest
{
public static void main(String[] args)
{
BufferedInputStream bufis=null;
BufferedOutputStream bufos=null;
try
{
bufis=new BufferedInputStream(new FileInputStream("c:\\123.mp3"));
bufos=new BufferedOutputStream(new FileOutputStream("d:\\321.mp3"));
int ch=0;
while ((ch=bufis.read())!=-1) //通过字节流的缓冲区完成复制
{
bufos.write(ch);
}
}
catch (IOException e)
{
throw new RuntimeException();
}
finally
{
if(bufis!=null)
try
{
bufis.close();
}
catch (IOException e)
{
}
if(bufos!=null)
try
{
bufos.close();
}
catch (IOException e)
{
}
}
}
}
自定义字节流缓冲区:
class MyBufferedInputStream
{
private InputStream in;
private byte[] buf=new byte[1024];
private int pos=0,count=0;
MyBufferedInputSteam(InputStream in)
{
this.in=in;
}
public int myRead()
{
if(count==0)
{
count=in.read(buf);
if(count<0)
return -1;
pos=0;
byte b=buf[pos];
count--;
pos++;
return b&255;
}
if(count>0)
{
byte b=buf[pos];
count--;
pos++;
return b&255;
}
return -1;
}
}
为什么字节流的read()方法及自定义字节流缓冲区的myRead()的返回值类型是Int,而不是byte:
因为read()方法读到末尾时返回的是-1。而在所操作的数据中,很容易出现连续多个的1的情况,而连续读到8个1就代表-1,会导致读取提前停止。所以将读到的一个字节给提升为一个int类型的数值,但是保留原字节并在剩余二进制位补0。具体操作:byte&255 or byte&0xff。
对于write方法,可以一次写入一个字节,但接收的是一个int类型数值。不过只写入该int类型数值的最低一个字节(8位)。
总结:read方法对读到数据进行提升,write方法对操作的数据进行转换。
五、转换流与字符编码
1、转化流:
特点:
1)字节流和字符流之间的桥梁;2)该流对象中可以对读取到字节数据进行指定编码表的编码转换。
什么时候使用转换流:
1)当字节和字符之间有转换动作时;2)当流操作的数据需要进行编码表的指定时。
具体的对象体现:
1)InputStreamReader:字节到字符的桥梁;2)OutputStreamWriter:字符到字节的桥梁。
Reader
|--InputStreamReader
|--FileReader
Writer
|--OutputStreamWriter
|--FileWriter
这两个流对象是字符流体系中的成员。它们有转换作用,本身又是字符流,所以在构造的时候,需要传入字节流对象进来。
构造函数:
InputStreamReader(InputStream is):通过该构造函数初始化,使用的是本系统默认的编码表GBK。
InputStreamReader(InputStream is,String charSet):通过该构造函数初始化,可以指定编码表。
OutputStreamReader(OutputStream os):通过该构造函数初始化,使用的是本系统默认的编码表GBK。
OutputStreamReader(OutputStream os,String charSet):通过该构造函数初始化,可以指定编码表。
转换流中的read方法,已经融入了编码表。在底层调用字节流的read方法时,将获取的一个或多个字节数据进行临时存储,并去查指定的编码表。如果编码表没有指定,则查的是默认码表。这样转换流的read方法就可以返回一个字符,比如中文。
转换流已经完成了编码转换的动作,对于直接操作文本文件的FileReader,就不用再重新定义,只要继承该转换流,获取其方法,就可以直接操作文本文件中的字符数据了。(注意,FileReader操作文本数据时,该对象使用的是默认编码表,若要使用指定编码表,必须使用转换流)
例10:读取键盘录入
/*
*例9:通过缓冲区复制mp3文件
*当录入一行数据后,就将改行数据打印。
*如果录入的数据是“over”,就停止录入。
*/
import java.io.*;
class BufferedTest
{
public static void main(String[] args)
{
//System.out、System.in都是字节流。但键盘录入的都是字符数据,
//且录入一行就打印的行为与readLine()功能相似。然而BufferedReader是字符流
//中的方法。为此,需要用转换流。
BufferedReader bufr=
new BufferedReader(new InputStreamReader(System.in));//System.in:标准输入设备,键盘
BufferedWriter bufw=
new BufferedWriter(new OutputStreamWriter(System.out));//System.out:标准输出设备,控制台
String line=null;
while ((line=bufr.readLine())!=null)
{
if("over".equals(line))
break;
bufw.write(line);
bufw.newLine();
bufw.flush();
}
bufr.close();
bufw.close();
}
}
2、字符编码:
编码表的由来:
计算机只能识别二进制数据,早期由来是电信号。为了方便应用计算机,让它可以识别各个国家的文字,就将各个国家的文字用数字来表示,并一一对应,形成一张表,这就是编码表。
常见的编码表:
1)ASCII:美国标准信息交换码。用一个字节的7位表示。
2)ISO8859-1:拉丁码表、欧洲码表。用一个字节的8位表示。
3)GB2312:中国的中文编码表。
4)GBK:中国的中文编码表升级版,融合了更多的中文文字符号。
5)Unicode:国际标准码,融合了多种文字。所有文字都用两个字节来表示,java语言使用的就是Unicode。
6)UTF-8:一字字节表示一个英文,三个字节表示一个中文。
……
/*
*例9:编码与解码
*/
import java.io.*;
class Test
{
public static void main(String[] args)
{
try
{
writeText();
readText();
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
public static void writeText()throws IOException
{
//编码时,指定编码表为GBK
OutputStreamWriter osw=new OutputStreamWriter(new FileOutputStream("gbk.txt"),"GBK");
osw.write("你好");
osw.close();
}
public static void readText()throws IOException
{
//解码时,编码表也必须相同,否则会解码错误
InputStreamReader isr=new InputStreamReader(new FileInputStream("gbk.txt"),"GBK");
char[] buf=new char[1024];
int num=isr.read(buf);
System.out.println(new String(buf,0,num));
isr.close();
}
}
编码原理及解码错误时的补救措施:
注意绿色箭头的走向,它体现了解码错误的结果以及相应的补救措施。
六、IO流的一般使用原则
1、按输入输出分类:
1)输入:Reader、InputStream类型的子类。
2)输出:Writer、OutputStream类型的子类。
2、按数据格式分类:
1)二进制格式(只要不能确定是纯文本的):InputStream、OutputStream类型的子类。
2)纯文本格式(含纯英文与汉字或其他编码方式):Reader、Writer类型的子类。
3、按数据来源(去向)分类
1)是文件:FileInputStream、FileOutputStream(字节流);FileReader、FileWriter(字符流)。
2)是byte[]:ByteArrayInputStream、ByteArrayOutputStream(字节流)。
3)是char[]:CharArrayReader、CharArrayWriter(字符流)。
4)是String:StringBufferInputStream、StringBufferOuputStream (字节流);StringReader、StringWriter(字符流)。
5)网络数据流:InputStream、OutputStream(字节流);Reader、Writer(字符流)。
4、按是否要缓冲分类:
要缓冲:BufferedInputStream、BufferedOutputStream(字节流);BufferedReader、BufferedWriter(字符流)。
5、按是否格式化输出分:
要格式化输出:PrintStream、PrintWriter。
6、特殊需求:
1)从字节流到字符流的转换流:InputStreamReader、OutputStreamWriter。
2)输入、输出对象:ObjectInputStream、ObjectOutputStream。
3)线程间通信:PipeInputStream、PipeOutputStream(字节流);PipeReader、PipeWriter(字符流)。
4)合并输入:SequenceInputStream。
5)更特殊的需要:PushbackInputStream、PushbackReader、LineNumberInputStream、LineNumberReader。
原则的应用:
需求:将键盘录入的数据保存到一个文件中。
那么按照原则分析:
对于输入向,
1、按照数据格式判断?
是纯文本格式,键盘录入的都是字符。选择Reader。
2、按照数据来源判断?
键盘对应的是System.in。这是字节流,与1选择的Reader冲突,因此需要转换流InputStreamReader。
3、是否要缓冲判断?
要缓冲,选择BufferedReader。
对于输出向,
1、按照数据格式判断?
是纯文本,键盘录入的都是字符。选择Writer。
2、按照数据去向判断?
硬盘里的一个文件。选择FileWriter。
3、是否要缓冲判断?
要缓冲,选择BufferedWriter。
至此,完成IO流的选择,如下,
BufferedReader bufr=
new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw=
new BufferedWriter(new FileWriter("demo.txt"));