JAVA-IO

IO流

传输数据的机制。IO — Input Output — 输入输出流。数据从外部流向程序—输入流;数据从程序流向外部—输出流。读取文件 — 数据从文件流向程序 — 输入流。向一个TXT文件中写入一个字符串 — 数据从程序流向文件 — 输出流
根据流的传输方向:输入流和输出流
根据流的传输形式:字节流和字符流

输入流输出流
字节流InputStreamOutputStream
字符流ReaderWriter

四个基本流都是抽象类
数据的来源/目的地:存储设备、内存、网络、输入设备

案例:向TXT文件中写入一个字符串 — 字符流、输出流、和文件有关的流 — FileWrite

public class FileWriterDemo1 {

public static void main(String[] args) throws IOException {

   // 创建一个新的文件
   // 如果这个文件不存在,则把创建的新文件作为目标文件
   // 如果这个文件已经存在,则创建新文件覆盖原文件
   FileWriter writer = new FileWriter("E:\\a.txt");
   // 写出数据
   // 数据并不是直接写到文件中而是写到缓冲区中
   // 数据还在缓冲区中,然后程序结束了 ---导致数据死在缓冲区中了
   writer.write("def");
   // 冲刷缓冲区
   // writer.flush();
   // 关闭流
   // 关流之前会自动冲刷一次缓冲区 --- 为了防止有数据死在缓冲区
   writer.close();
   // 为了释放内存
   writer = null;
   System.out.println(writer);
   }
}

流中的异常处理

  1. 将流放在try之外声明并且赋值为null,然后放在try之内创建
    2 在关流之前需要判断流对象是否为空
  2. 为了防止关流失败导致流占用文件,需要将流强制回收
  3. 为了防止关流失败导致数据死在缓冲区中,所以需要手动冲刷缓冲区

案例:从TXT文件读取数据 — FileReader

public class FileWriterDemo3 {
   public static void main(String[] args) {
   	//从JDK1.7开始,允许在try之后使用()定义资源
   	//try结束之后会自动关流
   	//try()中的对象对应的类必须实现AutoCloseable接口
   	try(FileWriter writer=new FileWriter("/home/soft01/b.txt")){
   	writer.write("abc");
   	writer.flush();
   	} catch (IOException e) {
   	// TODO Auto-generated catch block
   	e.printStackTrace();
   	}
   }
}

注意:java中原生的字符流无法读取Office组件 - POI,只能读取字符类文件 — .txt .java .html等

缓冲流

java.io.BufferedReader | java.io.BufferedWriter
构造方法:
BufferedReader(Reader in) | BufferedWriter(Writer out)
BufferedReader(Reader in, int sz) | BufferedWriter(Writer out, int sz)
将一个Reader传入BufferedReader后 BufferedReader会在原来Reader的基础上增加缓冲区,从而实现高效的读取数据,这个过程中BufferedReader不会改变原有Reader的功能,只是为底层的流提供了缓冲的能力,用来提高读取效率。实际上使用了装饰设计模式对Reader和Writer的能力进行了增强。(BufferedWriter同理)

案例:使用BufferedReader复制文件:

public class IOcopy2 {
   public static void main(String[] args) {
   	//1.创建字符输入输出流,连接文件
   	try (BufferedReader reader=new BufferedReader(new FileReader("/home/soft01/a.txt"));
   	BufferedWriter writer=new BufferedWriter(new FileWriter("/home/soft01/2.txt"))){
   	//2.从reader中读取数据,向writer中写入数据
   	String line=null;
   	while((line=reader.readLine())!=null) {
   	writer.write(line);
   	writer.newLine();
   	}
   	writer.flush();
   	}catch (IOException e) {
   	// TODO Auto-generated catch block
   	e.printStackTrace();
   	}
   }
}

FileInputStream、FileOutputStream实现文件拷贝

由于文件格式的多样性,字符文件都可以通过转化为字节来实现拷贝,而图片、音频等文件只能通过字节流实现拷贝。考虑到通用性,所以下面案例就以字节流来做演示。

