IO流体系简介
IO流顾名思义Input&Output,IO体系的建立依据四个基类:
①InputStream②OutputStream
③Reader
④Writer
IO流有三类分类标准:
1.流向分类,分为:输入流和输出流
2.传输单位,分为:字节流(8位)和字符流(16位)
3.作用角色,分为:处理流和节点流
根据这样的分类标准,我们可以看出如果根据标准1,那么①③为输入流,②④为输出流
根据标准2,那么①②分为字节流,②④分为字符流
根据标准3,暂且无法分类
注:节点流指的是直接作用于文件的四个类(FileInputStream,FileOutputStream,FileReader,FileWriter)
处理流就是用来包装节点流对象的,并且在这个基础上添加些增加效率的功能。
所以IO体系之复杂,我们必须有着自己的清晰的思路:
①首先定向输入输出是从程序的角度出发的;
②其次根据需求,如果需要复制纯文本文件那么选择字符流,其效率要远远高于字节流;如果需要复制非文本文件那么选择字节流
③如果要提升效率我们一般选择缓冲流(处理流)。
在认识这个体系之前有两个非常重要的类需要说一下:File类(文件的创建和删除以及文件属性的显示)和RandomAccessFile类(功能最全的流,可以完成输入和输出)
File类:
/**
* java中对于文件的定义分为:文件和目录
* @throws IOException
*/
@Test
public void testFile() throws IOException{
System.out.println("请输入您要操作的盘符:");
Scanner scanner = new Scanner(System.in);
//读取盘符
String disk=scanner.nextLine();
File file=new File(disk);
//访问文件名
File[] listTable = file.listFiles();
// listTable[10].renameTo(new File("hello111.txt"));
for(int i=0;i<listTable.length;i++){
// System.out.println(i+1+" "+listTable[i].getCanonicalPath());
// System.out.println(i+1+" "+listTable[i].getParent());
// System.out.println(i+1+" "+listTable[i].getPath());
// System.out.println(i+1+" "+listTable[i].getAbsolutePath());
// System.out.println(i+1+" "+listTable[i].getName());
}
//文件检测
System.out.println(listTable[10].isFile());//是否为一个真正的文件
System.out.println(listTable[10].isDirectory());//是否为一个文件目录
System.out.println(listTable[10].isHidden());//是否被隐藏
System.out.println(listTable[10].isAbsolute());//
System.out.println(listTable[10].canExecute());//可执行?
System.out.println(listTable[10].canRead());//可读?
System.out.println(listTable[10].canWrite());//可写?
//常规信息
System.out.println(new Date(listTable[10].lastModified()));
System.out.println(listTable[10].length());
//文件操作
File test=new File("E:/helloworld!!!");
test.createNewFile();
// 在指定目录下创建文件
File.createTempFile("123", ".java", listTable[10]);
//删除文件
test.delete();
//目录操作
test.mkdir();
}
RandomAccessFile类:
/**
* 从多行文本的指定位置插入字符串,不是覆盖
* RandomAccessFile既可充当输入流又可充当输出流,只要在构造器重设置下mode即可
* "r" 以只读方式打开。调用结果对象的任何 write 方法都将导致抛出 IOException。
"rw" 打开以便读取和写入。如果该文件尚不存在,则尝试创建该文件。
"rws" 打开以便读取和写入,对于 "rw",还要求对文件的内容或元数据的每个更新都同步写入到底层存储设备。
"rwd" 打开以便读取和写入,对于 "rw",还要求对文件内容的每个更新都同步写入到底层存储设备。
*/
@Test
public void testRandomAccessFile(){
RandomAccessFile in = null;
try {
in = new RandomAccessFile(new File("hello.txt"), "rw");
/**
* 移动指针位置
*/
in.seek(4);
int len=0;
byte[] content=new byte[1024];
//先将当前位置之后的字符全部保存
StringBuffer sb=new StringBuffer();
while((len=in.read(content))!=-1){
sb.append(new String(content,0,len));
}
//重新找回原先的位置,进行覆盖
in.seek(4);
in.write("Mr凡先生好帅!!!".getBytes());
in.write(sb.toString().getBytes());
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(in!=null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
下面来介绍下IO流这个体系:
实际上分类标准1和标准2可以让我们清晰的选择流。但是标准3可以更加清晰的学习流的作用。我们从标准3切入:
(重点)首先来介绍下节点流
(FileInputStream,FileOutputStream,FileReader,FileWriter)
还有比较重要的一种处理流-缓冲流,懂得通过他们来完成文件的复制功能就可以有个大致的掌握了。
然后我们来介绍下转换流的作用,和用法
对象流在序列化和反序列化方面的应用
打印流对于重定向的输入和输出。
大致的轮廓就清晰了!
节点流
1.FileInputStream和FileOutputStream
// 注意点:
// 1.FileInputStream的pathName一定要存在,否则会报FileNotfound异常
// 2.read():首先指针在文件内容前一个,当调用这个方法的时候文件指针会先向后移一个字节
// 然后读出这个字节的值,但是此时带来了一个问题:①中文怎么办???②为什么循环需要有b=fis.read()(就是图中写法二的问题在哪儿 )???
// 3.read(byte[] b)提升了效率,并且解决了中文输出的问题,对于问题②在这里也是作为字符串的一种截取。但是read()不需要截取啊!!!
// 所以问题②还没有实质性的解决
@Test
public void testFileInputStream() {
// 定义变量方便在try catch 和finally块中使用
FileInputStream fis = null;
try {
/* ======================read()========================= */
// int b;
// while((b=fis.read())!=-1){
// System.out.print((char)b);
// }
/**
* 写法二:无法进行读取为什么?
*/
// while(fis.read()!=-1){
// System.out.print((char)fis.read());
// }
/* ======================read()========================= */
/* ===================read(byte[] b)==================== */
/*
* fis = new FileInputStream("F:\\helloWorld.txt"); //读取文件了内容 byte[]
* content=new byte[1024]; fis.read(content); System.out.println(new
* String(content));
*/
fis = new FileInputStream("F:\\helloWorld.txt");
// 读取文件了内容
byte[] content = new byte[5];
int len = 0;
while ((len = fis.read(content)) != -1) {
// 错误写法①
/*
* for(byte b:content){ System.out.print((char)b); }
*/
// 错误写法②
/*
* for(int i=0;i<content.length;i++){
* System.out.print((char)content[i]); }
*/
// 错误写法③
// System.out.print(new String(content));
// 错误原因分析:由前面代码可知,缓冲区为5,文件内容为123456789,那么当文件第二次读取时
// 只有前四位被覆盖了,而最后一位没有被覆盖,所以如果讲第二次的数组全部答应出来是肯定会出现问题的:1234567895
// 方法一
// System.out.print(new String(content,0,len));
// 方法二
for (int i = 0; i < len; i++) {
System.out.print((char) content[i]);
}
}
/* ===================read(byte[] b)==================== */
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 注意点: FileOutputStream(String
* pathName)中的文件不存在时,会自动创建,若无法正常创建时会报出FileNotfound异常
* 如果pathName是一个文件,或者是一个已经存在但是无法正常打开那么也会FileNotfound异常
* 如果文件已存在并且能够被正常打开,那么当往文件里面写东西的时候会将原本的内容完全覆盖
* 。如果想让文件不被覆盖,需要使用RandomAccessFile类
*/
@Test
public void testFileOutputStream() {
FileOutputStream fos = null;
try {
fos = new FileOutputStream("F:/");
int len = 0;
byte[] content = "我是李刚我怕谁!".getBytes();
fos.write(content);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
重要练习:复制文件
/**
* 将文件从一个文件复制到另一个文件夹下
* 通过FileInputStream将文件内容读入并通过FileOutputStream将读取的内容输入指定的文件
*/
@Test
public void testCopy() {
String src = "F:\\helloWorld.txt";
String des = "";
}
public void copyFile(String src, String des) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(src);
fos = new FileOutputStream(des);
byte[] content = new byte[1024];
int len = 0;
while ((len = fis.read(content)) != -1) {
fos.write(content, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.FileReader和FileWriter
/**
*
* 复制文本文件
*/
@Test
public void copyByChar() {
FileReader fr = null;
FileWriter fw = null;
try {
fr = new FileReader("F:\\helloWorld.txt");
fw = new FileWriter("test.txt");
char[] content = new char[1024];
int len = 0;
while ((len = fr.read(content)) != -1) {
fw.write(content, 0, len);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fr != null) {
try {
fr.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fw != null) {
try {
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
处理流
1.BufferedInputStream和BufferedOutputStream
/**
* 通过处理流(缓冲流)进行非文本文件的复制
* 缓冲流需要注意的点:1.作用可以提升效率,原因是由于底层采用的是非阻塞的数组
* 2.缓冲流关闭时候可以关闭对应的节点流
* 3.需要使用flush清空缓冲输出流中的数据
*/
public void copyByBuffer(String src, String des) {
//定义缓冲流
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
try {
//包装缓冲流
FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(des);
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
int len = 0;
byte[] content = new byte[1024];
while ((len=bis.read(content)) != -1) {
bos.write(content, 0, len);
bos.flush();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if(bos!=null){
try {
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(bis!=null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.BufferedReader和BufferedWriter
/**
*BufferedReader的readLine()和BufferedWriter的newLine()=="\n"=="\r\n"
*/
@Test
public void testReadLine(){
BufferedReader br=null;
BufferedWriter bw=null;
try {
FileReader fr=new FileReader("hello.txt");
FileWriter fw=new FileWriter("common.txt");
br = new BufferedReader(fr);
bw = new BufferedWriter(fw);
String line=null;
while((line=br.readLine())!=null){
//方法一
bw.write(line+"\n");
bw.flush();
//方法二
/* bw.write(line);
bw.newLine();
bw.flush();
*/ }
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
finally{
if(br!=null){
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(br!=null){
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
这里需要注意影响读取的效率有两个因素:①是否使用了缓冲流②读取的数组大小是否合适
/**
* 影响效率的两个因素:①是否使用了缓冲流②读取的数组大小是否合适
*/
@Test
public void testEfficient(){
String src="C:\\Users\\Administrator\\Desktop\\1.wmv";
String des1="C:\\Users\\Administrator\\Desktop\\2.wmv";
String des2="C:\\Users\\Administrator\\Desktop\\3.wmv";
long startTime=System.currentTimeMillis();
copyFile(src, des1);
long endTime=System.currentTimeMillis();
System.out.println("未使用缓冲流之前:"+(endTime-startTime));
long startTime1=System.currentTimeMillis();
copyByBuffer(src, des2);
long endTime1=System.currentTimeMillis();
System.out.println("未使用缓冲流之前:"+(endTime1-startTime1));
}
其他处理流
1.转换流
/**
* 考虑到对于纯文本文件的读取时字符流的效率往往比字节流的效率高很多,所以当文本以字节流
* 形式读入的时候为了提升效率java设计了一种处理流(转换流),将字节流转化为字符流,从而
* 提升了文本文件的读取速度。
* 转换流读取文件的过程:
* 读:FileIutputStream->IutputStreamReader->FileReader
* 解码:字节数组——》字符串
* 写:FileOnputStream->OuputStreamWriter->FileWriter
* 编码:字符串-》字节数组
*
*
* 通过这个例子我们可以发现处理流编程的一个特点,就是将输入和输出流不停得向着顶端封装然后直接在顶端操作就好了!!!
*/
@Test
public void testStream2RW(){
BufferedReader br=null;
BufferedWriter bw=null;
try {
File file1=new File("hello.txt");
File file2=new File("hello2.txt");
//解码
FileInputStream fis=new FileInputStream(file1);
InputStreamReader isr=new InputStreamReader(fis);
br = new BufferedReader(isr);
//编码
FileOutputStream fos=new FileOutputStream(file2);
OutputStreamWriter osw=new OutputStreamWriter(fos);
bw = new BufferedWriter(osw);
String content=null;
while((content=br.readLine())!=null){
bw.write(content);
bw.newLine();
bw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}finally{
if(br!=null){
try {
br.close();
} catch (IOException e) {
// TODO Auto-generated catch block
}
}
if(br!=null){
try {
bw.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//从控制台接受输入
@Test
public void testStandard(){
BufferedInputStream bis=null;
try {
InputStream is=System.in;
InputStreamReader isr=new InputStreamReader(is);
BufferedReader br=new BufferedReader(isr);
String content=null;
while(true){
System.out.println("请输入一个字符串:");
content=br.readLine();
if("e".equalsIgnoreCase(content)||"exit".equalsIgnoreCase(content)){
System.out.println("已退出");
break;
}
System.out.println(content.toUpperCase());
/* InputStream is=System.in;
bis = new BufferedInputStream(is);
byte[] content=new byte[1024];
while(true){
System.out.println("请输入一个字符串:");
bis.read(content);
String inputStr=new String(content);
if("e\r\n".equalsIgnoreCase(inputStr)||"exit\r\n".equalsIgnoreCase(inputStr)){
System.out.println("已退出");
break;
}
System.out.println(inputStr.toUpperCase());
*/ }
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(bis!=null){
try {
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2.打印流
/**
* 重定向从在控制台显示到文件中显示,PrintStream PrintWriter
*/
@Test
public void testPrint(){
//接受控制台输入的字符串
BufferedReader br =null;
PrintStream ps=null;
try {
br = new BufferedReader(new InputStreamReader(System.in));
ps = new PrintStream(new FileOutputStream(new File("hello.txt")));
String content = null;
while((content=br.readLine())!=null){
System.out.println("请输入需要录入的数据");
if("e".equalsIgnoreCase(content)||"exit".equalsIgnoreCase(content)){
System.out.println("文件内容录入完毕,请查看hello.txt!");
}
ps.println(content);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
finally{
if(ps!=null){
ps.close();
}
if(br!=null){
try {
br.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
3.对象输入输出流
package com.heima;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import org.junit.Test;
public class TestSerializable {
/**
* 序列化:将对象特征提取成一组二进制流
* 反序列话:通过二进制流解析出对象
*/
@Test
public void testOutputStream(){
ObjectOutputStream ois = null;
try {
ois = new ObjectOutputStream(new FileOutputStream(new File("person.txt")));
Person person=new Person("tang", 12);
ois.writeObject(person);
ois.flush();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(ois != null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void testObjectInputStream(){
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream(new File("person.txt")));
Person person=new Person("tang", 12);
System.out.println(ois.readObject().toString());
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(ois != null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 序列化要求:
* 1.实体类必须继承Serializable接口
* 2.所有的数据成员类型必须实现Serializable接口
* 3.static和transient不能序列化
* @author John
*/
class Person implements Serializable{
private static final long serialVersionUID = 4848325561922145707L;
private String name;
private int age;
public String getName() {
return name;
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
练习:模拟实现Scanner类
package com.heima;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import org.junit.Test;
public class TestSerializable {
/**
* 序列化:将对象特征提取成一组二进制流
* 反序列话:通过二进制流解析出对象
*/
@Test
public void testOutputStream(){
ObjectOutputStream ois = null;
try {
ois = new ObjectOutputStream(new FileOutputStream(new File("person.txt")));
Person person=new Person("tang", 12);
ois.writeObject(person);
ois.flush();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(ois != null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void testObjectInputStream(){
ObjectInputStream ois = null;
try {
ois = new ObjectInputStream(new FileInputStream(new File("person.txt")));
Person person=new Person("tang", 12);
System.out.println(ois.readObject().toString());
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
if(ois != null){
try {
ois.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
/**
* 序列化要求:
* 1.实体类必须继承Serializable接口
* 2.所有的数据成员类型必须实现Serializable接口
* 3.static和transient不能序列化
* @author John
*/
class Person implements Serializable{
private static final long serialVersionUID = 4848325561922145707L;
private String name;
private int age;
public String getName() {
return name;
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}