---------------------- ASP.Net+Android+IO开发S、.Net培训、期待与您交流! ----------------------
一、IO流概述
IO流即输入(Input)输出(Output)流,流是种形象的说法,计算机数据(字节或字符)的转移都是大量、持续的,像水流般。输入指将制定位置的数据读入内存中,输出指将内存数据写到指定的位置上。那么IO流是用来处理设备间的数据传输的。
在java中:
(1) 对数据的操作是通过流的方式。
(2) 用于操作流的对象都在IO包中。
(3) 流按操作数据分为两种:字节流与字符流
(4) 流按流向分为:输入流和输出流。
IO流操作的异常处理:
由于IO流存在各方面的不确定,需要异常处理机制的支持,在常规处理时都建议采用捕获方式,下面IO流知识点的例子,为了突出知识点,简化了异常的处理,采用抛出方式,以便能使代码清晰些,常规处理应采用下列格式:
/*
IO异常的处理方式
*/
import java.io.*;
class IoExceptionDemo
{
public static void main(String[] args)
{
//在外面建立引用对象,在try中构造初始化
//colse也要求处理IO异常,所以也的进行try、
//即3个方法都要try处理
//如构造时抛出异常,将中断,但会执行finally中的
//fw.close,因为构造失败fw=null,调用失败
//需在前进行健壮性的判断,if(fw != null)
FileWriter fw = null;
try
{
fw =new FileWriter("Demo.txt");
//write将内容写入流缓存中,并未写入文件中
fw.write("写入文本内容");
//加flush刷新后,流中内容写入文件中,但流并未关闭,可继续写
fw.flush();
}
catch (IOException e)
{
System.out.println(e.toString());
}
finally
{
try
{
//这步要有,安全判断
//因fw =new FileWriter("Demo.txt");可能会失败,比如在不存在的K盘下
if(fw != null)
fw.close(); //先刷新,后关闭流,不能再写入数据了
}
catch (IOException e)
{
System.out.println(e.toString());
}
}
System.out.println("文件创建成功!");
}
}
二、字符流
在进行数据操作时,操作数据以字符为单元,该类体系为Reader和Writer。
1、 Writer和Reader
Writer为写入字符流的抽象类,其子类后缀名基本是Writer。其子类必须要实现父类的抽象方法为write(char[], int,int)、flush() 和 close()。当我们向文件write数据时,数据仅存在缓冲中,需用flush刷新后数据才从缓冲写入文件中。Close之前会进行一次缓冲的刷新。其实jvm是在调用系统内部的读写方法,因为系统会不同,jvm如都用自已的方式写入数据是不合适的。
Reader为读入字符流的抽象类,其子类后缀名基本是Reader,必须要复写的方法有read(char[],int, int) 和 close()。
2、 BufferedWriter和BufferedReader
为了提高对数据的读写效率,IO中定义了缓冲区,可以说是一个过渡区,将陆续的数据先写入缓冲区积攒,而后一股脑写入目标设备。就像我们写word,写一段后保存,而不是写一个字保存一下。所以不建议采用,读一字符,写一字符的方式,因为这使磁头读写操作频繁,建议先读一次性入流中,再将流数据写入目的文件中。 最后关闭读写流
特点:
①缓冲区的技术原理其实就是封装了数组。
②它的出现是为了提高流的操作效率存在的,所以在创建时,构造函数必须传入一个流对象。
③调用close关闭缓冲区时,就会关闭缓冲区的流对象,所以关闭了缓冲区就不需要写流对象关闭了。
④BufferedWriter和BufferedReader的类构造采用了装饰设计模式,对流的操作类进行了功能的加强,至于装饰模式,将在以后介绍。
BufferedWriter:字符流输出的缓冲区,提供了一个跨平台的换行方法newLine(),解决在不同系统中换行符不统一的问题。在向目标输出前记得先flush()刷新。
BufferedReader:字符流输入的缓冲区,同样提供了一个readLine方法,从文件中读取一行数据,返回给String,当读到文件尾时返回空,其本质还是用read一个一个字符读取的,只不过积攒一行返回。该类有个LineNumberReader子类,比BufferedReader多出行号的追踪功能。方法有setlineNumber();设置行号getReadLineNumber();读出行号
3、 InputStreamReader和OutputStreamWriter
转换流:由于某些需求,需要在字符流和字节流间互相转换,java便将该操作封装成类,以供需求,如他们的子类FileWriter和FileReader,我们直接可以用其子类进行字符的操作,而不必去考虑复杂的字节。
InputStreamReader为字节流转换成字符流,这样便可利用字节流的BufferedReader缓冲区操作提高效率,要求在创建时向构造函数传入InputStream的字节流对象,也可以用构造函数的Charset参数指定编码格式。InputStreamReader构造函数中传入System.in流参数,便可接手键盘录入数据。
OutputStreamWriter字符流转换成字节流,同理,只不过参数输入改输出,如接收System.out打印在控制台。
4、 FileWriter与FileReader
他们分别属于InputStreamReader和OutputStreamWriter的子类,专门对字符数据的文件进行操作。可接收路径字串和File对象等。
FileWriter:用来写入字符文件的便捷类,有默认的字符编码和缓冲区。可进行对文本的写入和文本的追加,追加文本的构造函数只要将参数2用上,初始成true即可,写入前需刷新。创建对象时,该类会打开指定文件,若文件已打开则创建失败。
FileReader:用于读取字符文件的便捷类,同样具有默认编码和缓冲区。由read()读取单个字符,返回一个整数,读到文件尾返回-1。
IO流操作会抛出异常,需处理,以应对文件已打开或文件不存在等异常,异常处理稍后介绍。下面是1、2、3的一个综合例子:将键盘录入,写入文件中。
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
/*
* 需求:将键盘录入写入文件中
*
* 说明这里不重点体现异常处理,所以简单的抛出了
* 正规开发,建议用try进行捕获
* */
public class CopyText
{
//由于异常处理不是这里要体现的,所以采取抛出异常,正常编码建议捕捉
public static voidmain(String[] args) throws IOException
{
//标准的键盘录入方式,将输入转字符交给缓冲区。
BufferedReader bufr= new BufferedReader(new InputStreamReader(System.in));
//FileWriter读取字符文件,交给缓冲,会抛出异常,简单抛出了,平时建议捕捉
BufferedWriter bufw= new BufferedWriter(new FileWriter("Test.txt"));
String strLine =null;
while((strLine =(bufr.readLine()))!=null)
{
//输入over时,结束
if("over".equals(strLine))
break;
//写入文本
bufw.write(strLine);
bufw.newLine();//换行
bufw.flush();//刷新
}
bufr.close();
bufw.close();
}
}
5、 CharArrayReader和CharArrayWriter
提供在缓冲区和内存间的操作方式,用于操作字符数组,内部拥有可自动延长的内存空间,由于是操作内存,最后不需关闭。
CharArrayReader,需接收一个字符数组型的数据源,将内存的数据以数组的方式存入传入的数组源中,无需关闭。
CharArrayWriter,同理。
6、 PrintWriter
向文本输出流打印对象的格式化表示形式,为其他字符流类添加了输出功能,在构建时,将构造函数参数2置true,还可带自动刷新功能,利用println()能实现换行,等于代替了上述代码中的 bufw.write(strLine);bufw.newLine();bufw.flush();三句。
构造函数能接收的文件源可以是,File对象,字符串路径,字节输出流。
7、 PipedWriter和PipedReader
管道流,它具有将一个程序的输出当做另一个程序的输入的能力,这个涉及线程,也就是我们线程通信的另一种方式,利用管道流通信,它需要利用connect将两者连接,即:读.connect(写)。
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
/*
* 字符管道流的测试*/
class Read implements Runnable
{
private PipedReader pr;
Read(PipedReader pr)
{
this.pr = pr;
}
public void run()
{
try
{
//构建接收数组
char[] chs = new char[1024];
//读取连接的写入管道流,多句用循环
int len = pr.read(chs);
System.out.println(newString(chs,0,len));
pr.close();
}catch(Exception e)
{
throw new RuntimeException("读取失败!");
}
}
}
class Write implements Runnable
{
private PipedWriter pw;
Write(PipedWriter pw)
{
this.pw = pw;
}
public void run()
{
try
{
//直接写出
pw.write("这是管道流数据!");
//多句要pw.flush()刷新,这里再close前自动刷新了
pw.close();
}catch(Exception e)
{
throw new RuntimeException("写入失败!");
}
}
}
public class PipedDemo
{
public static void main(String[] args) throws IOException
{
PipedWriter pw = new PipedWriter();
PipedReader pr = new PipedReader();
//需连接,读的连接写的
pr.connect(pw);
new Thread(new Read(pr)).start();
new Thread(new Write(pw)).start();
}
}
8、RandomAccessFile
该类不算是IO体系中子类,而是直接继承自Object。随机访问文件,自身具备读写方法。通过skipBytes(int x),seek(int x)来达到随机访问,即含有文件指针,可跳跃。
但是它是java.IO包中成员,因为它具备读和写功能,
它内部封装了一个数组,而且通过指针对数组的元素进行操作。可以通过getFilePointer获取指针位置。同时可以通过seek改变指针的位置。
其实完成读写的原理就是内部封装了字节输入流和输出流。
其构造函数只能接收文件对象,而且可以指定模式,如’r’,只读。
局限性:通过构造函数可已看出,该类只能操作文件。而且操作文件要用模式。
三、字节流
在进行数据操作时,操作数据以字节为单元,该类体系为InputStream和OutputStream,字节流的输出是直接从内存写入,不需要转字符的过程,因此没有字符流刷新的过程。
1、InputStream和OutputStream
InputStream为写出字节流的抽象类,其子类后缀名基本是InputStream。提供read()方法。
OutputStream为读入字节流的抽象类,其子类后缀名基本为OutputStream。提供write()方法。
2、FileOutputStream和FileInputStream
分别为InputStream和OutputStream的子类,复写了read()方法,主要用于读取诸如图像数据之类的原始字节流。没有缓冲区,自己构建byte类型的数组。
FileOutputStream:以字节流的形式输出到文件中,复写了write方法,一次写入一个字节,若要已向文件结尾处追加字节,在创建时的构造函数中初始第二个参数为true。
FileInputStream:将字节流读入到内存中,复写了read方法,每次只能读一个字节,返回一个int型数据,当读到文件尾时返回-1。他有个特有的方法available(),返回文件剩余的字节数。如果文件较小,可以先读出大小,声明足够的byte数组,不用循环,但是文件不能过大,虚拟机不可能开辟很大的内存空间,比如视频文件,new一个数组不会成功,所以应视情况使用available()。
其格式类似与FileWriter与FileReader,在这里不写例子了,稍作改动即可,将字符读入改为字节流的byte,不用刷新等。
3、ByteArrayOutputStream与ByteArrayInputStream
与字符流的CharArrayReader和CharArrayWriter同理,只不过数据源为byte数组。
4、PipedOutputStream与PipedInputStream
与字符流的PipedReader和PipedWriter同理,只不过一切按字节流的操作方式。
5、ObjectInputStream与ObjectOutputStream
对象序列化流,用于对象的序列化(持久化),即能将对象存入硬盘中,保存对象和对象数据。由ObjectOutputStream将java对象的基本数据类型和图形写入OutputStream(流)中,使用ObjectOutputStream可读取重构该对象。被操作的对象所属类必须实现Serializable接口,该接口没有要复写的方法,我们叫这种类型的接口为标记接口。实现该接口,其实就是给每给要保存的类一个ID号,简称UID,若要保存对象的类不实现该接口使用对象序列化流,将会报NotserializableException异常。
默认对象序列化后,不能修改其类,因为序列化文件中记录的UID变化了,使用时或报错,为防止修改类时,保存文件变化UID而导致不可用,我们采用自定义UID,
如public static final long srrialVersionUID = 42L;
静态成员不会被序列化,他再方法区,而对象在堆中。若不想某成员序列化,加tansient修饰该成员。
这两个类必须成对使用,用ObjectOutputStream写,就用ObjectInputStream。
如下例:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class ObjectStreamDemo
{
public static void main(String[] args) throws Exception
{
writeObject();
readObject();
}
public static void writeObject() throwsException
{
//这里为简化代码,异常省略捕捉处理
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("obj.txt"));
//写入一个Person对象
oos.writeObject(new Person("张三"));
oos.close();
}
public static void readObject() throws Exception
{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("obj.txt"));
//从文件中读取序列化的对象
Person p = (Person)ois.readObject();
System.out.println(p);
ois.close();
}
}
//要序列化的对象类,必须向实现Serializable
classPerson implements Serializable
{
//自定义一个标记号,防止修改二影响序列化的对象不可用。
public static final long serialVersionUID =42L;
private String name=null;
Person(String name)
{
this.name = name;
}
public String toString()
{
return "姓名:" + name;
}
}
6、SequenceInputStream
SequenceInputStream对多个流进行合并。从第一个流,读完,再接着读取第二流,依次下去。可以将多个流的操作数据写入到一个目标中。比如将三个文件的数据写入到一个文件中。构造函数中传入Enumeration对象,由Enumeration保存多个流对象。Enumeration时旧版本中Vector的类型。
先准备三个文件1.txt,2.txt,3.txt,将内容合并到4.txt中如下例子:
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.SequenceInputStream;
import java.util.Enumeration;
import java.util.Vector;
public class SequenceInputStreamDemo
{
//这里为代码简练省略捕捉处理异常
public static void main(String[] args)throws IOException
{
//由于SequenceInputStream的构造函数要传Enumeration类型,这里用Vector
Vector<FileInputStream> vct = new Vector<FileInputStream>();
//准备合并的三个文件
vct.add(new FileInputStream("1.txt"));
vct.add(new FileInputStream("2.txt"));
vct.add(new FileInputStream("3.txt"));
//获取三个文件的流对象
Enumeration<FileInputStream> en =vct.elements();
//合并
SequenceInputStream sis = new SequenceInputStream(en);
//合并后写入另一个文件
FileOutputStream fos = new FileOutputStream("4.txt");
byte[] buf = new byte[1024];
int len = 0;
while((len = sis.read(buf))!=-1)
{
fos.write(buf,0,len);
}
fos.close();
sis.close();
}
}
切割文件:
利用自定义byte[]数组限制读入大小,将文件分批存入多个新建的文件中。
但是在切割较大的视频的文件时,就不能new过大的byte数组了,
应定义1024*1024(1M)大小的数组向一文件存入数据,再采用一计数器记录装入了多少数据,达到要求时,再新建一文件,继续装入下一个。无太多技术要求,纯算法,这里不再举例。
8、 FilterInputStream与FilterInputStream的子类
(1) BufferedInputStream与BufferedInputStream
功能与字符流的BufferedReader与BufferedWriter缓冲区类似,早构造时可以指定缓冲大小
下面写一个拷贝图片的例子:
import java.io.*;
public class CopyPicDemo
{
//简化代码,没做异常的捕捉
public static void main(String[] args) throws IOException
{
//字节流缓冲区
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.jpg"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("2.jpg"));
int by = 0;;
while((by = bis.read())!=-1)
{
bos.write(by);
}
bis.close();
bos.close();
}
}
(2) DataOutputStream与DataIntputStream
可以对基本数据类型输入输出,构造时需传入流对象,DataOutputStream使用writeInt(int)、writeBoolen(bool)等方法向文本写入基本数据类型,用DataIntputStream的readInt()、readBoolean()等方法读出,有严格的字节顺序,读错顺序后面的数据将不能正常读出。
(3) PrintStream
类似于字符流的PrintWriter类。它为其他输出流添加了功能,使它们能够方便地打印各种数据值表示形式。与其他输出流不同,
PrintStream
永远不会抛出IOException
;而是,异常情况仅设置可通过checkError
方法测试的内部标志,通过checkError()方法获得。
---------------------- ASP.Net+Android+IO开发S、.Net培训、期待与您交流! ----------------------