总体简单认知:
IO就是输入输出操作。输入又可以有两个问题,从什么地方(也就是输入什么)输入,以哪种方式输入。输出也是类似。输出到那(输出什么),怎么输出。
- 按操作方式分为 字节流和字符流,
- 字节流:操作字节序列的对象的,输入和输出就是字节1 流;在IO包中使用抽象类InputStream和OutputStream继承的类构成一个层次结构
- 字符流:因为字节流是一个一个字节操作的,但是字节文件时计算机的语言,而人类语言时字符,字符在计算机中在计算机表示要使用Unicode编码,一个字符使用多字节存储信息。所以IO包中从抽象类Reader和Writer继承出来的的类构成一个单独的层次结构
- 按功能分有节点流和包装流
- 节点流:数据的源头,可以直接从数据源或目的地读写数据
- 也叫包装流,顾名思义不直接连接到数据源或目的地,是其他流(节点流,包装流)进行封装。目的主要是简化操作和提高性能。流的源头都是节点流
在IO操作中最重要的是5个类和3个接口;5个类分别是 File,InputSteram,OutputSteram,Reader,Writer。3个接口 Closeable,Flushable,Serializable。接下来将一 一介绍
Java中IO包下的类关系图
在谈论java中IO包的类图关系是,我们首先来认识一个设计模式;装饰模式。简单理解就是装饰,用一个东西A去装饰另一个东西B。这个东西A在是个装饰类,被装饰的东西B是具体的实现类。下面这个是IO的类图
而这个是用装饰模式去看IO包装的类
知道了这个IO的装饰模式,我们再来看着个IO的简化类图结构是不是清晰明了
5个重要类
File类(文件操作)
File类中的构造器可以通过传入(parent,child)从父抽象路径名和子路径名字符串创建新的 File实例。,也可以直接传入路径名创建File实例。还可以传入URL来创建File实例。File种的一些方法也应该掌握,知道如何使用。
- File.exists() 检测文件路径表示对目录或者文件是否存在
- File.createnewFile() 当且仅当具有该名称的文件尚不存在时,原子地创建一个由该抽象路径名命名的新的空文件
- File.mkdir() 创建由此抽象路径名命名的目录 ;mkdirs() 创建目录,并且创建任何必须但不存在的父目录
- File.listFiles() 返回一个抽象路径名数组
- File.delete()方法 可以删除一个文件
- File.toURI() 构造一个表示此抽象路径名的 file: URI。
其实这些方法,说一千到一万,你只要打打代码试一试,方法的主要作用也就了解了 下面是一个实例:
public class traverseFiles {
/**
* 需求:使用IO技术,获取磁盘指定目录下的指定类别的文件。说出其中的问题,借助集合改善项目
* 分析:
* 1、获取目录下的文件,目录不确定,需要使用程序的人指定(需要键盘录入)
* 2、文件类别也是不确定的,也是需要指定(需要键盘录入)
*
* Java中的键盘录入,需要使用到Scanner类
*
*/
public static void main(String[] args) throws IOException {
// 创建用于获取键盘录入的流对象
Scanner scanner = new Scanner( System.in );
System.out.println("请输入文件所在的目录:");
String dir = scanner.nextLine();
System.out.println("请输入查询文件类别:");
String extName = scanner.nextLine();
System.out.println(dir);
System.out.println(extName);
getFiles(dir , extName);
}
/**
* 用于获取指定目录下的文件
* @param dir
* @param extName
*/
private static void getFiles(String dir, String extName) throws IOException {
// 把目录封装成一个File对象
File dirs = new File(dir);
// 判断目录是否存在
if( ! dirs.exists() ){
System.out.println("您输入的目录不存在");
return ;
}
// 获取目录下的文件
File[] files = dirs.listFiles( new MyFileFilter( extName ) );
//MyFileFilter是自己写的一个简单的过滤器类;
//new MyFileFilter(extName)== pathname.isDirectory() || pathname.isFile() && pathname.getName().endsWith(extName);
// 判断有没有获取到指定目录下的内容(文件和文件夹)
if( files == null ){
System.out.println("您指定的目录下面没有文件,或者是没有权限访问");
return ;
}
// 遍历打印,查阅拿到的数据
for (File file : files) {
// 遍历获取到目录下的文件或文件夹
if( file.isFile() ){
// 当前拿到的是文件
System.out.println(file);
}else{
// 说明是文件夹
// 如果是目录(文件夹),可以继续获取这个文件夹中的内容
getFiles(file.getCanonicalPath() , extName);
}
}
}
}
这种方式是使用递归来实现文件目录的遍历;那如何不适应递归来遍历呢?可以自行尝试一下[^实现]
在文件的操作上还有一特别重要的点:就是相对路径和绝对路径
\ | 概念 | 例子 | 方法 |
---|---|---|---|
相对路径 | 就是文件目录先从盘符开始的完整路径 | D:/javaStudy/SxtJava/IO/4.jpg | getAbsolutePath() 的到绝对路径 |
绝对路径 | 就是基于某个基准目录的 部分路径(但是在真正使用绝对路径前,系统会根据当前的目录将相对目录拼接完整) | IO/4.jpg | System.getProperty(“user.dir”)可以得到相对路径前要拼接 |
😐 注意:
- 1 不管是相对路径或者绝对路径。同意使用/斜杆书写,因为它不会出现跨平台时路径因为斜杠的错误
- 2 对于绝对路径应该从盘符开始写起
- 3 对于相对路径,再写相对路径时,第一个字符不能是/。否则会出错
InputStream和OutputStream
这个两个输入输出字节操作流层次结构中。Int(输入)和Out(输出)都是成对出现的。
~ | 重要的方法 | ||
---|---|---|---|
InputStream | read() 读入一个字节,并返回读入的字节,或者在遇到输入源结尾的时返回-1 | available() 返回在不阻塞的情况下的可用的字节数 | close() 关闭输入流 |
~ | read(byte[] b) ;read(byte[] b,int off,int len); | ||
OutputStream | write(int n) 写出一个字节 | flush() 清空输出流,就是说将缓冲的数据发送到目的地 | close() 关闭输出流 |
~ | write(bety[] b) 写出一个字节数组 ;write(byte[] b, int off ,int len) off :第一个写出字节在b中的偏移量 ,len :写出字节最大数量 |
read和write方法在执行时都将阻塞,知道字节确实被读入或者写出,因此因为这个而造成的当前线程的阻塞;我们由available方法去检查当前可用于读入的字节数量;利用这个方法是的读取数据时不会被阻塞。当我们对流完成读写时,因该调用close()方法,关闭它(在这应该注意,输入流时输入流,输出流时输出流;这两个流没有内部任何关系)。关闭的同时会进行清空输出流的缓冲区。
对于对 InputStream和OutputStream继承的子类中。我就仅仅介绍 InputStream下一些常见的子类,因为在OutputStream有一一对应的。只是一个是输入,一个输出
- 具体子类
- FileInputSream:文件输入流。它通常用于对文件进行读取操作
- ObjectInputStream:实现了Serializable接口,对象输入流,用来提供对“基本数据或对象”的持久存储。通俗点讲,也就是能直接传输对象(反序列化中使用),
- ByteArrayInputStream:字节数组输入流,该类的功能就是从字节数组(byte[])中进行以字节为单位的读取,也就是将资源文件都以字节的形式存入到该类中的字节数组中去,我们拿也是从这个字节数组中
- FilterInputStream :他是抽象的类 ,装饰者模式中处于装饰者,具体的装饰者都要继承它,所以在该类的子类下都是用来装饰别的流的,也就是处理类。
- BufferedInputStream:具体的装饰类继承FilterInputStream 缓冲流,对处理流进行装饰,增强,内部会有一个缓存区,用来存放字节,每次都是将缓存区存满然后发送,而不是一个字节或两个字节这样发送。效率更高
- DataInputStream:具体的装饰类继承 FilterInputStream 数据输入流,它是用来装饰其它输入流,它“允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型”;就是有些特殊的方法如readInt(), readDouble()等可以读取一个 int(4字节), double(8个字节)并返回一个int,double类型的值,
具体的实例:实现拷贝字节流的拷贝,(深拷贝)
package com.zs.IO.testUse;
import java.io.*;
/**
* @program: SxtJava
* @description: 利用字节流的输入输出,实现文件,图片,等等的拷贝
* @author: 周硕
* @create: 2020-08-12 10:24
**/
public class copyFile {
/**
* 1.创建源
* 2选择流
* 3操作
* 4释放资源
*/
public static void main(String[] args) {
String inputPath = "2.jpg";
String outputPath = "21.jpg";
Copytest(inputPath,outputPath);
}
public static void Copytest(String Input, String Output) {
//创建输入源,输出源
File Inputsrc = new File(Input);
File Outputsrc = new File(Output);
//选择流
InputStream in = null;
OutputStream out = null;
//操作
try {
in =new BufferedInputStream(new FileInputStream(Inputsrc));
out = new BufferedOutputStream(new FileOutputStream(Outputsrc));
byte[] datas = new byte[1024];
int len =-1;
while( (len=in.read(datas))!= -1){
out.write(datas,0,len);
}
out.flush();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
//释放资源 先打开的后关闭
try {
if(in !=null) {
out.close();
in.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
Reader和Writer
~ | |||
---|---|---|---|
reader | read() 读入一个字符 | close()关闭并释放与之相关的任何资源 | mark(int r);skip(long n) |
~ | read(char[] cubf) 将字符读入数组 | read(char[] cbfu,int off,int len) 将字符读入数组的一部分 | |
writer | writer(char[] cbf) 写入一个数组 | flush()刷新流 | close() 关闭流,先刷新 |
~ | write(char[] bucf ,int off,int len) 写入数组的一部分 | write(int c) 写一个字符;write(String str) 写一个字符串 | write(String str,int off, int len) 写一个字符串的一部分 |
这是Read和Writer抽象类的一些方法;对于继承他们的类大部分会重写他们的抽象方法;以便提供更好的效率和一些功能;
-
Read的子类:
*CharArrayReader StringReader 是两种基本的节点流,它们分别将CharArray,String中读取数据。还有PipedReader 是从与其它线程共用的管道中读取数据在这不做过多的讨论。- BufferedReader 很明显就是一个装饰器,它和其子类负责装饰其它Reader 对象。从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取。
- FilterReader 是所有自定义具体装饰流的父类,其子类PushbackReader 对Reader 对象进行装饰,会增加一个行号。
- InputStreamReader 是一个连接字节流和字符流的桥梁,它将字节流转变为字符流。FileReader 读取字符流,
-
Writer的子类
- CharArrayWriter、StringWriter 是两种基本的节点流,它们分别向Char 数组、String 中写入数据。PipedWriter 是向与其它线程共用的管道中写入数据,
- BufferedWriter 是一个装饰器为Writer 提供缓冲功能。将文本写入字符输出流,缓冲字符,以提供单个字符,数组和字符串的高效写入。
- PrintWriter 和PrintStream 极其类似,功能和使用也非常相似。将对象的格式表示打印到文本输出流;就是调用printf,println或者forname来完成
- OutputStreamWriter 是OutputStream 到Writer 转换的桥梁,它的子类FileWriter 其实就是一个实现此功能的具体类(具体可以研究一SourceCode)。功能和使用和OutputStream 极其类似,后面会有它们的对应图。
你是否可以仿照上面的以字节流的方式拷贝代码。写一个以字符流的方式拷贝字符文件呢。在下一篇网络给出答案
三个常见接口
- Closeable,方法close(),关闭此流并释放与之相关联的任何系统资源。 如果流已经关闭,则调用此方法将不起作用。
- Flushable,方法flush(),通过将任何缓冲的输出写入底层流来刷新流。
Serializable。实现这个接口的类 可以实现序列化;
序列化和反序列化
序列化:将对象数据转化位二进制数据流进行传输。
我们要将一个对象序列化或者将二进制文件反序化成对象。前提是这个对象必须是可以允许序列化的,也就是说对象实例化的类是实现了Serialzable接口的;也可以是实现Externalizable接口。
当我们需要把对象实例化时。我们会使用到ObjectInputStream和ObjectInputStream这两个类。这两个是字节流层次下的类并且是用来提供对“基本数据或对象”的持久存储。更具体的我们需要用writeObject(Object obj))将指定的对象写入ObjectOutputStream。 readObject(ObjectInputStream in) 从ObjectInputStream读取一个对象。这两个方法。
下面们使用代码实例来测试序列化
**
* @program: SxtJava
* @description: 利用序列化实现深拷贝
* @author: 周硕
* @create: 2020-10-03 18:01
**/
public class SerializationCopy implements Cloneable, Serializable {
private String name;
private Date birthday;
public SerializationCopy(String name, Date birthday) {
this.name = name;
this.birthday = birthday;
}
public SerializationCopy() {
}
public String getName() {
return name;
}
public Date getBirthday() {
return birthday;
}
public void setName(String name) {
this.name = name;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
return obj;
}
}
/**
*序列化测试
**/
public class SerializationTest {
public static void main(String[] args) throws Exception {
Date data = new Date(123412341234L);
SerializationCopy s1 = new SerializationCopy("母体", data);
System.out.println(s1.getName());
System.out.println(s1.getBirthday());//1
//DeepSheep s2 = (DeepSheep) s1.clone();
//将对象s1序列化,在进行反序列化得到s2;
ByteOutputStream bos = new ByteOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(s1);
byte[] bytes = bos.getBytes();
ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
ObjectInputStream ois = new ObjectInputStream(bis);
SerializationCopy s2 = (SerializationCopy) ois.readObject();
s2.setName("子体");
System.out.println(s2.getName());
System.out.println(s2.getBirthday());
data.setTime(23423412341L);//改变母体的引用对象的属性
System.out.println("改变母体的引用对象,母体引用对象的值:" + s1.getBirthday());//2引用对象的属性改变
System.out.println("改变母体的引用对象,子体引用对象的值:" + s2.getBirthday());//3 拷贝的子体引用对象的属性也发生改变
}
}
1位=1比特 ;1字节=8位;1字=2字节;1字=16位 ↩︎