/**
* 以字节流的方式实现文件的拷贝
* 这个案例中的拷贝 读取一组字节就写出一组字节 数据不用大量堆积在内存中 可以节省内存
* 使大文件的拷贝成为可能
* 减少了循环的次数,可以提升性能
*
*/
public class CopyDemo5 {
   public static void main(String[] args) {
   	long begin=System.currentTimeMillis();
   	//1.创建字节 输入输出流
   	try (InputStream fis=new FileInputStream("/home/soft01/2.png");
   	OutputStream fos=new FileOutputStream("/home/soft01/1.png")){
   	//2.读取2.txt中的内容并写出 每读到一组字节就写出一组字节
   	int len=-1;
   	byte[] data=new byte[1024];
   	while((len=fis.read(data))!=-1) {
   	fos.write(data,0,len);
   	}
   	fos.flush();
   	} catch (Exception e) {
   	// TODO Auto-generated catch block
   	e.printStackTrace();
   	}
   	long end=System.currentTimeMillis();
   	System.out.println(end-begin);
   }
}

转换流

字符流的底层也是通过字节来操作数据的 只不过在字节流的基础上增加了 默认的转换器 可以经字节和字符进行映射 采用的编码集为系统码 且无法更改
所以当使用字符流 操作 非系统码的文件时 可能产生乱码
此时可以通过转换流 将自己构建的字节流 转换为字符流 并在这个过程中 指定需要的编码集 来解决问题
InputStreamReader - 可以将字节输入流转换为字符输入流 在转换的过程中 其实内部就是多了一个将字节 映射为 字符的操作 而采用的编码集 可以由程序来指定
构造方法:
InputStreamReader(InputStream in) //将字节输入流转换为字符输入流 内部增加的转换器 采用默认的码表(系统码)
InputStreamReader(InputStream in, String charsetName) //将字节输入流转换为字符输入流 内部增加的转换器 采用的码表由charsetName来指定

系统流

System是java内置的代表当前系统的类 其上提供了很多和系统相关的属性和方法 方便我们对系统进行操作

常用的属性:
static InputStream in - 标准输入流
static PrintStream out - 标准输出流
static PrintStream err - 标准错误输出流

常用的方法:
static void exit(int status) //退出虚拟机
static void gc() //启动垃圾回收 虚拟机通常会根据自身的内存使用情况 自动决定什么时候来进行垃圾回收 但是也可以通过这个方法 通知虚拟机该垃圾回收 但是注意 这个方法调用并不会触发垃圾回收 只是一个通知告诉虚拟机该回收垃圾了 至于是否启动垃圾回收 仍然由虚拟机自己决定
static void setIn(InputStream in) //设定系统标准输入流
static void setOut(PrintStream out) //设定系统标准输出流
static void setErr(PrintStream err) //设定系统标准错误输出流

其中System上提供的 in out err三个字节流 称之为系统流
系统流其实也是字节流 只不过这个字节流不需要我们自己创建 是系统创建好 直接提供给我们用的 不要关 也不能关
其中 in 是系统输入流 从系统标准输入流中获取数据 默认的系统标准输入流的来源 是控制台
其中 out err 是系统输出流 向系统标准输出流中写出数据 默认的系统标准输出流的目的地 是控制台
所以,平常大家写的 System.out.println(“hello world~”); 起始就是通过System.out得到的了系统输出流 向这个流中 通过println方法写出了 数据 最终去往了控制台显示
也可以用 System.err.println(“xxx”) 写出数据 和 System.out 唯一的不同是 打印出来将会是红色的文本 更加醒目 通常用来打印错误信息 平常大家见到的异常信息 就是通过System.err打印的
系统输出流的默认位置是控制台 也可以通过System.setOut() 或System.setErr() 方法修改out或err指向的 系统输出流

而 其中的System.in代表标准输入流 默认数据来源是控制台 所以可以利用它从控制台中读取数据
所以平常从控制态中读取数据可以写成:
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in))
简单写法
Scanner scanner = new Scanner(System.in)
系统标准输入流默认的位置是控制台 也可以通过System.setIn() 方法来修改系统默认输入流 从而从其他位置读取数据

打印流

