------Java培训、Android培训、iOS培训、.Net培训、期待与您交流!-----
一、流概述和图表。
1、IO(Input/Output)概述:
流是一组有顺序的,有起点和终点的字节集合,是对数据传输的总称或抽象。即数据在两设备间的传输称为流,流的本质是数据传输,根据数据传输特性将流抽象为各种类,方便更直观的进行数据操作。 JAVA通过流的方式,对数据进行操作。操作流的对象 在java.io.*包中。
IO流的分类: A. 字符流、字节流
B. 输入流、输出流。
2、 IO知识框图:
类 | 注释 |
File | 文件类 |
RandomAccessFile | 随机存取文件类 |
InputStream | 字节输入流 |
OutputStream | 字节输出流 |
Reader | 字符输入流 |
Writer | 字符输出流 |
注:此图系转载。用以参考。
二、字符流。
1、简介:
Java 流在处理上分为字符流和字节流。字符流处理的单元为 2 个字节的 Unicode 字符,分别操作字符、字符数组或字符串,而字节流处理单元为 1 个字节,操作字节和字节数组。Java 内用 Unicode 编码存储字符,字符流处理类负责将外部的其他编码的字符流和 java 内 Unicode 字符流之间的转换。而类 InputStreamReader 和 OutputStreamWriter 处理字符流和字节流的转换。字符流(一次可以处理一个缓冲区)一次操作比字节流(一次一个字节)效率高。
2.字符流的读取和写入:
2.1字符流的写入步骤:
创建FileWriter对象,明确操作的文件。
调用对象.write();方法将“字符串”写入文件。
调用.close();方法刷新流,并且关闭流。
Tips:如果指定位置不存在会触发IOException需要进行异常处理。
<示例1>向文件“abc.txt”中续写内容:
import java.io.*; public class IoTest1 { public static void main(String[] args) { //创建字符流对象fw,“abc.txt”为所要操作的文件 FileWriter fw = null; try { //写入字符 fw = new FileWriter("D:\\abc.txt",true); fw.write("从前有座山\r\n山里有座庙"); } catch(IOException e) { System.out.println(e.toString()); } finally { try{ if(fw != null) //刷新流 fw.flush(); } catch(IOException e) { System.out.println(e.toString()); } } } }
2.2字符流的读取步骤:
两种方式:单个读取,利用数组存取。
创建FileReader对象并指定要操作的文件,(文件不存在则会触发FileNotFoundException异常);
调用FileReader对象的.read();方法,读取单个字符,可结合循环操作
.close();关闭资源。
<示例2>
import java.io.*; public class IoTest1 { public static void main(String[] args) { FileReader fr = null; try { //创建字符流读取对象,指定所要操作的文件 fr = new FileReader("d:\\abc.txt"); int i = 0; //循环读取字符的编码值并赋予变量i while(( i = fr.read())!= -1) { //强制转换i为char型并打印 System.out.println((char)fr.read()); } } catch(IOException e) { e.printStackTrace(); } finally { try { fr.close(); } catch(IOException e) { e.printStackTrace(); } }
//字符数组 读取 import java.io.*; public class IoTest1 { public static void main(String[] args) { FileReader fr = null; char[] ch = new char [12]; try { //创建字符流读取对象,指定所要操作的文件 fr = new FileReader("d:\\abc.txt"); int i = 0; //循环读取字符的编码值并赋予变量i while(( i = fr.read(ch))!= -1) { //强制转换i为char型并打印 System.out.println("个数="+i+new String(ch)); } } catch(IOException e) { e.printStackTrace(); } finally { try { fr.close(); } catch(IOException e) { e.printStackTrace(); } } } }
2.3 小练习:
字符流输入输出综合运用:文件拷贝。
/*在C盘创建一个文件,用于D存储盘文件中的数据。 * 定义读取流和C盘文件关联。 * 通过不端读写完成数据存取。 * 关闭资源 */ import java.io.*; public class IoTest1 { public static void main(String[] args) { copyFile(); } public static void copyFile() { //创建字符输入输出流对象 FileReader fr = null; FileWriter fw = null; try{ fr = new FileReader("d:\\abc.txt");//关联文件 fw = new FileWriter("c:\\abc_copy.txt");//关联文件 int i = 0; char[] temp = new char[1024]; //定义存放读取文件的字符数组 while((i = fr.read(temp))!= -1) { fw.write(temp,0,i); } }catch(IOException e) { throw new RuntimeException("读写失败"); } finally { try { if(fr!=null) fr.close(); } catch(IOException e) { e.printStackTrace(); } finally { try{ if(fw!=null) fw.close(); }catch(IOException e){} } } } }
3、字符流的缓冲区
缓冲区的出现提高了对数据的读写效率。
对应类:BufferedReader和BufferedWriter。
在流的基础上对流的功能进行了增强。
缓冲区需要结合流才可以使用。
步骤:以BufferedWriter为例:1、创建字符写入流对象。2、创建缓冲区对象,并将字符流对象作为缓冲区构造函数的参数。3、bw.newLine();缓冲区的newLine()方法可以提供 换行功能。4、bw.flush.只要用到缓冲区就要刷新。5、bw.close();关闭缓冲区(同时关闭了流对象)。
个人理解:如果把字符流理解为 水流,则缓冲区可以理解位一个装水的容器。 因此缓冲区操作的数据类型是 String.
对比: FileWriter fw = new FileWriter();
BufferedWriter bwfw = new BufferedWriter(fw);
fw.read();
bwfw.readLine();
int i = 0; while((i = fw.read())! = -1))
String s = null; while((s = bwfw.readLine()) != null ) //缓冲区一次可以读一行。
<示例3>通过缓冲区复制文件,对比字符流复制文件。
/* * 通过缓冲区复制文件。 */ import java.io.*; public class IoTest1 { public static void main(String[] args)throws IOException { BufferedReader br = null; BufferedWriter bw = null; String s = null; try { br = new BufferedReader(new FileReader("D:\\abcd.txt"));//创建缓冲区读取对象,关联文件 bw = new BufferedWriter(new FileWriter("C:\\abcd_copy.txt"));//创建缓冲区写入对象,关联文件 while((s = br.readLine())!= null) { //判断文件是否还留有文本 bw.write(s); //将读取的字符串写入目标文件 bw.flush(); //刷入文件 } } catch(IOException e) { throw new RuntimeException("erro"); } finally { try { if(br != null) br.close(); } catch(IOException e) { throw new RuntimeException("erro"); } finally { try { if(bw != null) bw.close(); } catch(IOException e) { throw new RuntimeException("erro"); } } } } }
3.1自定义Buffered
如果把字符流理解为 水流,则缓冲区可以理解位一个装水的容器。 在此理解上可以自定义Buffered。
<示例4>
/* * 自定义Buffered */ import java.io.*; public class IoTest1 { public static void main(String[] args) { } } class MyBufferedReader { private FileReader fr; MyBufferedReader(FileReader fr) { //构造函数(参数类型:FileReader) this.fr = fr; } public String myReadLine () throws IOException { // 自定义readLine方法,返回一个字符串 //利用StringBuilder容器存储读取的字符串 StringBuilder sb = new StringBuilder(); int i = 0; while((i = fr.read()) != -1) { if(i == '\r') continue; if(i == '\n') //循环到换行符时返回String字符串。 return sb.toString(); else sb.append((char)i); } return null; } public void myClose() throws IOException { fr.close(); } }
3.2 装饰设计模式:
以上的例子中,MyBufferedReader是FileReader的一个增强类,这种设计模式被称作是装饰设计模式。
当相要对已有的对象进行功能增强时,基于已有的功能,提供扩展功能,这个自定义的类成为装饰类。
装饰类通常通过构造方法接受被装饰的类。
例如
class Person {
public void chifan() {
sop("吃馒头");
}
}
class SuperPerson {
SuperPerson(Person p) {
this.p = p;
}
扩展......
public void superChifan() {
p.chifan();
sop("吃东坡肉");
sop("吃烧鸡");
}
}
装饰类和继承的区别:
BufferedReader bf = new BufferReader(FileReader fr);//这就是一个典型的装饰类例子。可以传递(各种Reader参数)。
装饰类通过多态的形式极大的提高了扩展性。优化了体系结构。
1)比继承灵活,避免了继承体系结构的臃肿(不必包含被装饰类的所有成员属性和方法)。
2)装饰类具备的功能和已有的功能是相同的,提供了更强的功能。所以装饰类和被装饰类一般都属于一个体系。
3)继承结构转为组合结构。
4.LineNumberReader extends BufferedReader:
LineNumberReader是BufferedReader的一个子类,扩展了功能,提供了以下扩展方法:
1)getLine();//获取行号
2)setLine();//设置行号(从N行开始)
<示例5> MyLineNumberReader
class MyLineNumberReader { private FileReader fr; private int lineNumber; MyLineNumberReader(FileReader fr) { //构造函数(参数类型:FileReader) this.fr = fr; } public String myReadLine () throws IOException { // 自定义readLine方法,返回一个字符串 lineNumber++; //利用StringBuilder容器存储读取的字符串 StringBuilder sb = new StringBuilder(); int i = 0; while((i = fr.read()) != -1) { if(i == '\r') continue; if(i == '\n') //循环到换行符时返回String字符串。 return sb.toString(); else sb.append((char)i); } if(sb.length() != 0) //如果最后一个字符不是换行符的情况 return sb.toString(); else return null; } public void myClose() throws IOException { fr.close(); } public int getLineNumber() { //获取行号方法 return lineNumber; } public void setLineNumber(int n) { //设置行号方法 lineNumber = n; } }
三、字节流。
1、字节流的特点
1)相关类:InputStream/OutputStream
2)字节流操作的是字节Byte型数据,写入和读取的都是字节,所以在写入时候,要以字节的形式写入。例如:FileOutputStream fos = new.....; fos.write("abcd".getBytes();)
3)int available方法:可以理解为获取文件的字节总数(文件大小)
FileInputStream fis = new FileInputStream("abc.txt");
byte[] temp = new byte[fis.available()];
fis.read(buf); //把abc.txt中的所有字节写入temp数组中,大小刚好为文件的字节总数。可以不再写循环。
注意,这个方法容易引起数据溢出,一般还是用byte[] temp = byte[1024]; (1024bytes = 1KB ;1024*1024bytes = 1MB)
<示例6> 拷贝一个图片,太6了!
/* * COPY图片! */ import java.io.*; public class IoTest1 { public static void main(String[] args) { FileInputStream fis = null; FileOutputStream fos = null; byte[] temp = null; try{ fis = new FileInputStream("F:\\a.jpg"); //建立字节输入流对象,关联文件 fos = new FileOutputStream("c:\\b.jpg"); //字节输出流对象,关联文件 temp = new byte[fis.available()]; //定义字节数组,大小为a.jpg文件的字节数 fis.read(temp); fos.write(temp); } catch(IOException e) { new RuntimeException("erro"); } finally { try { fis.close(); }catch (IOException e) { new RuntimeException("erro"); } finally { try { fos.close(); }catch (IOException e) { new RuntimeException("erro"); } } } } }
2、字节流的缓冲区 : BufferedInputStream/BufferedOutputStream
用法示例:BufferedInputStream bis = new BuffferedInputStream(new FileInputStrem(“C:\\爱要克制.mp3”))
字节流的缓冲区的特点:
read()方法:虽然字节流操作的数据是Byte(8bits),但是BufferedInputStream的read方法返回的是int型的数据,32bits
原因,在“拷贝MP3文件”的例子中,如果遇到字节数据是11111111时,即使转换为int型数据,高位补1后返回值仍为-1,因此需要&255
(&0000000 000000000 00000000 11111111),高位补0
write()方法则将转换后的int型数据 保留低8位写入。
对缓冲区的几点个人理解:
缓冲区相比普通的流存取,由于在内存中划分了缓冲的区域,所以使得存取的效率提高。
read(byte[] b)我认为也是一种缓冲方式。
Buffered中的read()方法,应该也是以数组的方式在内存中划分了区域,
因此,是否BufferedInputStream中的read()方法 的原理 其实是 FileInputStream类的read(Byte[] b)方法呢?
<示例7> 自定义字节流缓冲区 MyBufferedInputStream
/* 自定义字节流读取缓冲区 思路: */ import java.io.*; class MyBufferedInputStream { privateInputStream in; privatebyte[] by=new byte[2048]; privateint count=0,pos=0; MyBufferedInputStream(InputStreamin) { this.in=in; } public int myRead()throws IOException { //通过in对象读取硬盘上数据,并存储by中。 //存储在数组中的数据被读取完,再通过in对象从硬盘上读取数据 if(count==0) { count=in.read(by); if(count<0) return -1; pos=0;//初始化指针 byteb=by[pos]; count--;//每被读一个字节,表示数组中的字节数少一个 pos++;//指针加1 returnb&255;//返回的byte类型提升为int类型,字节数增加,且高24位被补1,原字节数据改变。 //通过与上255,主动将byte类型提升为int类型,将高24位补0,原字节数据不变。 //而在输出字节流写入数据时,只写该int类型数据的最低8位。 } elseif(count>0)//如果数组中的数据没被读取完,则继续读取 { byteb=by[pos]; count--; pos++; returnb&0xff; } return-1; } //自定义关闭资源方法 publicvoid close()throws IOException { in.close(); } } //测试自定义输入字节流缓冲区 class MyBufferedCopyMp3 { publicstatic void main(String[] args) { longstart=System.currentTimeMillis(); //利用字节流的缓冲区进行复制 copy_2(); longend=System.currentTimeMillis(); System.out.println("复制共用时:"+(end-start)+"毫秒"); } //使用字节流的缓冲区进行复制 publicstatic void copy_2() { BufferedOutputStreambout=null; MyBufferedInputStreambin=null; try { //关联复制文件输入流对象到缓冲区 bin=newMyBufferedInputStream(new FileInputStream("C:/Users/asus/Desktop/一样的月光.mp3")); //指定文件粘贴位置的输出流对象到缓冲区 bout=newBufferedOutputStream(new FileOutputStream("C:/Users/asus/Desktop/一样的月光2.mp3")); intby=0; while((by=bin.myRead())!=-1) { bout.write(by);//将缓冲区中的数据写入指定文件中 } } catch(IOException e) { thrownew RuntimeException("MP3复制失败"); } finally { try { if(bin!=null) bin.close();//关闭输入字节流 } catch(IOException e) { thrownew RuntimeException("读取字节流关闭失败"); } try { if(bout!=null) bout.close();//关闭输出字节流 } catch(IOException e) { thrownew RuntimeException("写入字节流关闭失败"); } } } }