------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
一、IO概述
IO流,即InputOutput的缩写。
IO的特点:
(1)IO流用来处理设备间的数据传输。
(2)Java中对数据的操作是通过流的方式。
(3)Java用于操作流的对象都在IO包中。
(4)流按操作数据分为两种:字节流和字符流。
按流向分为:输入流和输出流。
注意,流只能操作数据,不能操作文件。
IO流的常用基类:
(1)字节流的抽象基类:InputStream和OutputStream
(2)字符流的抽象基类:Reader和Writer
这四个基类派生出来的子类名称都是以父类名作为子类名的后缀,以功能为前缀。如InputStream的子类FileInputStream。
二、字符流
字符流中的对象融合了编码表,使用的是默认的编码,即当前系统的编码。字符流只用于处理文字数据,而字节流可以处理媒体数据。IO流是用于操作数据的,数据的最常见体现形式就是文件,查阅API,可以找到专门用于操作文件的Writer子类对象:FileWriter后缀是父类名,前缀是功能。该流对象一被初始化,就必须有被操作的文件存在。
字符流的读写:
1.写入字符流:
(1)创建一个FileWriter对象,对象一被初始化,就必须要明确被操作的文件。且该目录下如果已有同名文件,则同名文件被覆盖。其实就是在明确数据要存放的目的地。
(2)调用write(String s)方法,将字符串写入到流中。
(3)调用flush()方法,刷新该流的缓冲,将数据刷新到目的地中。
(4)调用close()方法,关闭流资源。但是关闭前会刷新一次内部的缓冲数据,并将数据刷新到目的地中。
close()和flush()区别:
flush()刷新后,流可以继续使用;close()刷新后,将会关闭流,不可再写入字符流。
如下:public static void main(String[] args)
{
FileWriter fw=null;
try {
//建立写入流对象,定义要操作的文件,如果文件已经存在,不覆盖,在其后面续写。
fw=new FileWriter("F:\\123.txt",true);
//写入数据
fw.write("你好,早安");
//刷新数据到指定文件
fw.flush();
} catch (IOException e) {
throw new RuntimeException("写入失败");
} //关闭流资源,因为一定要执行,所以定义在finally语句中
finally{
try {
if(fw!=null)
fw.close();
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException("关闭失败");
}
}
}
要注意几点:1.其实Java自身不能写入数据,而是调用系统内部方式完成数据的写入,使用系统资源后,一定要关闭资源。
2.文件的数据的续写是通过构造函数FileWriter(String s,boolean append),在创建对象时,传递一个true参数,代表不覆盖已存在的文件。并在已有文件的末尾处进行数据续写。(Windows系统中的文件内换行用\r\n完成,而Linux中用\n完成)
3.由于在创建对象时,需要指定创建文件位置,如果指定的位置不存在,就会发生IOException异常,所以在整个步骤中,需要对IO异常进行try处理。
2.读取字符流:
(1)创建一个文件读取流对象,和指定名称的文件相关联。要保证该文件已经存在,若不存在,将会发生FileNotFoundException异常。
(2)调用读取流对象的read方法。
第一种方式:read():一次读一个字符
第二种方式:read(char[] c):通过字符数组进行读取。
(3)读取后调用close方法关闭流资源。
如下:
public static void main(String[] args)
{
FileReader fr=null;
try {
//建立读取流对象,关联指定文件。
fr=new FileReader("F:\\123.txt");
//第一种:一个一个读取
int ch=0;
while((ch=fr.read())!=-1)
System.out.print((char)ch);
//第二种:通过数组进行读取
char[]arr=new char[1024];
int len=0;
while((len=fr.read(arr))!=-1)
System.out.print(new String(arr,0,len));
} catch (IOException e) {
// TODO: handle exception
throw new RuntimeException("读取失败");
} //关闭流资源,因为一定要执行,所以定义在finally语句中
finally{
try {
if(fr!=null)
fr.close();
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException("关闭失败");
}
}
}
下面通过字符流完成一个文件的拷贝:
public static void main(String[] args)
{
FileReader fr=null;
FileWriter fw=null;
try {
//建立流对象,关联指定文件。
fr=new FileReader("F:\\123.txt");
fw=new FileWriter("E:\\123.txt");
//读写操作
char[]arr=new char[1024];
int len=0;
while((len=fr.read(arr))!=-1)
fw.write(arr, 0, len);
} catch (IOException e) {
throw new RuntimeException("复制失败");
} //关闭流资源,因为一定要执行,所以定义在finally语句中
finally{
try {
if(fr!=null)
fr.close();
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException("读取流关闭失败");
}finally{
try {
if(fw!=null)
fw.close();
} catch (Exception e) {
throw new RuntimeException("写入流关闭失败");
}
}
}
}
字符流的缓冲区:
缓冲区的出现,提高了流的读写效率,在缓冲区创建前,要先创建流对象,即先将流对象初始化到构造函数中。缓冲区中封装了数组,将数据存入,再一次性取出。
读取流缓冲区BufferedReader中提供了按行读取的readLine方法,方便对文本数据的读取,当返回null时表示读到文件末尾。readLine方法返回的是回车符之前的文本数据内容,并不返回回车符。
而写入流缓冲区BufferedWriter中提供了一个跨平台的换行符:newLine();可以在不同操作系统上调用,用作对数据换行。
下面通过利用加入缓冲技术的流对象,来完成对文件的拷贝。如下:
public static void main(String[] args)
{
BufferedReader fr=null;
BufferedWriter fw=null;
try {
//建立流对象,关联指定文件,为了提高效率,加入缓冲技术
fr=new BufferedReader(new FileReader("F:\\123.txt"));
fw=new BufferedWriter(new FileWriter("E:\\123.txt"));
//通过缓冲区的 特有操作,实现内容复制
String line=null;
while((line=fr.readLine())!=null){
fw.write(line);
//换行
fw.newLine();
//为了数据的安全性,写一行刷新一次。
fw.flush();
}
} catch (IOException e) {
// TODO: handle exception
throw new RuntimeException("复制失败");
} //关闭流资源,因为一定要执行,所以定义在finally语句中
finally{
try {
if(fr!=null)
fr.close();
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException("读取流关闭失败");
}finally{
try {
if(fw!=null)
fw.close();
} catch (Exception e) {
// TODO: handle exception
throw new RuntimeException("写入流关闭失败");
}
}
}
}
readLine的原理:无论是读一行,或者读取多个字符。其实最终都是在在硬盘上一个一个读取。所以最终使用的还是read方法一个一个读。根据这个原理,自定义一个缓冲区。如下:
//定义一个缓冲区,继承Reader类
class MyBufferedReader extends Reader{
private Reader fr;
MyBufferedReader(Reader fr){
this.fr=fr;
}
//自定义读一行的方法。
public String myRead()throws IOException{
//定义一个临时容器,用来存储数据
StringBuilder sb=new StringBuilder();
int ch=0;
while((ch=fr.read())!=-1){
if(ch=='\r')
continue;
//碰到换行标识,返回该行数据
else if(ch=='\n')
return sb.toString();
sb.append((char)ch);
}
//判断容器中是否还有数据
if(sb.length()!=0)
return sb.toString();
return null;
}
//复写父类Read方法
public int read(char[] cbuf, int off, int len) throws IOException {
return fr.read(cbuf,off,len);
}
//复写关闭流方法
public void close() throws IOException{
fr.close();
}
}
这个自定义的类体现了Java设计模式中的一种常用模式:装修设计模式。当想对已有对象进行功能增强时,可定义一个类,将已有对象传入,基于已有对象的功能,并提供加强功能,自定义的类就是装饰类。装饰类通过构造方法接收被装饰的对象,并基于被修饰的对象的功能,提供加强的功能。
装饰和继承相比,装饰模式比继承更灵活,避免了继承体系的臃肿,降低了类与类之间的关系。装饰类因为增强已有对象,具备的功能和已有的是相同的,只不过提供了加强的功能,所以装饰类和被装饰的类通常都属于一个体系。从继承结构转为组合结构。在定义类时,不要以继承为主,可通过装饰设计模式进行增强类功能。灵活性强,当装饰类中的功能不适合时,可以再使用被装饰类的功能。
三、字节流
字节流和字符流的基本操作是相同的,但字节流除了可以操作文本文件之外,还可以操作媒体文件。由于媒体文件的数据都是以字节存储的,所以字节流对象可直接将媒体文件的数据写入到指定目的文件中,而不用进行刷新动作。
字节流不用刷新的原理:字节流操作的是字节,即数据的最小单位,不需要像字符流一样要进行转换,所以可直接将字节数据写入到指定目的文件中。
输入流InputStream中有一个available()方法,返回文件中的字节个数。可以使用此方法来指定读取方式中传入数组的长度,减少循环判断,但如果文件较大,虚拟机启动分配的默认内存有限,容易造成内存溢出。所以此方法谨慎使用。
下面通过字节流拷贝一个图片文件,如下:
public static void main(String[] args)
{
FileOutputStream fos = null;
FileInputStream fis = null;
try
{ //建立字节流对象,与文件相关联
fos = new FileOutputStream("F:\\1.JPG");
fis = new FileInputStream("E:\\1.JPG");
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 RuntimeException("写入关闭失败");
}
}
}
有个知识点要注意:在字节流中,read():会将字节byte型值提升为int型值
write(): 会将int类型转换为byte型,即保留二进制数的最后八位。
read() 返回值提升的原因:因为有可能会读到连续八个二进制1的情况,八个二进制1对应的十进制是-1,那么数据还没有读完就结束,因为-1是判断读取结束的结尾标记。为了避免这种情况就对读到的字节进行int类型的提升,在保留原字节数据的情况前面补24个0,变成int类型。可以通过将byte型数据&255获取。最后在write()中,会把int类型转换为byte型,保留了最后八位。
四、转换流
1.键盘录入:
标准输入输出流:
System.in:对应的标准输入设备,键盘。 System.in的类型是InputStream。
System.out:对应的是标准的输出设备,控制台。System.out的类型是PrintStream是OutputStream的子类FilterOutputStream的子类。
当使用输入流进行键盘录入时,只能一个字节一个字节进行录入。为了提高效率,可以自定义一个数组将一行字节进行存储。当一行录入完毕,再将一行数据进行显示。这种正行录入的方式,和字符流读一行数据的原理是一样的。也就是readLine方法。
那么能不能直接使用readLine方法来完成键盘录入的一行数据的读取呢?readLine方法是字符流BufferedReader类中方法。而键盘录入的read方法是字节流InputStream的方法。这时就可以利用到转换流了。
当字节流中的数据都是字符时,通过转换流来操作读写效率高。转换流是字符流和字节流之间的桥梁方便了字符流和字节流之间的操作。