---------------------- android培训、java培训、期待与您交流! ----------------------
IO流总结
一、IO流
IO流用来处理设备之间的数据传输
Java中的数据全都是用流来操作的,java所有的流都放在IO包中。
分类:流按操作对象分为两种:字节流与字符流。
流按流向分为:输入流,输出流。输入输出流一般是对应的,有输入流就有对应的输出流。
输出流中,指定的目的文件不存在时会新建,存在时会覆盖。
IO流中的阻塞方法:没有读到数据就等。read
二、字符流和字节流
字符流的抽象基类:Reader , Writer。
字节流的抽象基类:InputStream ,OutputStream。
这四个类的子类命名:这四个父类名作为子类的后缀名,可以看出属于这四种流中的哪一种。前缀名表示流的功能。
字符流和字节流区别:1.字符流向文件存数据时,必定会 用读到的数据去查找编码表。所以,用字符流copy图片时,图片不能看。
2.字节流写数据write时,会直接将字节数据写到目的地。字节流在指定缓冲区(BufferedInputStream)时才刷新。
字符流写数据write时,数据会先缓冲起来,需要刷新到目的地。原理:字符流内部封装了字节流,由于它操作的是文本文件,
例如读一个汉字(2个字节),将读到的第一个字节缓冲起来,读完第二个字节后,再去查编码表,经过刷新将其对应的汉字写到目的文件。
三、字符流和字节流体系
字符流
Reader抽象类
|---InputStreamReader:字节流转换为字符流的桥梁。在创建该流实例时,就要传入一个要转换的字节流。构造方法可以指定编码表。
|---FileReader:文件读取流。操作文本文件
|---BufferedReader:字符流缓冲区
|---LineNumberReader:跟踪行号的缓冲字符输入流。用setLineNumber(int)和getLineNumber()设置和获取行号。
|---PipedReader管道输入流,应该连接到管道输出流;不建议对这两个对象尝试使用单个线程,因为这样可能死锁线程。
|---CharArrayReader
Writer:该类中的write方法,只能将数据写到流中,不能写到目的地,要写到目的地,必须进行刷新。
|--OutputStreamWriter:字符流转换为字节流的桥梁。最重要的是,构造方法可以指定编码表,并且只有转换流能指定编码表
|---FileWriter:文件写入流。构造方法使用 默认编码表GBK
|---BufferedWriter
|---PipedWriter
|---CharArrayWriter
|---PrintWriter 构造方法中要传入目的,即文件或者输出流,可以是字节输出流或者字符输出流。特点:可以设置自动刷新。
Socket s=new Socket(); PrintWriter out=new PrintWriter(s.getOutputStream(),true); out.println("nihao");//println方法并没有向流中写入结束标记。
字节流
InputStream抽象类(System.in)
|---FileInputStream:操作非文本文件,如图片、视频、音乐。
|---FilterInputStream
|---BufferedInputStream
|---DataInputStream
|---ObjectInputStream只有字节流
|---PipedInputStream字节流字符流都有
|---ByteArrayInputStream
OutputStream:该类写数据时,可以直接将数据写到目的地,不用刷新,只有用到缓冲区BufferedOutputStream时,才需要刷新。
|---FileOutputStream:文件输出流,将数据写入 File 或 输出流
|---FilterOutputStream
|---BufferedOutputStream
|---PrintStream(System.out)
|---DataOutputStream
|---ObjectOutputStream
|---PipedOutputStream
|---ByteArrayOutputStream
四、方法
1.超类方法
Writer中的方法:
void write(char[] cbuf) 将字符数组cbuf写入到输出流(经刷新再写入目的地)。
abstract void write(char[] cbuf, int off, int len) 写入字符数组的某一部分。
void write(int c) 写入单个字符。写入的是c的低16位,16个高位被忽略。
void write(String str) 写入字符串。
void write(String str, int off, int len) 写入字符串的某一部分。
abstract void close() 关闭此流,但要先刷新它。
abstract void flush() 刷新该流的缓冲。
Reader中的方法:
int read() 一次读一个字符。而且会自动往下读。返回的是,字符对应的整数,读完时返回-1
int read(char[] cbuf) 将数据读入到字符数组cbuf中,返回的是读到的字符个数
abstract int read(char[] cbuf, int off, int len) 将数据读入到数组的某一部分。
abstract void close() 关闭该流并释放与之关联的所有资源。
---------------------------------------------------------------------------------------------
OutputStream中的方法:
void write(byte[] b) 将字节数组b写入此输出流,不用刷新可直接到目的地。
void write(byte[] b, int off, int len)
abstract void write(int b) 写入单个字节。写入的是b的低8位,24个高位被忽略。注意:将read方法提升后的整数,强转了。
void close()
void flush();
InputStream中的方法:与字符流Reader的区别就是:将字符换成字节。读取一个字符换成读取一个字节,读取一个字符数组换成读取一个字节数组。
abstract int read() 从输入流中读取数据的下一个字节。返回 0 到 255 范围内的 int 字节值。
即,将读取到的字节转换成int返回,当读取完时返回-1.注意:read方法在做提升。这样做可以避免读取到的字节与判断读取结束的标记-1相撞。
提升原理:在8个二进制位前面补24个0,即将读取到的字节 与上整数255。
11111111 读取的字节正好为-1
11111111-11111111-11111111-11111111 提升成int:-1
& 00000000-00000000-00000000-11111111 &255
00000000-00000000-00000000-11111111 要返回的数,从-1成为了255.
int read(byte[] b) 从输入流中读取一定数量的字节,并将其存储在缓冲区数组 b 中,返回读取的字节个数。
int read(byte[] b, int off, int len)
int available();返回的是要读取的文件的字节数,即文件的大小,包括换行符。
2.子类方法
2.1 FileWriter中的方法:都继承自父类
在创建一个文件写入流对象时,就要传入一个目的文件。当文件已存在时,就覆盖;文件不存在时,就在指定目录下新建。
当传入的文件路径错误时,会抛出IOException
FileReader中的方法:都继承自父类
在创建一个文件读取流对象时,同样要传入一个源文件。如果源文件不存在,会抛出异常 FileNotFoundException
常用代码:char[] buf=new char[1024];
int num=0;
while((num=fileReader.read(buf))!=-1){
fileWriter.write(buf,0,num);
}
fileWriter.close();
fileReader.close();
使用完记得关闭流。
2.2 缓冲区。
特点:1.在创建缓冲区之前,必须要先有流对象。因为缓冲区的出现就是为了提高流的读写效率。
2.关闭了缓冲区,就是在关闭缓冲区中的流对象。就不用再关闭流对象。原理:缓冲区的close方法,调用了关闭流的代码
3.只要用到BufferedWriter缓冲区,就必须刷新
注意:数组只是起到缓冲的作用,并不是缓冲区。
字符流缓冲区:
BufferedWrite的方法: void newLine();跨平台的换行方法
BufferedReader的方法:String readLine(); 读取一行。不包含任何行终止符。返回的是读取到的数据,当读取完毕时,返回null。
字节流缓冲区。
BufferedOutputStream:继承自OutputStream
BufferedInputStream: 继承自InputStream
五、流操作的基本规律
流对象有很多,该选用哪一个?
通过三个明确来完成:
1,明确源和目的。
源:输入流。InputStream Reader
目的:输出流。OutputStream Writer。
2,操作的数据是否是纯文本。
是:字符流。 打开文件能看懂的 是纯文本。如.txt、.java、键盘录入的数据、打印到控制台的数据等
不是:字节流。 打开文件看不懂的 不是纯文本的:图片、音乐、视频
3,当体系明确后,在明确要使用哪个具体的对象。
通过设备来进行区分:
源设备:内存,硬盘。键盘(字节流 System.in,但读取的数据是能看懂的纯文本,所以要进行字节流转换成字符流)
目的设备:内存,硬盘,控制台(字节流 System.out)。
注意:有必要时,要进行流转换。
例如:将键盘录入的数据保存到文件中。
源:1.InputStream Reader 2.是能看懂的纯文本,字符流。
3.键盘 字节流,此时为了方便操作纯文本数据,应将字节流转换为字符流。BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
扩展一下,将键盘录入的数据按照指定的编码表(utf-8),将数据存到文件中。
目的:OutputStream Writer
是否是存文本?是!Writer。
设备:硬盘。一个文件。使用 FileWriter。
但是FileWriter是使用的默认编码表。GBK.
但是存储时,需要加入指定编码表utf-8。而指定的编码表只有转换流可以指定。
所以要使用的对象是OutputStreamWriter。
而该转换流对象要接收一个字节输出流。而且还可以操作的文件的字节输出流。FileOutputStream
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("d.txt"),"UTF-8");
需要高效吗?需要。
BufferedWriter bufw = new BufferedWriter(osw);
所以,记住。转换流什么使用?字符和字节之间的桥梁。
通常,涉及到字符编码转换时,需要用到转换流。
六、IO的异常处理
凡是与设备数据有关系的数据都会发生IO异常。
public static void copyFile_Exception(){
BufferedReader br=null;
BufferedWriter bw=null;
try{
br=new BufferedReader(new FileReader("f:\\a.txt"));
bw=new BufferedWriter(new FileWriter("f:\\aa.txt"));
String line=null;
while((line=br.readLine())!=null){
bw.write(line);
bw.newLine();
bw.flush();
}
}catch(Exception e){
e.printStackTrace();
}
finally{
//关闭多个流时,要分别进处理
try{
br.close();
}
catch(Exception e){
e.printStackTrace();
}
try{
bw.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
七、其它类
1.Properties容器:获取配置文件内容时使用
特点:1.是Map体系中Hashtable的子类,具备map集合的特点
2.存储的键值对都是字符串。
3.集合中和IO技术相结合的集合容器。
4.用于键值对形式的配置文件。可以通过读取流直接将配置文件中的键值对直接加载到内存中。那么在加载数据时,需要数据有固定格式:键=值。
方法:
添加: Object setProperty(String key, String value) ;向集合中添加键值对。
获取: String getProperty(String key)根据键获取值。如果在此属性列表中未找到该键,则接着递归检查默认属性列表及其默认值。如果未找到属性,则此方法返回 null。
void load(InputStream inStream) 将文件中的键值对(属性列表)通过流直接加载到内存的集合当中。
void load(Reader reader)
void store(OutputStream out,String comments)与load方法相反。将集合当中的键值对通过流写入到文件(属性列表)当中。
comments - 对属性列表的描述。前面加了个#,是不会被加载到集合当中的。
void store(Writer writer, String comments)
Set<String> stringPropertyNames() 返回此属性列表中的键集,其中该键及其对应值是字符串,如果在主属性列表中未找到同名的键,则还包括默认属性列表中不同的键。
方法练习:
//将属性列表中的键值对加载到集合当中
FileReader fr = new FileReader("f:\\属性列表.txt");
Properties prop=new Properties();
prop.load(fr);
prop.list(System.out);
//将集合中的键值对写入到属性列表中
prop.setProperty("zhangsan", "20");
FileWriter fw = new FileWriter("f:\\属性列表.txt");
prop.store(fw, "描述信息");
2.ObjectOutputStream和ObjectInputStream:操作对象的字节流。只有字节流没有字符流。
ObjectOutputStream:
构造方法 :ObjectOutputStream(OutputStream out)
方法:void writeObject(Object obj) 将对象obj写入到关联的OutputStream指定的文件当中。
方法当中含有写入8中基本数据类型的方法:writeInt(int val)(写入一个大于255的数据时用此方法)、writeFloat(float val)...
ObjectInputStream:
构造方法 :ObjectInputStream(InputStream in)
方法: Object readObject() 读取在文件当中存储的对象。方法当中含有读取8中基本数据类型的方法:
注意:要被操作的对象必须被序列化,即所属的类 要继承Serializable接口。
序列化特点:1.静态成员变量 不能序列化。
2.非静态成员变量 不想参加序列化时,可以在前面加上transient。
3.在类中加入代码 public static final long serialVersionUID = 42L;时,就是自己在给类编序列号。
如果没有该代码时,Java会自动编号。此时,如果对参加序列化的成员进行修改,那么文件中保存的对象读取出来时就不能使用了。
存取对象演示:
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
oos.writeObject(new Person("lisi0",399,"kr"));
oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
Person p = (Person)ois.readObject();
System.out.println(p);
ois.close();
class Person implements Serializable
{
public static final long serialVersionUID = 42L;
private String name;
transient int age;
static String country = "cn";
Person(String name,int age,String country)
{
this.name = name;
this.age = age;
this.country = country;
}
public String toString()
{
return name+":"+age+":"+country;
}
}
3.管道流:PipedWriter、PipedReader和PipedOutputStream、PipedInputStream
1.用构造方法连接,或connect方法
2.它的写方法,是将数据写到了管道中;读取数据,是从管道中读取。
3.结合线程使用。
PipedReader pr=new PipedReader();
PipedWriter pw=new PipedWriter(pr);
/*或者 PipedWriter pw=new PipedWriter(); pw.connect(pr);*/
class Write implements Runnable{
private PipedOutputStream out=null;
PipedOutputStream(PipedOutputStream out){ this.out=out; }
public void run(){
try{
syso("开始写入数据,等待6秒后。");
Thread.sleep(6000);
out.write("piped is coming!".getBytes());
out.close();
}
catch(Exception e){ syso("管道关闭失败"); }
}
}
class Read implements Runnable{
private PipedInputStream in=null;
PipedInputStream(PipedInputStream in){ this.in=in; }
public void run(){
byte[] buf=new byte[1024];
int len=0;
while((len=in.read(buf))!=-1){
syso(new String(buf,0,len));
}
}
}
class PipedStreamDemo{
public static void main(String[] args){
PipedInputStream in=new PipedInputStream();
PipedOutputStream out=new PipedOutputStream(in);
Read r=new Read(in);
Write w=new Write(out);
new Thread(r).start();
new Thread(w).start();
}
}
4.RandomAccessFile随机访问文件
特点:1.该类不是IO体系中子类,而是直接继承自Object。但是它是IO包中成员。因为它具备读和写功能,原理是内部封装了字节输入流和输出流。
2.内部封装了一个数组,而且通过指针对数组的元素进行操作。也就是将文件中的内容放入到了数组中,可以随机访问数组中的任意一段数据。
可以通过getFilePointer获取指针位置,同时可以通过seek改变指针的位置。
3.该类只能操作文件。而且操作文件还有模式:只读r,,读写rw等。
如果模式为只读 r。不会创建文件。会去读取一个已存在文件,如果该文件不存在,则会出现异常。而普通读取流,文件不存在时也创建。
如果模式rw。操作的文件不存在,会自动创建。如果存在则不会覆盖。
4.可以随机读写文件。在指定的位置写一段数据,前面可能还没写上数据。应用:用多线程,每个线程在指定位置段写数据,实现多线程下载。
方法:1.读写基本数据类型的方法:writeInt(int v)、readInt()、writeByte(int v)...
2. long getFilePointer() 获取指针当前位置。
void seek(long pos)改变指针位置。
3. int skipBytes(int n) 让指针跳过n个字节。只能向后跳,不能回跳。
5.DataInputStream、DataOutputStream:操作基本数据类型的流。
特点:在创建实例对象时,就要传入流。
方法:1.writeInt(int v)、readInt()、writeByte(int v)...
2.writeUTF(String s)将字符串s按照utf修改版写入文件当中。读取时必须按utf修改版读取,即String readUTF().
方法演示: DataOutputStream dos=new DataOutputStream(new FileOutputStream("gbk.txt"));
dos.writeInt(234);
dos.writeBoolean(true);
dos.writeDouble(9887.543);
dos.close();
DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
int num = dis.readInt();
boolean b = dis.readBoolean();
double d = dis.readDouble();
System.out.println("num="+num);System.out.println("b="+b); System.out.println("d="+d);
dis.close();
DataOutputStream dos=new DataOutputStream(new FileOutputStream("utf.txt"));
dos.writeUTF("你好");
DataInputStream dis = new DataInputStream(new FileInputStream("utf.txt"));
String s=dis.readUTF();
6.ByteArrayInputStream 、ByteArrayOutputStream:用于操作字节数组的流对象。应用:将文件中的数据存放到内存中的数组当中。
特点:1.ByteArrayInputStream在构造的时候,需要接收数据源。而且数据源是一个字节数组。
ByteArrayOutputStream在构造的时候,不用定义数据目的,因为该对象中已经内部封装了可变长度的字节数组。这就是数据目的地。
2.因为这两个流对象都操作的数组,并没有使用系统资源。所以,不用进行close关闭。
在流操作规律讲解时:
源设备,
键盘 System.in,硬盘 FileStream,内存 ArrayStream。
目的设备:
控制台 System.out,硬盘FileStream,内存 ArrayStream。
用流的读写思想来操作数据。
----------------------详细请查看:http://edu.csdn.net/heima---------------------------------------