data-source——数据源是提供数据的原始媒介。常见的数据源有数据库、文件、其他程序、内存、网络连接、I/O设备等。
数据源分为源设备和目标设备。
- 源设备:为程序提供数据,一般对应输入流
- 目标设备,程序数据的目的地,一般对应输出流
Java中对文件的操作是以流的方式进行的。流是Java内存中一组有序数据序列。Java将数据从源读入到内存中,形成流,然后这些流还可以写到另外的目的地。为什么要称之为流呢?因为数据序列在不同时刻操作的是源的不同部分。
对于输入流,程序时最终用户,通过流将数据源中的数据输送到程序中。
对于输出流,目标数据源是目的地,通过流将程序中的数据输出到目的的数据源。
输入/输出流的划分是相对于程序而言的,而不是相对于数据源。
当程序需要读取数据源的数据是,就会通过I/O流对象开启一个通向数据源的流,通过这个I/O流对象的相关方法可以顺序读取数据源中的数据。
经典写法如下:
import java.io.*;
public class Study{
public static void main(String[] args) {
FileInputStream fp = null;
try{
fp = new FileInputStream("study.txt");//文件内容为ABC
StringBuilder sb = new StringBuilder();
int temp = 0;//当temp==-1时,表示到了文件结尾,停止读取
while((temp = fp.read()) != -1)
sb.append((char)temp);
System.out.println(sb);
} catch (Exception e){
e.printStackTrace();
}finally {
try{
//这种写法保证了及时遇到异常情况也会关闭对象流
fp.close();//流对象使用完必须关闭
//否则总是占用系统资源,最终会造成系统崩溃
}catch (IOException e){
e.printStackTrace();
}
}
}
}
//输出结果如下
ABC
按流的方向分类
- 输入流:数据流向是数据源到程序(以InputStream、Reader结尾的流)
- 输出流:数据流向是程序到目的地(以OutPutStream、Write结尾的流)
按处理的数据单元分类
- 字节流:以字节为单位获取数据,命名上以Stream结尾的流一般是字节流,如FileInputStream、FileOutputStream
- 字符流:以字符为单位获取数据,命名上以Reader/Writer结尾的流一般是字符流,如FileReader、FileWrite
按处理对象不同分类
- 节点流:可直接从数据源或者目的地读写数据,如FileInputStream、FileReader、DataInputStream等
- 处理流:不直接连接到数据源或者目的地,是“处理数据的流”。通过其它流的处理提高程序的性能,如BufferedInputStream、BufferedReader,处理流也叫包装流
节点流处于I/O操作的第一线,所有操作必须通过它们进行,处理流可以对节点流进行包装,提高性能或者提高程序的灵活性
比如如下示意图:
Java提供了各式各样的I/O流,在程序中可根据需求选择合适的I/O流。下面列出I/O流类体系:
其中绿色是节点流,蓝色是处理流,橙色是字符流。很多流都是成对出现的,下面再给出常用类的简单总结:
- InputStream&OutputStream:字节流的抽象类
- Reader&Writer:字符流的抽象类
- FileInputStream&FilterOutputStream:节点流,以字节为单位直接操作“文件”
- ByteArrayInputStream&ByteArrayOutputStream:节点流,以字节为单位直接操作“字节数组对象”
- ObjectInputStream&ObjectOutputStream:处理流,以字节为单位直接操作“对象”
- DataInputStream&DataOutputStream:处理流,以字节为单位直接操作“基本数据类型和字符串类型”
- FileReader&FileWriter:节点流,以字符为单位直接操作“文本文件”(只能读写文本文件)
- BufferReader&BufferedWriter:处理流,将Reader/Writer对象进行包装,增加缓存功能,提高读写效率
- BufferedInputSream&BufferedOutputStream:处理流,将InputStream&OutputStream对象进行包装,提高读写效率
- InputStreamReader&OutputStreamWriter:处理流,将字节流对象转化成字符流对象
- PrintStream:处理流,将OutputStream进行包装,它可以方便地输出字符,更灵活
InputStream
该抽象类是表示字节输入流的所有类的父亲。InputStream是一个抽象类,不可以实例化。数据的读取需要它的子类来实现。根据节点的不同,它派生了不同的节点流子类。
继承自InputStream的流都是用于向抽象中输入数据,且数据的单位为字节(8bit)。
常用方法:
OutputStream
该抽象类表示字节输出流的所有父类。输出流接收输出字节并将这些字节发送到某个目的地。
常用方法:
Reader
Reader用于读取的字符流抽象类,数据单位为字符。
Writer
Writer用于写入的字符流抽象类。
FileInputStream通过字节的方式读取文件,适合读取所有类型文件。Java也提供了FileReader专门读取文本文件。
FileOutputStream通过字节的方式写数据到文件中,适合写入所有类型文件。Java也提供了FileWriter专门写入文本文件。
示例1:将字符串/字节数组的内容写入到文件中
import java.io.FileOutputStream;
import java.io.IOException;
public class Study{
public static void main(String[] args) {
FileOutputStream fp = null;
String str = "将字符串写入文件中";
try{
fp = new FileOutputStream("study.txt");
//该方法将一个字节数组写到文件中,而write(int n)是写入一个字节
fp.write(str.getBytes());
}catch (Exception e){
e.printStackTrace();;
}finally {
try {
if(fp != null)
fp.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class Study{
public static void main(String[] args) {
copyFile("study.txt", "copy.txt");
}
static void copyFile(String src, String dec){
FileInputStream f1 = null;
FileOutputStream f2 = null;
//设置缓存数组以提高效率
byte[] buffer = new byte[1024];
int temp = 0;
try {
f1 = new FileInputStream(src);
f2 = new FileOutputStream(dec);
while((temp = f1.read(buffer)) != -1)
f2.write(buffer, 0, temp);
}catch (Exception e){
e.printStackTrace();
}finally {
try{
if(f1 != null)
f1.close();
}catch (IOException e){
e.printStackTrace();
}try {
if(f2 != null)
f2.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
字节流可以处理所有的文件,但是字节流不能很好地处理Unicode字符,经常会出现乱码的情况。故处理文本文件时一般使用文件字符流,它以字符为单位进行操作。
示例3:使用FileReader和FileWriter实现文本文件复制
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class Study{
public static void main(String[] args) {
copyFile("study.txt", "copy.txt");
}
static void copyFile(String src, String dec){
FileReader fr = null;
FileWriter fw = null;
int len = 0;
try{
fr = new FileReader(src);
fw = new FileWriter(dec);
char[] buffer = new char[128];
while((len = fr.read(buffer)) != -1)
fw.write(buffer, 0, len);
}catch (Exception e){
e.printStackTrace();
}
try{
if(fr != null)
fr.close();
}catch (IOException e){
e.printStackTrace();
}try {
if(fw != null)
fw.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
Java缓冲流本身并不具有I/O流的读取与写入功能,只是在其它流上加上缓冲功能以提高效率,就像是把其他的流包装起来一样,因此缓冲流是一种处理流。
当对文件或者其他数据源进行频繁读写时,效率比较低,这是如果使用缓冲流就可以更高效的写入信息。缓冲流现将数据缓存起来,当缓存区存满或者手动刷新时再一次性将数据读取到程序或者写入目的地。
BufferedInputStream和BufferedOutputStream这两个流是缓冲字节流,通过内存缓存数组来提高操作流的效率。
示例4:使用缓冲流实现文件的高效率复制
import java.io.*;
public class Study{
public static void main(String[] args) {
long UStime = System.currentTimeMillis();
UsualCopyFile("d:/Test/test/老爹单曲飘向北方.mp4", "d:/Test/copy/复制.mp4");
long Midtime = System.currentTimeMillis();
BufferCopyFile("d:/Test/test/老爹单曲飘向北方.mp4", "d:/Test/copy/复制的.mp4");
long BEtime = System.currentTimeMillis();
System.out.println("普通字节流复制花费的时间:" + (Midtime - UStime));
System.out.println("缓冲字节流复制花费的时间:" + (BEtime - Midtime));
}
static void BufferCopyFile(String src, String dec){
FileInputStream fis = null;
BufferedInputStream bis = null;
FileOutputStream fos = null;
BufferedOutputStream bos = null;
int temp = 0;
try{
fis = new FileInputStream(src);
fos = new FileOutputStream(dec);
//使用缓冲字节流包装文件字节流,增加缓冲功能,提高效率
//缓存区的大小(缓存数组长度)默认是8192,也可以自己指定大小
bis = new BufferedInputStream(fis);
bos = new BufferedOutputStream(fos);
while((temp = bis.read()) != -1)
bos.write(temp);
}catch (Exception e){
e.printStackTrace();
}finally {
try{
if(fos!=null)
fos.close();
}catch (IOException e){
e.printStackTrace();
}try {
if(fis!=null)
fis.close();;
}catch (IOException e){
e.printStackTrace();
}try {
if(bos!=null)
bos.close();
}catch (IOException e){
e.printStackTrace();
}try{
if(bis!=null)
bis.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
static void UsualCopyFile(String src, String dec){
FileInputStream fis = null;
FileOutputStream fos = null;
int temp = 0;
try{
fis = new FileInputStream(src);
fos = new FileOutputStream(dec);
while((temp = fis.read()) != -1)
fos.write(temp);
}catch (Exception e){
e.printStackTrace();
}finally {
try{
if(fos!=null)
fos.close();
}catch (IOException e){
e.printStackTrace();
}try {
if(fis!=null)
fis.close();;
}catch (IOException e){
e.printStackTrace();
}
}
}
}
//输出结果如下:
普通字节流复制花费的时间:391641
缓冲字节流复制花费的时间:389
BufferedReader和BufferedWriter增加了缓存机制,大大提高了读写文本文件的效率,同时提供了更方便的安行读取的方法readLine()。在处理文本时,可以使用缓冲字符流。
示例5:使用缓冲字符流实现文本文件的高效率复制
import java.io.*;
public class Study{
public static void main(String[] args) {
long UStime = System.currentTimeMillis();
UsualCopyFile("博客之容器.txt", "ucopy.txt");
long Midtime = System.currentTimeMillis();
BufferCopyFile("博客之容器.txt", "bcopy.txt");
long BEtime = System.currentTimeMillis();
System.out.println("普通字符流复制花费的时间:" + (Midtime - UStime));
System.out.println("缓冲字符流复制花费的时间:" + (BEtime - Midtime));
}
static void BufferCopyFile(String src, String dec){
FileReader fr = null;
BufferedReader br = null;
FileWriter fw = null;
BufferedWriter bw = null;
String temp = "";
try{
fr = new FileReader(src);
fw = new FileWriter(dec);
br = new BufferedReader(fr);
bw = new BufferedWriter(fw);
//BufferReader提供了更方便的readLine()方法,直接按行读取文本
//br.readLine()方法的返回值是一个字符串对象,即本文本中的一行内容
while((temp = br.readLine()) != null) {
//将读取的一行字符串写入文件
bw.write(temp);
//下次写入之前先换行,否则会在上一行后边继续追加,而不是另起一行
bw.newLine();
}
}catch (Exception e){
e.printStackTrace();
}finally {
try{
if(fr!=null)
fr.close();
}catch (IOException e){
e.printStackTrace();
}try {
if(fw!=null)
fw.close();;
}catch (IOException e){
e.printStackTrace();
}try {
if(br!=null)
br.close();
}catch (IOException e){
e.printStackTrace();
}try{
if(bw!=null)
bw.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
static void UsualCopyFile(String src, String dec){
FileReader fr = null;
FileWriter fw = null;
int temp = 0;
try{
fr = new FileReader(src);
fw = new FileWriter(dec);
while((temp = fr.read()) != -1)
fw.write(temp);
}catch (Exception e){
e.printStackTrace();
}finally {
try{
if(fr!=null)
fr.close();
}catch (IOException e){
e.printStackTrace();
}try {
if(fw!=null)
fw.close();;
}catch (IOException e){
e.printStackTrace();
}
}
}
}
//输出如下
普通字符流复制花费的时间:20
缓冲字符流复制花费的时间:3
ByteArrayInputStream和ByteArrayOutputStream经常用在需要流和数组之间转化的情况,FileInputStream把文件当做数据源,而ByteArrayInputStream则是把内存中的“某个字节数组对象”当做数据源。
示例6:ByteArrayInputStream的使用
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class Study{
public static void main(String[] args) {
byte[] b = "abcdefghijkl".getBytes();
for(byte temp: b)
System.out.print(temp + " ");
System.out.println();
test(b);
}
public static void test(byte[] b){
ByteArrayInputStream bais = null;
StringBuilder sb = new StringBuilder();
int temp = 0;
int num = 0;
try{
bais = new ByteArrayInputStream(b);
while((temp = bais.read()) != -1){
//一次读一个字节
sb.append((char) temp);
num++;
}
System.out.println(sb);
System.out.println("读取的字节数:"+num);
}finally {
try {
if(bais != null)
bais.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
//输出如下
97 98 99 100 101 102 103 104 105 106 107 108
abcdefghijkl
读取的字节数:12
数据流将“基本数据类型与字符串类型”作为数据源,从而允许程序以与机器无关的方式从底层输入/输出流中操作Java基本数据类型与字符串类型。
DataInputStream和DataOutputStream提供了可以存取与机器无关的所有Java基础类型数据的方法。
DataInputStream和DataOutputStream是处理流,可以对其他节点流或者处理流进行包装,增加一些更灵活、更高效的功能。
示例7:DataInputStream和DataOutputStream的使用
import java.io.*;
public class Study{
public static void main(String[] args) {
DataOutputStream dos = null;
DataInputStream dis = null;
FileOutputStream fos = null;
FileInputStream fis = null;
try{
fos = new FileOutputStream("a.txt");
fis = new FileInputStream("a.txt");
//使用数据流对缓冲进行包装,新增缓冲功能
dos = new DataOutputStream(new BufferedOutputStream(fos));
dis = new DataInputStream(new BufferedInputStream(fis));
dos.writeChar('c');
dos.writeInt(295);
dos.writeDouble(Math.random());
dos.writeBoolean(true);
dos.writeUTF("不会吧不会吧不会吧");
dos.flush();//手动刷新缓冲区,将流中数据写入文件中
//直接读取数去,读取数据顺序要和之前写入的一样,否则不能正确读取数据
System.out.println("char:" + dis.readChar());
System.out.println("int:" + dis.readInt());
System.out.println("double:" + dis.readDouble());
System.out.println("boolean:" + dis.readBoolean());
System.out.println("String:" + dis.readUTF());
}catch (Exception e){
e.printStackTrace();
}finally {
try{
if(dos != null)
dos.close();
}catch (IOException e){
e.printStackTrace();
}try{
if(dis != null)
dis.close();
}catch (IOException e){
e.printStackTrace();
}try{
if(fos != null)
fos.close();
}catch (IOException e){
e.printStackTrace();
}try{
if(fis != null)
fis.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
//输出如下
char:c
int:295
double:0.12439793758984896
boolean:true
String:不会吧不会吧不会吧
如果要对某个对象进行读写操作,需要学习一对新的处理流:ObjectInputStream和ObjectOutputStream。它们以对象为数据源,但是必须对传输的对象进行序列化和反序列化的操作。序列化和反序列化在后面学习。
示例8:ObjectInputStream和ObjectOutputStream的使用
import java.io.*;
import java.util.Date;
public class Study{
public static void main(String[] args) {
write();
read();
}
public static void write(){
OutputStream os = null;
BufferedOutputStream bos = null;
ObjectOutputStream oos = null;
try{
os = new FileOutputStream(new File("xxx.txt"));
bos = new BufferedOutputStream(os);
oos = new ObjectOutputStream(bos);
//对象流也可以用基本数据类型进行读写操作哦
oos.writeInt(295);
oos.writeDouble(3.69);
oos.writeChar('a');
oos.writeBoolean(false);
oos.writeUTF("不会吧不会吧不会有人觉得java难学吧");
//对象流能够对对象数据类型进行读写操作
//Date是系统提供的类,已经实现了序列化接口
//如果是自定义的类,则需要自己实现序列化接口
oos.writeObject(new Date());
}catch (Exception e){
e.printStackTrace();
}finally {
try{
if(oos != null)
oos.close();
}catch (IOException e){
e.printStackTrace();
}try{
if(bos != null)
bos.close();
}catch (IOException e){
e.printStackTrace();
}try{
if(os != null)
os.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
public static void read(){
InputStream is = null;
BufferedInputStream bis = null;
ObjectInputStream ois = null;
try{
is = new FileInputStream(new File("xxx.txt"));
bis = new BufferedInputStream(is);
ois = new ObjectInputStream(bis);
System.out.println(ois.readInt());
System.out.println(ois.readDouble());
System.out.println(ois.readChar());
System.out.println(ois.readBoolean());
System.out.println(ois.readUTF());
System.out.println(ois.readObject().toString());
}catch (ClassNotFoundException e){
e.printStackTrace();
}catch (IOException e){
e.printStackTrace();
}finally {
try{
if(ois != null)
ois.close();
}catch (IOException e){
e.printStackTrace();
}try{
if(bis != null)
bis.close();
}catch (IOException e){
e.printStackTrace();
}try{
if(is != null)
is.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
//输出如下
295
3.69
a
false
不会吧不会吧不会有人觉得java难学吧
Sun Jul 12 18:51:55 CST 2020
InputStreamReader和OutputStreamWriter用来实现将字节流转换成字符流。
示例9:使用InputStreamReader接受用户输入,并输出到控制台
import java.io.*;
public class Study{
public static void main(String[] args) {
//创建字符输入和输出流,使用转换流将字节转换成字符流
BufferedReader br = null;
BufferedWriter bw = null;
try{
br = new BufferedReader(new InputStreamReader(System.in));
bw = new BufferedWriter(new OutputStreamWriter(System.out));
String str = br.readLine();
while(!"#".equals(str)){
bw.write(str);//写到控制台
bw.newLine();//写一行后换行
bw.flush();//手动刷新
str = br.readLine();//在读一行
}
}catch (IOException e){
e.printStackTrace();
}finally {
if(br != null){
try{
br.close();
}catch (IOException e){
e.printStackTrace();
}
}if (bw != null){
try {
bw.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}
}
//示例输出如下
ab5654ad2@@&*sads13//.;.'
ab5654ad2@@&*sads13//.;.'
asds1z\cs/fsf65sa+5s61
asds1z\cs/fsf65sa+5s61
#
Process finished with exit code 0
RandomAccessFile可以实现两个作用:
1)实现对一个文件的读和写操作
2)可以访问文件的任意位置,不像其他流只能按照先后顺序读取
此流掌握三个核心方法:
1)RandomAccessFile(String name, String mode) name用于确定文件,mode取r(读)或rw(可读写),通过mode可以确定流对文件的访问权限。
2)seek(long a)用于定位流对象读写文件的位置,a确定读写位置距离文件开头的字节数。
3)getFilePointer()用于获得流的当前读写位置。
示例10:RandomAccessFile应用
import java.io.IOException;
import java.io.RandomAccessFile;
public class Study{
public static void main(String[] args) {
RandomAccessFile raf = null;
try{
int[] data = {
1 , 2, 3, 5, 9, 32, 56, 75, 165, 135};
raf = new RandomAccessFile("a.txt", "rw");
for(int i = 0; i < data.length; i++)
raf.writeInt(data[i]);
raf.seek(4);
System.out.println(raf.readInt());
for(int i = 0; i < 10; i+=2){
raf.seek(i * 4);
System.out.print(raf.readInt() + " ");
}
System.out.println();
raf.seek(8);
raf.writeInt(88);
for(int i = 0; i < 10; i++){
raf.seek(i * 4);
System.out.print(raf.readInt() + " ");
}
}catch (IOException e){
e.printStackTrace();
}finally {
try{
if(raf != null)
raf.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}//输出如下
2
1 3 9 56 165
1 2 88 5 9 32 56 75 165 135
对象的本质是用于组织和存储数据,对象本身也是数据。
当两个进程进行远程通信时,可能会发送各种类型的数据,无论是何种类型的数据,都是以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列才能在网络上传送,接收方则需要把字节序列再恢复为Java对象才能正常读取。
将Java对象转换为字节序列的过程称为对象的序列化,将字节序列恢复为Java对象的过程称为对象的反序列化。
对象序列化的作用如下:
ObjectOutputStream代表对象输出流,它的writeObject(Object obj)方法可对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。
ObjectInputStream代表对象输入流,它的readObject()方法可以从一个源输入流中读取字节序列,再将其反序列化为一个对象并返回。
只有实现了Serializable接口的类的对象才能被序列化。Serializable接口是一个空接口,只起到标记作业。
import java.io.*;
class Person implements Serializable{
//添加序列化ID,它决定这是否能够成功反序列化
private static final long serialVersionUID = 1L;
String name;
int age;
boolean isMan;
public Person(String name, int age, boolean isMan){
this.name = name;
this.age = age;
this.isMan = isMan;
}
@Override
public String toString() {
return "Person [name:" + name + ", age:" + age + ", isMan:"+ isMan + "]";
}
}
public class Study{
public static void main(String[] args) {
FileOutputStream fos = null;
ObjectOutputStream oos = null;
ObjectInputStream ois = null;
FileInputStream fis = null;
try{
Person person = new Person("xxx", 18, true);
//序列化
fos = new FileOutputStream("a.txt");
oos = new ObjectOutputStream(fos);
oos.writeObject(person);
oos.flush();
//反序列化
fis = new FileInputStream("a.txt");
ois = new ObjectInputStream(fis);
Person p = (Person)ois.readObject();
System.out.println(p);
}catch (Exception e){
e.printStackTrace();
}finally {
try{
if(oos != null)
oos.close();
}catch (IOException e){
e.printStackTrace();
}try{
if(fos != null)
fos.close();
}catch (IOException e){
e.printStackTrace();
}try{
if(ois != null)
ois.close();
}catch (IOException e){
e.printStackTrace();
}try{
if(fis != null)
fis.close();
}catch (IOException e){
e.printStackTrace();
}
}
}
}//输出如下
Person [name:xxx, age:18, isMan:true]
装饰器模式是GoF的23中设计模式中较为常用的一种模式,他可以对源有的类进行包装和修饰,是新的类具有更强的功能。
例如以下代码:
class Computer{
private String name;
public Computer(String name){
this.name = name;
}
public void show(){
System.out.println("我是" + name + ", 我的屏幕可以播放视频");
}
}
class ComputerWithAudio{
public Computer computer;
public ComputerWithAudio(Computer c){
this.computer = c;
}
public void show(){
computer.show();
System.out.println("我还可以播放他们的声音");
}
}
public class Study{
public static void main(String[] args) {
Computer computer = new Computer("dell");
computer.show();
System.out.println("========装饰之后=========");
ComputerWithAudio computerWithAudio = new ComputerWithAudio(computer);
computerWithAudio.show();
}
}//输出如下
我是dell, 我的屏幕可以播放视频
========装饰之后=========
我是dell, 我的屏幕可以播放视频
我还可以播放他们的声音
I/O流体系中大量使用了装饰器模式,我们之前的代码实现中大量的使用了,这让流具有更强的功能以及更强的灵活性。如:
OutputStream os = new FileOutputStream(new File("xxx.txt"));
BufferedOutputStream bos = new BufferedOutputStream(os);
ObjectOutputStream oos = new ObjectOutputStream(bos);
ObjectOutputStream装饰了原有的BufferedOutputStream,BufferedOutputStream装饰了原有的OutputStream,使普通的OutputStream也具有了缓存功能和读取写入对象的功能,提高了效率,增强了灵活性。