Java标准的IO操作

java标准的IO操作

使用java IO我们可以对外界设备以相同的方式进行读写,完成数据交换

同一套操作,来操作不同的设备

java IO将"读"与"写"按照方向进行了划分:

输入:从外界到程序的方向,用于让程序获取外界数据
因此输入是"读"数据的操作

输出:从程序到外界的方向,用于将数据"写"出的操作.

输入流(InputStream)、输出流(OutputStream),流动的是字节流

java IO以"流"的形式表示读写功能

InputStream是所有字节输入流的父类,其定义了基础的读取方法

通过输入流我们可以连接上外界设备从而读取该设备数据

常用的方法如下:

  • int read()
    读取一个字节,以int形式返回,该int值的"低八位”有效,若返回值为-1则表示EOF
  • int read(byte[] d)
    尝试最多读取给定数组的length个字节并存入该数组,返回值为实际读取到的字节量。
OutputStream是所有字节输出流的父类,其定义了基础的写出方法

常用的方法如下:

  • void write(int d)
    写出一个字节写的是给定的in的”低八位”
  • void write(byte[ d)
    将给定的字节数组中的所有字节全部写出

节点流(低级流)与处理流(高级流)

java将流分为两大类:节点流与处理流

按照流是否直接与特定的地方(如磁盘、内存、设备等)相连,分为节点流和处理流两类。

节点流:也称为低级流

可以从或向一一个特定的地方(节点)读写数据。

真实连接程序与数据源的"管道",用于实际搬运数据的流
读写一定是建立在节点流的基础上进行的

处理流:又称为“高级流”或“过滤流”

是对一个已存在的流的连接和封装,通过所封装的流的功能调用实现数据读写。

高级流不能独立存在,必须连接在其他流上,目的是当数据 流经当前流时对其做某些加工处理,简化我们读写数据时的相应操作

可以理解为:处理水的热水器、净水器,单独存在无意义,连接在其他流上(低级流和高级流)才有用

流的链接:
处理流的构造方法总是要带一个其他的流对象做参数,一个流对象经过其他流的多次包装,称为流的链接。
换种说法
实际使用IO时,我们通常会串联若干的高级流最终连接到低级流上,使得读写数据以流水线式加工处理完成,这个操作称为“流的连接”,这也是IO的精髓所在

相当于把热水器或净水器连接到水管上(高级流连低级流)
把热水器连接到净水器上(高级流连高级流)
流动的是数据

这种设计模式是装饰者模式
在不改变原有类的情况下,增强其能力

文件流(一种低级流)

文件流是一对低级流,作用是连接到文件上,用于读写文件数据
java.io.FileOutputStream:文件输出流
java.io.FileInputStream:文件输入流

文件流提供的构造方法:

FileOutputStream(File file)
FileOutputStream(String path)
以上两种创建方式,默认为覆盖写模式
即:若指定的文件已经存在,那么会将该文件原有数据全部删除,然后再将新数据写入文件

FileOutputStream(File file,boolean append)
FileOutputStream(String path,boolean append)
以上两种构造器允许再传入一个boolean值类型的参数,
如果该值为true时,文件输出流就是追加写模式
即:数据中原有数据都保留,新内容会被追加到文件末尾

文件输出流:

package io;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

public class FosDemo {
	public static void main(String[] args) throws UnsupportedEncodingException, IOException {
		FileOutputStream fos = new FileOutputStream("./fos.txt",true);

		//String line = "回首,掏~";
		//fos.write(line.getBytes("UTF-8"));
		
		//fos.write("鬼刀一开看不见~走位~走位~".getBytes("UTF-8"));

		fos.write("手".getBytes("UTF-8"));

		System.out.println("写出完毕!");
		fos.close();
	}
}

文件输入流:

package io;

import java.io.FileInputStream;
import java.io.IOException;

public class FISDemo {
	public static void main(String[] args) throws IOException {
		FileInputStream fis = new FileInputStream("fos.txt");
		byte[] data = new byte[1000];  //提供读取的容器
		int len = fis.read(data); //记录读取的实际长度
		//String str = new String(data,"utf-8").trim();
		//String 还支持另外一种重载(字节数组,起始下标,结束下标,编码格式)
		String str = new String(data,0,len,"utf-8");
		System.out.println(str);
		fis.close();
	}
}
文件流与RAF的区别:

RAF是基于指针的随机读写形式
可以对文件任意位置进行读或写的操作,可以做到对文件部分数据覆盖等操作,读写更灵活。

文件流是基于java IO的标准读写,而IO是顺序读写模式
即:只能向后写或读数据,不能回退,没有指针

单从读写灵活度来讲RAF是优于文件流的
但是文件流可以基于java IO的流连接完成一个复杂数据的读写
这是RAF做不到的,选择这两种方法时,根据实际需求不用过于纠结

练习:使用文件流完成文件的复制工具
package io;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * 使用文件流完成文件的复制工具
 * @author Tian
 *
 */
public class CopyDemo {
	public static void main(String[] args) throws IOException {
		//创建存取数据的字节数组(数据容器,相当于交换变量值时的第三方变量)
		byte[] data = new byte[1024*10];
		
		//记录运行开始时间
		long start = System.currentTimeMillis();
		
		//读取源文件,创建输入流(注意输入是从文件输入到现在的这个代码程序)
		FileInputStream fis = new FileInputStream("./image.jpg");
		//写入复制文件,创建输出流(注意输出是从现在这个程序写出到文件)
		FileOutputStream fos = new FileOutputStream("./image_copy.jpg");
		
		int len = -1; //记录读取的实际长度
		
		//循环写入每次的10k大小字节数组
		while((len = fis.read(data))!=-1){//读取信息并返回读取长度存储到len中
			//将字节数组的信息写入复制的文件
			fos.write(data,0,len);
		}
		
		//读取完毕,关闭输入流
		fis.close();
		//写出完毕,关闭输出流
		fos.close();
		
		//记录运行结束时间
		long end = System.currentTimeMillis();
		
		//程序运行结束,输出时间
		System.out.println("复制完毕,耗时:"+(end-start)/1000+"s");
	}
}

常见的高级流(加工数据的工具)

一、缓冲流(在流连接中的提高速读写效率)

java.io.BufferedOutputStream
java.io.BufferedInputStream

缓冲流是一对高级流,在流连接中的作用是提高速读写效率
使得我们在进行读写操作时用单字节读写也能提高读写的效率

在向硬件设备做写出操作时,增大写出次数无疑会降低写出效率
为此我们可以使用缓冲输出流来一 次性批量写出若干数据,减少写出次数来提高写出效率

BufferedOutputStream缓冲输出流内部维护着一个缓冲区(一个8k字节数组)
每当我们向该流写数据时,都会先将数据存入缓冲区
当缓冲区已满时,缓冲流会将数据一次性全部写出

无论我们使用缓冲流进行何种读写(单字节或块读写),最终都会被缓冲流转换为块读写,来提高效率

package io;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class CopyDemo2 {
	public static void main(String[] args) throws IOException {
		//创建输入流
		FileInputStream fis = new FileInputStream("./image.jpg");
		//创建输入流的缓冲流,传入参数为输入流
		BufferedInputStream bis= new BufferedInputStream(fis); 
		//创建输出流
		FileOutputStream fos = new FileOutputStream("./image_copy.jpg");
		//创建输出流的缓冲流,传入参数为输出流
		BufferedOutputStream bos = new BufferedOutputStream(fos);
		//复制时,一次一字节
		int d = -1;
		//对缓冲流进行操作就行了
		while((d = bis.read())!=-1){
			bos.write(d);
		}
		//关闭时,关闭缓冲流会自动关闭其附身的低级流
		bis.close();
		bos.close();
	}
}

缓冲输出流的缓冲区问题:

使用缓冲输出流可以提高写出效率,但是这也存在着一个问题,就是写出数据缺乏即时性。
有时我们需要在执行完某些写出操作后,就希望将这些数据确实写出
而非在缓冲区中保存直到缓冲区满后才写出
这时我们可以使用缓冲流的一个方法flush

void flush() 清空缓冲区,将缓冲区中的数据强制写出
flush方法是OutputStream中定义的方法
所有的输出流都具有该方法,但是只有缓冲流的该方法实现了,有实际意义
其他的流具有该方法的目的是在流连接中传递缓冲操作给缓冲流

flush的作用是将缓冲流中已经缓存的数据一次性写出
频繁的调用flush方法会提高写出次数从而降低写出效率,但是能保证数据写出的及时性
所以需要根据实际需求选择,比如文件复制不用,但是聊天需要用flush

package io;

import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;

public class BOS_flush {
	public static void main(String[] args) throws UnsupportedEncodingException, IOException {
		FileOutputStream fos = new FileOutputStream("./bos.txt");
		BufferedOutputStream bos = new BufferedOutputStream(fos);
		bos.write("此剑抚平天下不平事,此剑无愧世间有愧人".getBytes("utf-8"));
		bos.flush();
		System.out.println("写出完毕!");
        //close时也会自动调用flush,class方法中调用了flush方法
		bos.close();
	}
}

二、对象流(在流连接中读写java对象)

java.io.ObjectOutputStream
java.io.ObjectInputStream

对象流是一种高级流,在流连接中的作用是方便读写java对象(对象与字节的转换由对象流完成)
将java对象按照其结构,转换成字节流,存入文件

对象流的三大块:类的创建、类对象的写入(对象输出流)、类对象的读出(对象输入流)

1、对象流中访问类的创建

使用当前类的实例测试对象流的对象读写操作时

注意:使用对象流的类如果报java.io.NotSerializableException

说明该类要实现Serializable的接口,该接口为序列化操作

ps:
序列化:把一组数据按一个结构转化为一组字节信息的过程
Serializable:是签名接口,我们开发一般不用,这种接口一般是java做的
签名接口:没有方法内容,在编译时,编译器为自动添加方法,即:在java文件编译成class文件后才会出现

签名接口:没有方法内容,在编译时,编译器为自动添加方法,即:在java文件编译成class文件后才会出现

在实现序列化接口后,该类会提示添加一个序列化版本号
**序列化版本号:**java会默认指定加上,这个会影响反序列化,写出与读入的版本号要一致,否则还原不会成功
建议自己生成版本号,不要让编译器自动添加;自己定义,版本号不会随修改类的变化而变化

**transient关键字:**在序列化中,用于忽略该关键字修饰的属性

被修饰的属性将不会被忽略,不会序列化记录,可以达到省略存储空间的目的,减少不必要的开销

但是相应的该属性的信息也没有了,恢复了默认值

代码示例

package io;

import java.io.Serializable;
import java.util.Arrays;

//实现Serializable,记得要导包
public class Person implements Serializable{
	private static final long serialVersionUID = 1L;//序列化版本号,java会默认指定加上
	private String name;
	private int age;
	private String gender;
	private String[] otherInfo;
	
	public Person() {}

	public Person(String name, int age, String gender, String[] otherInfo) {
		this.name = name;
		this.age = age;
		this.gender = gender;
		this.otherInfo = otherInfo;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public String getGender() {
		return gender;
	}

	public void setGender(String gender) {
		this.gender = gender;
	}

	public String[] getOtherInfo() {
		return otherInfo;
	}

	public void setOtherInfo(String[] otherInfo) {
		this.otherInfo = otherInfo;
	}
	
	@Override
	public String toString() {
		return name+","+age+","+gender+","+Arrays.toString(otherInfo);
	}
}
2、类对象的写入(对象输出流)

步骤:

  1. 创建需要写入文件的实例化对象
  2. 创建文件输出流(低级流),传入文件路径
  3. 创建对象输出流(高级流),传入低级流
  4. 将对象通过对象流写入文件,调用对象流的写入对象的方法(oos.writeObject§;)
  5. 关闭对象输出流

注意事项:

  1. 在第四步中要注意对象流的写入方法方法可能抛出:NotSerializableException
    这说明写出的对象所属的类没有实现Serializable接口,我们需要在该类中实现该接口
  2. 写入文件后发现该文件的实际数据量比当前对象保存的内容要大
    这是因为这组字节除了包含了该对象的数据外,还含有这个对象的结构信息
    如果想减少不必要的开销,我们可以用 transient 关键字修饰不必要的属性

代码示例

package io;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

/**
 * 对象输出流
 * @author Tian
 *
 */
public class OOSDemo {
	public static void main(String[] args) throws IOException {
		/*
		 * 将一个Person实例写入文件Person.obj中
		 */
		//实例化对象
		String name = "吴素";
		int age = 22;
		String gender = "女";
		String[] otherInfo = {"陆地剑仙","佩剑","大凉龙雀","此剑抚平天下不平事","此剑无愧天下有愧人"};
		Person p = new Person(name,age,gender,otherInfo);
		//创建文件输出流
		FileOutputStream fos = new FileOutputStream("./Person.obj");
		//创建对象输出流
		ObjectOutputStream oos = new ObjectOutputStream(fos);
		//写出数据,调用对象输出流的写入对象方法
		//该方法可能抛出:NotSerializableException
		//当写出的对象所属的类没有实现Serializable接口时就会抛出该异常
		oos.writeObject(p);
		/*
		 * 写入文件后发现该文件的实际数据量比当前对象保存的内容要大
		 * 这是因为这组字节除了包含了该对象的数据外
		 * 还含有这个对象的结构信息
		 */
		System.out.println("写出完毕!");
		//关闭高级流
		oos.close();
	}
}
3、类对象的读出(对象输入流)

步骤:

  1. 创建文件输入流(低级流),传入文件路径
  2. 创建对象输入流(高级流),传入低级流
  3. 创建引用,接收读取的对象,通过ois.readObject()方法读取对象
  4. 关闭对象输入流

注意事项:

  1. 在第三步中,我们需要先创建一个相应对象,并且读取出来的对象要注意强转为我们新建引用的对象
  2. 注意如果文件中的数据不是相应的数据类型会报ClassNotFoundException的异常

ps. 在进行文件对象的读取时,我们进行了反序列化操作(把字节转换为对应数据)

代码示例:

package io;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

/**
 * 对象输入流
 * @author Tian
 *
 */
public class OISDemo {
	public static void main(String[] args) throws ClassNotFoundException, IOException {
		/*
		 * 将Person.obj文件中的对象读取出来
		 */
		//创建文件输入流
		FileInputStream fis = new FileInputStream("./Person.obj");
		//创建对象输入流
		ObjectInputStream ois = new ObjectInputStream(fis);
		//读入数据
		//Object p = ois.readObject();
		//需要强转为对应的对象类型
		//要注意,如果文件中的数据不是相应的数据类型会报ClassNotFoundException的异常
		//这里进行了反序列化:把字节转换为对应数据
		Person p = (Person) ois.readObject();
		System.out.println(p);
		ois.close();
	}
}

ps.文件流、缓冲流、对象流为字节流

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值