------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! -------
IO流:
用来处理设备之间的数据传输,Java对数据的操作时通过流的方式。Java用于操作流的对象都是在IO包中。
输出流和输入流相对与内存设备(可以理解为流对象本身,数据进入流为输入流,数据传出流为输出流)。
Java提供了很多的流对象,对应不同的流操作,本意是好的,老程序员都能很好的调用合适的流对象为自己搭建代码,但是对于初学者来说往往是痛苦的,
FileInputStream/FileOutputStream/FileReader/FileWriter就够让人觉得奔溃了。为什么仅仅是对文件的操作就要分这么细致呢?何况这对于IO流体系只是冰山一角。
JavaIO体系看似庞大复杂,其实还是有规律的,我们来了解一下结构:
1. 其对称性质:InputStream 与 OutputStream, Reader与 Writer,他们分别是一套字节输入-输出,字符输入-输出体系
2. 原始处理器(适配器)与链接流处理器(装饰器)
字节流体系:
字符流体系:
我们仔细查看上述的两张图片,就能看出其实还是基本上能一一对应的。我们可以根据流对象的前缀来判断流的功能,我们先简单使用一下,看看是不是这样:
需求:将一些文字存储到硬盘一个文件中。
记住:如果要操作文字数据,建议优先考虑字符流。
而且要将数据从内存写到硬盘上,要使用字符流中的输出流:Writer。
硬盘的数据基本体现是文件,希望找到一个可以操作文件的Writer:FileWriter。
示例:
package com.leaf.iotest;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterDemo {
public static void main(String[] args) throws IOException {
String path = "d:\\dome.txt";
//创建一个FileWriter对象,传入一个路径
FileWriter fw = new FileWriter(path);
//使用FileWriter时,假如路径下没有文件会自动创建,如何依据有该文件,则会覆盖
//使用write来吧数据写入到流中
fw.write("haha,wo lai le");
//把流中的数据刷新出去,注:字符流才需要
fw.flush();
//关闭流
fw.close();
}
}
result:
注:1.因为流对象调用的系统的资源,在使用完毕后一定要关闭。
2.在FileWriter的构造函数中,添加一个参数true可以在创建文件的时候进行续写,而不是覆盖。即:FileWriter fw = new FileWriter(path,true);
IO的异常处理方式(所有涉及IO的方法写法都是大同小异的):
示例:
package com.leaf.iotest;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterDemo {
public static void main(String[] args) {
String path = "d:\\dome.txt";
FileWriter fw = null;
try {
fw = new FileWriter(path);
fw.write("haha,wo lai le");
fw.flush();
} catch (IOException e) {
System.out.println("我在处理异常");
}finally{
if(fw!= null)
try {
fw.close();
} catch (IOException e) {
System.out.println("关闭流异常");
}finally{
fw = null;
}
}
}
}
需求:读取一个文件,将读取的字符打印到控制台;
分析:来源:文件
目的:控制台
示例:
package com.leaf.iotest;
import java.io.FileReader;
import java.io.IOException;
public class FileReaderDemo {
public static void main(String[] args) {
String path = "d:\\dome.txt";
FileReader fr = null;
try {
fr = new FileReader(path);
int ch = 0;
while((ch = fr.read())!=-1){
System.out.println((char)ch);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
if(fr!= null)
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}finally{
fr = null;
}
}
}
}
result:
查看结果,read方法是一个个字节在读取。
那有更高效的方法吗?查看api int read(char[]);
示例:
package com.leaf.iotest;
import java.io.FileReader;
import java.io.IOException;
public class FileReaderDemo {
public static void main(String[] args) {
String path = "d:\\dome.txt";
FileReader fr = null;
try {
fr = new FileReader(path);
char[] chs = new char[1024];
int len = 0;
while((len = fr.read(chs))!=-1){
System.out.println(new String(chs,0,len));
}
} catch (Exception e) {
e.printStackTrace();
}finally{
if(fr!= null)
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}finally{
fr = null;
}
}
}
}
result:
经过上面的俩个类的使用,我们把上述两个类进行结合。
需求:将d盘下的demo.txt复制到e盘下,名字叫做demo_copy.txt
分析:
操作:字符
来源:文件 FileReader
目的:文件 FileWriter
示例:
package com.leaf.iotest;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class CopyFileDemo {
public static void main(String[] args) throws IOException {
String path_src = "d:\\dome.txt";
String path_goal = "e:\\File\\demo_copy.txt";
//创建文件的读取和输出流
FileReader fr = new FileReader(path_src);
FileWriter fw = new FileWriter(path_goal);
int len = 0;
char[] chs = new char[1024];
while ((len = fr.read(chs))!= -1) {
fw.write(chs,0,len);
}
//记得关闭流0
fw.close();
fr.close();
}
}
result:
运行前
运行后:
字符流缓冲区
缓冲区的出现提高了对数据的读写效率
对应类:
BufferedWriter BufferedReader
作用:在流的基础上对流的功能的、进行了增强
示例:能提高写入的效率
package com.leaf.iotest;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedDemo {
public static void main(String[] args) throws IOException {
String path_src = "d:\\dome.txt";
String path_goal = "e:\\File\\demo_copy.txt";
//创建文件的读取和输出流
//为了提高读取文件的效率,使用读取文件的字符流缓冲区
FileReader fr = new FileReader(path_src);
BufferedReader bufr = new BufferedReader(fr);
//为了提高写入文件的效率,使用写入文件的字符流缓冲区
FileWriter fw = new FileWriter(path_goal);
BufferedWriter bufw = new BufferedWriter(fw);
String buf = null;
while((buf = bufr.readLine())!= null){
bufw.write(buf);
}
bufr.close();
bufw.close();
}
}
result:
运行前:
运行后:
装饰设计模式:
对原有的类进行了功能的改变,增强。
示例;
package com.leaf.iotest;
public class PersonDemo {
public static void main(String[] args) {
Person p = new Person();
NewPerson np = new NewPerson(p);
np.chifan();
System.out.println("-----------");
NewPerson2 np2 = new NewPerson2();
np2.chifan();
}
}
/**
* 采用装饰类的方式对类的功能进行增强
* @author Administrator
*
*/
class NewPerson{
private Person p;
public NewPerson(Person p) {
this.p = p;
}
public void chifan(){
System.out.println("开胃酒");
p.chifan();
System.out.println("来一根");
}
}
/**
* 采用继承的方式对类的功能进行增强
* @author Administrator
*
*/
class NewPerson2 extends Person{
public void chifan(){
System.out.println("开胃酒");
super.chifan();
System.out.println("来一根");
}
}
class Person{
public void chifan() {
System.out.println("吃饭!");
}
}
result:
继承和装饰都能对类的功能进行拓展,但是他们还是很大的区别的。
继承体系:会因为对功能的拓展会越来越臃肿,今天添加要添加一个功能,明天要添加另一个不相关的功能,其中一个子类有了这个功能,那其他相似的子类呢?这 样一级级往下加,继承体系就会十分臃肿,而且实现起来很难。而装饰类,只要将需要进行装饰的类传进装饰类中,这样就不必一级级的对每个子类进行实现增强的子类。
好处:装饰类比继承灵活
一般情况下:装饰类和被装饰类都必须所属同一个接口或者父类。
IO常用字节流
字符流只能操作字符,如果操作了图片信息,很可能就让图片不能进行显示。而字节流就能操作所以文件。
示例:
package com.leaf.iotest;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ByteStreamDemo {
public static void main(String[] args) throws IOException {
String path_src = "d:\\1.png";
String path_goal = "e:\\File\\1_copy.png";
FileInputStream fis = new FileInputStream(path_src);
FileOutputStream fos = new FileOutputStream(path_goal);
byte[] buf = new byte[1024];
int len = 0;
while((len = fis.read(buf))!= -1){
fos.write(buf, 0, len);
}
fis.close();
fos.close();
}
}
result:
运行前
运行后:
在字节流中可以不用flush,字符流要进行刷新是因为字符流在加载数据到内存中时,数据会进行编码,所以数据会在内存中,必须刷新出去,而字节流两边操作的都是字节,不行进行编码,直接传输就好。
字节流也要缓冲区,和字符流的用法一样。这里不进行演示。
需求:读取一个键盘录入的数据,并打印在控制台上。
分析:键盘本身就是一个标准的输入设备,对于java来说,这种输入设备都有对应的对象。
package com.leaf.iotest;
import java.io.IOException;
import java.io.InputStream;
public class ReaderFromKey {
public static void main(String[] args) throws IOException {
readFromKey();
}
public static void readFromKey() throws IOException{
InputStream in = System.in;
int ch = in.read();//阻塞方法
System.out.println(ch);
ch = in.read();//阻塞方法
System.out.println(ch);
ch = in.read();//阻塞方法
System.out.println(ch);
in.close();
}
}
result:
ps.
1. 获取键盘录入数据,然后将数据流向显示器,那么显示器就是目的地。
通过System类的setIn和setOut方法可以对默认设备进行改变。
System.setIn(newFileInputStream(“1.txt”));//将源改成文件1.txt;
System.setOut(newPrintStream(“2.txt”));//将目的改成文件2.txt;
因为是字节流处理的是文本数据,可以转换成字符流,操作更方便。
BufferedReaderbufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriterbufw = new BufferedWriter(new OutputStreamWriter(System.out));
2. 默认的输入和输出系统不需要关,他会随着系统的结束而消失。
示例:
获取用户录入的数据并将数据变成大写显示在控制台上,如果用户输入的是over,结束键盘录入。
思路:
1. 因为键盘录入只读取一个字节,要判断是否是over,需要将读取的字节拼成字符串。
2. 需要一个容器:StringBuilder
3. 在用户回城之前将录入的数据变成字符串判断即可。
package com.leaf.iotest;
import java.io.IOException;
import java.io.InputStream;
public class ReaderFromKey {
public static void main(String[] args) throws IOException {
readFromKey();
}
public static void readFromKey() throws IOException{
StringBuilder sb = new StringBuilder();
InputStream in = System.in;
int ch =0;
while((ch = in.read())!=-1){
//判断标记
if(ch == '\r')
continue;
if(ch == '\n'){
String temp = sb.toString();
if("over".equals(temp))
break;
System.out.println(temp.toUpperCase());
sb.delete(0, sb.length());
}else{
//将读取的数据存入StringBuilder
sb.append((char)ch);
}
}
}
}
result:
重点:转换流
由来:字符流和字节流之间的桥梁,方便字符流与字节流之间的操作。
应用:字节流的数据都是字符时,转换成字节流操作更高效。
转换流:
InputStreamReader:字节到字符的桥梁,解码。
OutputStreamWriter:字符到字节的桥梁,编码。
用转换流实现上面的示例:
package com.leaf.iotest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class TranStreamDemo {
public static void main(String[] args) throws IOException {
//字节流
InputStream in = System.in;
//
InputStreamReader isr = new InputStreamReader(in);
BufferedReader bufr = new BufferedReader(isr);
String line = null;
while((line = bufr.readLine())!=null){
if("over".equals(line))
break;
System.out.println(line.toUpperCase());
}
}
}
result:
ps
OutputStreamWriter这里不做演示,有兴趣的朋友可以自己设计一段代码,和上述代码相似。
转换流的综合应用:
package com.leaf.iotest;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class TranStreamDemo {
public static void main(String[] args) throws IOException {
BufferedReader bufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(System.out));
String line = null;
while((line = bufr.readLine())!=null){
if("over".equals(line))
break;
bufw.write(line.toUpperCase());
bufw.newLine();
bufw.flush();
}
}
}
result:
需求:将键盘录入的数据写入到一个文件中。
思路:
操作对象:字符,但是键盘读取的是System.in为字节流
源:键盘:使用缓冲流和转换流
目的:文件:FileOutputStream 转换流 缓冲流
package com.leaf.iotest;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class TranStreamDemo1 {
public static void main(String[] args) throws IOException {
BufferedReader bufr = new BufferedReader(new InputStreamReader(
System.in));
BufferedWriter bufw = new BufferedWriter(new OutputStreamWriter(
new FileOutputStream("E:\\File\\1.txt")));
String line = null;
while ((line = bufr.readLine()) != null) {
bufw.write(line);
bufw.newLine();
bufw.flush();
}
bufr.close();
bufw.close();
}
}
result:
其他的读取写入方法大同小异,那么可以看出什么规律吗?
流的操作规律:
我们在一开始就说过,java中的流对象太多了,又有很多为了增强功能进行设计的装饰类,开发的时候很容易就因此而头昏眼花,其实我们只要能按照以下的思路进行判断,流其实还是不难的。
1. 明确源和目的
源:InputStream Reader
目的:OutputStreamWriter
2. 明确数据是否为纯文本数据
源:
是纯文本:Reader
否:InputStream
目的:
是纯文本:writer
否:OutputStream
3. 明确具体的设备
源设备:
硬盘:File
键盘:System.in
内存:数组Array
网络:Socket
目的设备:
硬盘:File
控制台:System.out
内存:数组Array
网络:Socket
4. 是否需要其他额外的功能
是否需要高效的读取和写入(缓冲区),BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream。
我们使用上面的思路进行设计程序
需求:复制一个文本文件
1、 明确源和目的。
源:InputStream Reader
目的:OutputStream Writer
2、 纯文本?
是!
源:reader
目的:writer
3、 明确具体设备
源:
硬盘:File
目的:
硬盘:File
FileReader fr =new FileReader(“1.txt”);
FileWriter fw =new FileWriter(“1_copy.txt”);
4、需要额外功能吗?
需要
BufferedReaderbufr = new BufferedReader(new FileReader(“1.txt”));
BufferedWriterbufw = new BufferedWriter(new FileReader(“1_copy.txt”));
需求:读取键盘录入的信息,并写道一个文件中
1、 明确源和目的。
源:InputStream Reader
目的:OutputStreamWriter
2、 纯文本?
是!
源:reader
目的:writer
3、 明确具体设备
源:
键盘:System.in
目的:
硬盘:File
FileReader fr =new System.in;
FileWriter fw =new FileWriter(“1_copy.txt”);
4、需要额外功能吗?
需要
转换。将字节流转换成字符流
InputStreamReaderisr = new InputStreamReader(System.in);
FileWriter fw =new FileWriter(1_copy.txt);
还需要其他功能吗?
BufferedReaderbufr = new BufferedReader(new InputStreamReader(System.in));
BufferedWriterbufw = new BufferedWriter(new FileWriter(1_copy.txt));
需求:将一个文本文件的数据显示在控制台上
1、 明确源和目的。
源:InputStream Reader
目的:OutputStreamWriter
2、 纯文本?
是!
源:reader
目的:writer
3、 明确具体设备
源:
硬盘:File
目的:
控制台:System.in
FileReader fr =new FileReader(“1.txt”);
FileWriter fw = System.in;//PrintStream;
4、需要额外功能吗?
需要
转换。将字节流转换成字符流
FileReader fr =new FileLLReader(“1.txt”);
OutputStreamWirterosw = new OutputStreamWriter(System.in);
还需要其他功能吗?
BufferedReaderbufr = new BufferedReader(new FileReader(“1.txt”));
BufferedWriterbufw = new BufferedWriter(new OutputStreamWriter(System.out));
流和编码:
任何java识别的字符数据使用的都是Unicode码表,但是FileWriter写入本地文件使用的是本地编码,GBK。
而OutputStreamWriter可使用指定的编码将要写入流中的字符编码成字节。
示例:
package com.leaf.iotest;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
public class TranStreamDemo2 {
public static void main(String[] args) throws IOException {
//我们可以在转换流中定义编码表
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("e:\\File\\2.txt"),"UTF-8");
osw.write("我来了!");
osw.close();
}
}
result:
运行前:
运行后:
ps:UTF-8编码,一个中文三个字节,编码解码的问题这里不做详细介绍,需要了解的朋友可以到网上去看。
读取刚刚2.txt文件中的数据:
import java.io.FileReader;
import java.io.IOException;
public class TranStreamDemo2 {
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("E:\\File\\2.txt");
char[] buf = new char[1024];
int len = 0;
while((len = fr.read(buf))!=-1){
System.out.println(new String(buf,0,len));
}
fr.close();
}
}
//我把Eclipse的编码改成utf-8的了,这里使用dos命令行来演示(默认编码为GBK)
从结果可以看出,读取的字符变成了乱码,和使用utf-8读取的结果不同。