采用了装饰设计模式 为普通输出流 增加打印功能
PrintStream
可以讲普通的字节流转换为打印字节流 这也是一个装饰设计模式 增加了打印的机制 有如下的三个好处
1.打印流增加了打印各种类型数据的方法,省去了将各种类型的数据转换为字节再输出的过程。
2.打印流会自动刷新流,不需要手动进行flush。
3.打印流永远不抛异常,它的内部自动解决异常。

PrintWriter
可以将普通的字符流转换为打印字符流 这也是一个装饰设计模式 增加了打印的机制 有如下的三个好处
1.打印流增加了打印各种类型数据的方法,省去了将各种类型的数据转换为字符再输出的过程。
2.打印流会自动刷新流,不需要手动进行flush。
3.打印流永远不抛异常,它的内部自动解决异常。

常见的具有特定功能的流

序列化反序列化

java通过创建对象代表现实中的一个具体的事物。
而对象本身是存活在内存中的,内存中的对象的结构是很复杂的,没法直接将一个对象当作一个字节数组来操作。
而在现实开发中,经常需要将内存中的对象 存储到磁盘 通过网络来传输 等等操作,此时需要将内存中的对象转换为可以直接操作的字节数组,这个过程称之为对象的序列化。
而将之前序列化的对象的字节数组,再转回成对象的过程,就称之为反序列化。
java中的序列化反序列化 主要是靠如下两个流来实现的:
ObjectOutputStream - 将对象序列化的流
此类也是装饰设计模式的实现,可以在构造方法中将一个普通字节流传入,此类在传入的流的基础上增加了将对象转换为字节输出的方法。void writeObject(Object obj)

想要用java序列化进行序列化操作的对象的类必须实现一个特定的java.io.Serializable接口。
Serializable接口中并没有定义任何的方法,这个接口仅仅是一个标志,告知虚拟机当前类的对象可以进行序列化反序列化,这样使用接口的方式,称之为标记接口。

ObjectInputStream - 将对象反序列化的流
此类也是装饰设计模式的实现,可以在构造方法中将一个普通字节流传入,此类在传入的流的基础上增加了将之前序列化的字节信息转换为对象的方法。Object readObject()

案例1:创建Person对象 并通过序列化将对象转为字节后输出到文件中

/**
* ObjectOutputStream 实现序列化
*/
public class Demo01 {
   public static void main(String[] args) throws Exception {
   	Person p = new Person("zhangsan",18,"666999");
   	//1.创建ObjectOutputStream
   	ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.data"));
   	//2.通过ObjectOutputStream将对象写出
   	oos.writeObject(p);
   	//3.关闭流
   	oos.flush();
   	oos.close();
   }
}

案例2:将之前案例中序列化后持久化保存的Person对象反序列化恢复到内存中

public class Demo02 {
   public static void main(String[] args) throws Exception {
   	//1.创建输入流 读取序列化文件 包装为ObjectInputStream
   	ObjectInputStream oin = new ObjectInputStream(new FileInputStream("1.data"));
   	//2.从ObjectInputStream中读取对象信息
   	Person p = (Person) oin.readObject();
   	System.out.println(p.getName());
   	System.out.println(p.getAge());
   	System.out.println(p.getPsw());
   	//3.关闭流
   	oin.close();
   }
}

序列化与反序列化需要注意以下几点

1.java在序列化 和 反序列化时 会通过类中的SerialVersionUID来判断 序列化和反序列化时使用的类 是否是同一个 如果一致 则认为是同一个类 正常反序列化 如果不同则认为不是同一个类 抛出异常。 所以通常要在需要在实现了Serializable接口的类中 声明static final long SerialVersionUID 属性 来指定此编号,并且要保证,序列化和反序列时编号一致。如果不手动指定此编号,虚拟机会自动生成一个编号。
2.将对象进行序列化 转换为字节数组 保存在磁盘中的过程 也称之为将对象持久化了起来。将持久化的对象 再变回内存中对象的过程 也称之为反持久化
3.在实现了Serializable接口的类中 如果部分属性不想被序列化 则可以加上transient关键字,则这个属性将会在序列化的过程中被忽略。
4.静态的属性也不会被序列化
5.集合/数组不能被序列化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值