Java IO
IO(Input Output)流
IO流用来处理该设备之间的数据传输
Java对数据的操作是通过流的方式
Java用于操作流的对象都在IO包中
流按操作数据分为两种:字节流与字符流(字符流有编码表)
流按流向分为:输入流、输出流
IO流常用基类
字节流的抽象基类:
InputStream,OutputStream
字符流的抽象基类:
Reader,Writer
注:由这四个类派生出来的子类名称都是以其父类名作为子类名的后缀.
如:InputStream的子类FileInputStream;Reader的子类FileReaders
eg:需求:在硬盘上创建一个文件并写入一些文字数据。
找到一个专门用于操作文件的writer子类对象:FileWriter,后缀是父类名,前缀是该留对象的功能。
<span style="font-size:14px;">//创建一个FileWriter对象,该对象一初始化就必须要明确被操作的文件。
//而且该对象会被创建到指定目录下,如果该目录下已有同名文件,将被覆盖。
//其实该步就是在明确数据要存放的目的地。
FileWriter fw=new FileWriter(“demo.txt”); //得抛异常 throws IOException
//调用writer方法,将字符串写入到流中(此时并没有写进文件)
fw.writer(“abcde”);
//刷新流对象中的缓冲中的数据。将数据刷到目的地中(到文件中了)
fw.flush();
//关闭流资源,但是关闭之前会刷新一次内部的缓冲中的数据
//将数据刷到目的地中
//和flush区别:flush刷新后,流可以继续使用,close刷新后,会将流关闭
fw.close();
</span>
IO异常处理方式
eg:
<span style="font-size:14px;">import java.io.*;
class FileWriterDemo
{
public static void main(String[] args)
FileWriter fw=null; //因为后面finally用到,要放到外面
try
{
fw=new FileWriter(“demo.txt”);
fw.writer(“abcdefg”);
}
catch(IOException e)
{
System.out.println(“catch:”+e.toString());
}
finally
{
try //再try,因为这里可以有异常。注:有几个IO流,这里try几次(分别关)
{
if(fw!=null)
fw.close();
}
catch(IOException e)
{
System.out.println(e.toString());
}
}
}
</span>
对已有文件的续写
<span style="font-size:14px;">public static void main(String[] args)throws IOException
{
//传递一个true参数,代表不覆盖已有的文件,并在已有文件的末尾处进行数据续写
FileWriter fw=new FileWriter(“demo.txt”,true); //编程时这里要try
fw.writer(“mhao\r\nxiexie”); //windows换行
fw.close();
}
</span>
文本读取方式
1> 创建一个文件读取流对象,和指定名称的文件相关联
要保证该文件是已经存在的,如果不存在,会发生异常FileNotFoundException
FileReader fr=new FileReader(“demo.txt”);
调用读取流对象的read方法。read()方法:一次读一个字符,而且会自动往下读。
int ch=fr.read(); //注:返回int
//读到末尾返回“-1”
int ch=0;
while((ch=fr.read())!=-1)
{
System.out.println(“ch=”(char)chs);
}
2> 第二种方式:通过字符数组读取
<span style="font-size:14px;">FileReader fr=new FileReader(“demo.txt”);
//定义一个字符数组,用于存储读到字符
//该read(char[])返回得失读到字符个数。
char[] buf=new char[1024];//一般定义1024,如果小于会循环用。1个字符2字节,即2KB
int num=0;
while((num=fr.read(buf))!=-1)
{
System.out.println(new String(buf,0,num)); //只打印取到的字符
}
fr.close();
</span>
拷贝文本文件
复制的原理:
其实就是将C盘下的文件数据存储到D盘的一个文件中。
步骤:
1. 在D盘创建一个文件,用于存储C盘文件中的数据
2. 定义读取流和C盘文件关联
3. 通过不断地读写完成数据存储
4. 关闭资源
eg:
<span style="font-size:14px;">import java.io.*;
class CopyText
{
public static void main(String[] args) throws IOException
{
copy();
}
public static void copy()
{
FileWriter fw=null;
FileReader fr=null;
try
{
fw=new FileWriter(“SystemDemo_Copy.txt”);
fr=new FileReader(“SystemDemo.java”);
char[] buf=new char[1024];
int len=0;
while((len=fr.read(buf))!=-1)
{
fw.write(buf,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)
{
}
}
}
}
</span>
字符流的缓冲区
缓冲区的出现提高了对数据的读写效率
对应类
BufferedWriter
BufferedReader
缓冲区要结合流才可以使用
在流的基础对流的功能进行了增强
BufferedWriter (字符写入流缓冲区)
建立缓冲区之前,必须要先有流对象;该缓冲区提供了一个跨平台的换行符newLine().
eg:
<span style="font-size:14px;">//创建一个字符写入流对象
FileWriter fw=new FileWriter(“buf.txt”);
//为了提高字符写入流效率,加入了缓冲技术
//只要将需要被提高效率的流对象作为参数传递给缓冲区的构造函数
BufferedWriter bufw=new BufferedWriter(fw);
for(int x=1;x<5;x++)
{
bufw.write(“abcd”+x);
bufw.newLine();
bufw.flush(); //刷新,防止突然停电等
}
//记住,只要用到缓冲区,就要记得刷新。
//其实关闭缓冲区,就是在关闭缓冲区中的流对象。
bufw.close(); //不用再fw.close();
</span>
BufferedReader (字符读取流缓冲区)
该缓冲区直接提供了一个歌词读一行的方法readLine(),方便于对文本数据的获取。当返回null时,表示读到文件末尾。
readLine方法返回的时候只返回回车符之前的数据内容,并不返回回车符。
eg:
<span style="font-size:14px;">//创建一个读取流对象和文件关联
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;
</span>
eg:通过缓冲区复制一个.java文件
<span style="font-size:14px;">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(“BufferedWriter_Copy.java”));
String line=null;
while((line=bufr.readLine())!=null)
{
bufw.writer(line);
bufw.newLine();
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(“写入关闭失败”);
}
}
}
}
</span>
readLine()原理: 【用到了装饰】
代码:
<span style="font-size:14px;">import java.io.*;
class MyBufferedReader extends Reader
{
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’) //\r\n代表换行(Windows中)
continue;
if(ch==’/n’)
return sb.toString();
else
sb.append((char)ch);
}
if(sb.length()!=0)
{
return sb.toString(); //如果有数据却没有换行
}
return null;
}
@Override
public int read(char[] cbuf,int off,int len)throws IOException
{
return r.read(cbuf,off,len);
}
@Override
public void close()throws IOException
{
r.close();
}
public void myClose() throws IOException
{
r.close();
}
}
</span>
装饰设计模式
当想要对已有的对象进行功能增强时
可以定义类,将已有对象传入,基于已有功能,并提供加强功能。
那么自定义的该类称为装饰类
BufferedReader即是
装饰类通常会通过构造方法接收被装饰的对象
并给予被装饰的对象的功能,提供更强的功能
eg:
<span style="font-size:14px;">class Person
{
public void chiFan()
{
System.out.println(“吃饭”);
}
}
class SuperPerson
{
private Person p;
Super Person(Person p)
{
this.p=p;
}
public void superChiFan()
{
System.out.println(“开胃酒”);
p.chiFan();
System.out.println(“甜点”);
}
}
</span>
装饰和继承的区别
装饰模式比继承要灵活。避免了继承体系臃肿。
而且降低了类与类之间的关系。
装饰类因为增强了已有对象,具备的功能和已有的是相同的,只不过提供了更强功能。所以装饰类和被装饰类通常都是属于一个体系的。
eg:
继承:
MyReader
|---MyTextReader
|---MyBufferTextReader
|---MyMediaReader
|---MyBufferMediaReader
|---MyDateReader
|---MyBufferDataReader
找到共同点,用多态形式
<span style="font-size:14px;">class MyBufferReader extends MyReader
{
private MyReader r;
MyBufferReader(MyReader r)
{
}
}
</span>
即:MyReader
|---MyTextReader
|---MyMediaReader
|---MyDataReader
|---MyBufferReader
LineNumberReader
LineNumberReader是BufferedReader的子类(和其用处基本一样)
有两个特殊方法:setLineNumber(int LineNumber)[设置当前行号]
getLineNumber()[获取当前行号]【默认从0开始,一行结束+1,没有没有一行则是1】
注:setLineNumber(int )不会实际更改流中的当前位置,它是更改将由getLineNumber()返回的值。
字节流File读写操作
InputStream 【读】 OutPutStream【写】
字符流底层也是用字节流,一个字符占两字节,不能单个操作,所以先存起来后刷新
eg:
需求:想要操作图片数据,这时就要用到字节流。
复制一个图片
<span style="font-size:14px;">import java.io.*;
class FileStream
{
public static void main(String[] args)throws IOException
{
writeFile();
readFile_3();
}
//方法三,如果文件太大,内存会溢出
public static void readFile_3()throws IOException
{
FileInputStream fis=new FileInputStream(“fos.txt”);
//int num=fis.available(); //fis文件的大小
byte[] buf=new byte[fis.available()]; //定义了一个大小刚好的缓冲区,不
//用再循环了
fis.read(buf);
System.out.println(new String(buf));
fis.close();
}
//方法二,定义了一个1024大小的字节数组
public static void readfile_2() throws IOException
{
FileInputStream fis=new FileInputStream(“fos.txt”);
byte[] buf=new byte[1024];
int len=0;
while((len=fis.read(buf))!=-1)
{
System.out.println(new String(buf,0,len));
}
fis.close();
}
//一个一个读
public static void readFile_1() throws IOException
{
FileInputStream fis=new FileInputStream(“fos.txt”);
int ch=0;
while((ch=fis.read())!=-1)
{
System.out.println((char)ch);
}
fis.close();
}
public static void writeFile() throws IOException
{
FileOutputStream fos=new FileOutputStream(“fos.txt”);
fos.write(“abcde”.getBytes()); //将此String转为byte数组
fos.close();
}
}
</span>
<span style="font-size:14px;">/*复制一个图片
思路:
1. 用字节读取流对象和图片关联
2. 用字节写入流对象创建一个图文件。用于存储获取到的图片数据
3. 通过循环读写,完成数据的存储
4. 关闭资源
*/
import java.io.*;
class CopyPic
{
public static void main(String[] args)
{
FileOutputStream fos=null;
FileInputStream fis=null;
try
{
fos=new FileOutputSream(“c:\\2.bmp”);
fis=new FileInputStream(“c:\\1.bmp”);
byte[] buf=new byte[1024];
int len=0;
while((len=fis.read(buf))!=-1)
{
fos.write(buf.0.len);
}
}
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 Runtime(“写入关闭失败”);
}
}
}
}
</span>
字节流的缓冲区:
BufferedOutputStream
BufferedInputStream
<span style="font-size:14px;">/* BufferedInputStream 的原理+复制一首mp3 */
import java.io.*;
class MyBufferedInputStream
{
private InputStream in;
private byte[] buf=new byte[1024*4];
private int pos=0,count=0; //pos取数据的指针;count计数器,记录byte[]中还
//有多少数据
MyBufferedInputStream(InputStream in)
{
this.in=in;
}
//一次读一个字节,从缓冲区(字节数组)获取。
public int myRead() throws IOException
{
//通过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; //因为byte转int类型,为了保持数据不变,
}
else if(count>0)
{
byte b=buf[pos];
count--;
pos++;
return b&0xff; //255
}
return -1;
}
public void myClose() throws IOException
{
in.close();
}
}
</span>
<span style="font-size:14px;">//下面是通过缓冲区,复制mp3的代码(如果用BufferedInputStream,只需替换
//MybufferedInputStream即可
class CopyMp3
{
public static void main(String[] args) throws IOException
{
long start=System.currentTimeMillis();
copy_2(); //方法2
long end=System.currentTimeMillis();
System.out.println((end-start)+”毫秒”); //获取程序运行时间
}
//调用字节流的缓冲区原理那个方法
public static void copy_2() throws IOException
{
MyBufferedInputStream bufis=
new MyBuffertedInputStream(new FileInputStream(“c:\\3.mp3”));
BufferedOutputStream bufos=
new BufferedOutputStream(new FileOutputStream(“c://2.mp3”));
int by=0;
while((bu=bufis.myRead())!=-1)
{
bufos.write(by);
}
bufos.close();
bufis.myClose();
}
//调用系统自己的方法
public static void copy_1() throws IOException
{
BufferedInputStream bufis=
new BufferedInputStream(new FileInputStream(“c:\\0.mp3”));
BufferedOutputStream bufos=
new BufferedOutputStream(new FileOutputStream(“c:\\1.mp3));
int by=0;
while((by=bufis.read())!=-1)
{
bufis.write(by);
}
bufos.close();
bufis.close();
}
}
</span>
读取键盘录入
System.out:对应的是标准输出设备,控制台;
System.in: 对应的是标准输入设备:键盘。
eg:需求:通过键盘录入数据。
当录入一行数据后,就将该行数据进行打印。
如果录入的数据时over,那么停止录入。
<span style="font-size:14px;">import java.io.*;
class ReadIn
{
public static void main(String[] args)throws IOException
{
InputStream in=System.in;
StringBuilder sb=new StringBuilder();
while(true)
{
int ch=in.read();
if(ch==’\r’)
continue;
if(ch==’\n’)
{
String s=sb.toString();
if(“over”.equals(s))
break;
System.out.println(s.toUpperCase());
sb.delete(0,sb.length()); //清空sb
}
else
sb.append((char)ch);
}
}
}
</span>
读取转换流
字符流:FileReader,FileWriter;
BufferedReader,BufferedWriter.
字节流:FileInputStream,FileOutputStream
BufferedInputStream,BufferedOutputStream
readLine方法是字符流BufferedReader类中的方法。
而键盘录入的read方法是字节流InputStream的方法。
将字节流转换成字符流再使用readLine方法。
代码:
<span style="font-size:14px;">import java.io.*;
class TransStreamDemo
{
public static void main(Sring[] args)throws IOException
{
//获取键盘录入对象
InputStream in=System.in;
//将字节流对象转成字符流对象,使用转换流。
InputStreamReader isr=new InputStreamReader(in);
//为了提高效率,将字符串进行缓冲区技术高效操作,使用
//BufferedReader
BufferedReader bufr=new BufferedReader(isr);
String line=null;
while((line=bufr.readLine())!=null)
{
if(“over”.equals(line))
break;
System.out.println(line.toUpperCase());
}
bufr.close();
}
}
</span>
写入转换流
OutputStreamWriter
代码:
<span style="font-size:14px;">import java.io.*;
class TransStreamDemo
{
public static void main(String[] args)throws IOException
{
//键盘的最常见写法
BufferedReader bufr=
new BufferedReader(new InputStreamReader(System.in));
//以下是写入转换流(输出)
OutputStream out=System.out;
OutputStreamWriter osw=new OutputStreamWriter(out);
BufferedWriter bufw=new BufferedWriter(osw); //为了用newLine()
String line=null;
while((line=bufr.readLine())!=null)
{
if(“over”.equals(line))
break;
bufw.writer(line.toUpperCase());
bufr.newLine();
bufr.flush(); //刷新,字符流要刷新
}
bufr.close();
}
}
</span>
流操作规律:
InputStreamReader/ OutputStreamWriter:是字节流通向字符流的桥梁。
1. 源:键盘录入;目的:控制台
BufferedReader bufr=
new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw=
new BufferedWriter(new OutputStreamWriter(System.out));
2. 需求:想把键盘录入的数据存储到一个文件中。
源:键盘;目的:文件。
BufferedReader bufr=
new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw=
new BufferedWriter(new OutputStreamWriter(new FileOutputStream(“out.txt)));
3. 需求:想要将一个文件的数据打印在控制台上。
源:文件;目的:控制台
BufferedReader bufr=
new BufferedReader(new InputStreamReader(“copypic.java”));
BufferedWriter bufw=
new BufferedWriter(new OutputStreamWriter(System.out));
流操作的基本规律:最痛苦的就是流对象很多,不知道该用哪一个。
通过三个明确来完成:
1> 明确源和目的
源:输入流:InputStream,Reader
目的:输出流:OutputStream,Writer
2> 操作的数据是否纯文本
是:字符流
不是:字节流
3> 当体系明确后,再明确要使用那个具体的对象
通过设备来进行区分:
源设备:内存、硬盘、键盘
目的:内存、硬盘、控制台
① 将一个文本文件中数据存储到另一个文件中,复制文件。
源:因为是源,所以使用读取流。InputStream、 Reader
是不是操作文本文件。
是!这时就可以选择Reader
这样体系就明确了。
接下来明确要使用体系中的哪个对象。
明确设备:硬盘上一个文件。
Reader体系中可以操作稳健的对象是:FileReader
是否需要提高效率:是!加入Reader体系中缓冲区BufferedReader
FileReader fr=new FileReader(“a.txt”);
BufferedReader bufr=new BufferedReader(tr);
目的:
OutputStream Writer
是否是纯文本
是!Writer
设备:硬盘,一个文件。
Writer体系中可以操作文件的对象FileWriter
是否需要提高效率:是!加入Writer体系中缓冲区BufferedWriter
FileWriter fw=new FileWirter(“b.txt”);
BufferedWriter bufw=new BufferedWriter(“fw”);
② 需求:将键盘录入的数据保存到一个文件中
这个需求中有源和目的都存在。
那么分别分析:源:InputStream、Reader
是否是纯文本:是!Reader
设备:键盘。对应的对象是:System.in
不是选择Reader吗?System.in对应的不是字节流吗?
为了操作键盘的文本数据方便。转成字符流按照字符串操作是最方便的。所以既然明确了Reader,那么就将System.in转成Reader。
用了Reader体系中的转换流,InputStreamReader
InputStreamReader isr=new InputStreamReader(System.in);
需要提高效率吗?需要!BufferedReader
BufferedReader bufr=new BufferedReader(isr);
目的:OutputStream Writer
是否是纯文本?是!Writer
设备:硬盘,一个文本,使用FileWirter
FileWriter fw=new FileWriter(“c.txt”);
需要提高效率吗?需要
BufferedWriter bufw=new BufferedWriter(fw);
扩展,想要把录入的数据按照指定的编码表(utf-8),将数据存到文件中。
目的:OutputStream、Writer
是否是纯文本?是!Writer
设备:硬盘,一个文件、使用FileWriter
但是FileWriter是使用的默认编码表、GBK
但是存储时,需要加入指定编码表utf-8,而指定的编码表只有转换流可以指定,所以要使用的对象是OutputStreamWiter
而该转换流对象要接受一个字节输出流,而且还可以操作的文件的字节输出流FileOutputStream
OutputStreamWriter osw=
new OutputStreamWirter(new FileOutputStream(“d.txt”),”utf-8”);
需要高效吗?需要
BufferedWriter bufw=new BufferedWriter(osw);
所以,记住。转换流什么使用,字符流和子街之间的桥梁,通常涉及到字符编码转换时,需要用到转换流。
改变标准输入输出设备
System下的:SetIn、SetOut
引用“写入转换流”的例子。
代码:
public static void main(String[] args)throws IOException
{
System.SetIn(new FileInputStream(“PersonDemo.java”));
System.SetOut(new PrintStream(“222.txt”));
}
File概述
File类:用来将文件或者文件夹封装成对象
方便对文件与文件夹的属性信息进行操作
File对象可以作为参数传递给流的构造函数
了解File中常用方法
<span style="font-size:14px;">//创建File对象
public static void consMethod()
{
//将a.txt封装成file对象,可以装已有的和未出现的文件或者文件夹封装
//成对象
File f1=new File(“a.txt);
//File(String parent,String child) ;parent代表路径名字符串,child为parent
//下的File实例
File f2=new File(“c:\\abc”,”b.txt”); //方式1
File d=new File(“c:\\abc”);
File f3=new File(d,”c.txt”);
System.out.println(“f1:”+f1); //打印的是文件的路径(相对路径就打相对,
//绝对就打绝对.
System.out.println(“f2:”+f2);
System.out.println(“f3:”+f3);
}
</span>
File对象功能-创建和删除、判断、获取
File类常见方法:
1. 创建
boolean createNewFile():在指定位置创建文件,如果该文件已经存在,则不创建,返回false。和输出流不一样,输出流对象一建立创建文件,而且文件已经存在的话,会覆盖。
boolean mkdir():创建文件夹(只能创建一级目录)
boolean mkdirs():创建多级文件夹。
File dir=new File(“abc\\kk\\cc”); dir.mkdirs();
2. 删除
boolean delete():删除失败返回false,如果文件正在被使用,则删除不了返回false。
void deleteOnExit():在程序退出时删除指定文件。
3. 判断(java有的文件用流无法访问,如:c:\\Systemde目录;java不管隐藏与否都拿)
boolean exists():文件是否存在
isFile(): 是否是一个标准文件
isDirectory():此路径名表示的文件是否是一个目录。
isHidden(): 是否是一个隐藏文件
isAbsolute(): 测试此抽象路径是否为绝对路径名
4. 获取信息
getName():返回由此抽象路径名表示的文件或目录的名称。
getPath():将此抽象路径名转换为一个路径名字符串。
getParent():返回此抽象路径名父目录的路径名字符串,如没有指定父目录,返null。
getAbsolutePath():返回此抽象路径名的绝对路径名字符串。
long lastModified():返回由此抽象路径名表示文件的长度。
long length():返回有此抽象路径名表示文件的长度。
renameTo(File dest):重命名此抽象路径明表示的文件。
File 对象功能——文件列表
(static File)listRoots()列出可用的文件系统根(返回一个File数组)
eg:
<span style="font-size:14px;"> File[] files=File.listRoots();
for(File f:files)
{
System.out.println(f);
}
</span>
结果:(列出了及其中的盘符)
C:\
D:\
E:\
F:\
(String[] )List(),用来返回指定抽象路径下的文件和目录(包含隐藏文件)
eg:
<span style="font-size:14px;">File f=new File(“c:\\”);
String[] name=f.list();
for(String name:name)
{
System.out.println(name);
}
</span>
(String[] )List(FilenameFilter filter) 【文件名过滤,如只需要某目录下的.java
文件】
eg:
<span style="font-size:14px;"> //取.bmp文件后缀的文件
File dir=new File(“d:\\java12\\day18”);
String[] arr=dir.list(new FilenameFilter()
{
public boolean accept(File dir,String name)
{
return name.endsWith(“.bmp”);
}
});
for(String name:arr)
{
System.out.println(name);
}
</span>
IO包中的其他类
打印流
PrintWriter与PrintStrem
可以直接操作输入流和文件
序列流
SequenceInputStream
对多个流进行合并
操作对象
ObjectInputStream与ObjectOutputStream
被操作的对象需要实现Serializable(标记接口);
IO包中的其他类
操作基本数据类型
DataInputStream与DataOutputStream
操作字节数组
ByteArrayInputStream与ByteArrayOutStream
操作字符数组
CharArrayReader与CharArrayWriter
操作字符串
StringReader与StringWriter