<Java>17 IO流
本文最后更新于:2023年6月27日 上午
17 IO流
17.1 文件
文件就是保存数据的地方。
文件流:文件 在 程序 中是以 流 的形式来操作的。
流:数据在数据源(文件)和程序(内存)之间经历的路径
输入流:数据从数据源到程序的路径
输出流:数据从程序到数据源的路径
17.1.1 常用的文件操作
Java 提供了 File 类,用于处理文件相关的操作
-
创建文件对象相关构造器和方法
-
new File(String pathname)
:根据路径创建一个 File 对象String path1 = "d:/test.jpg"; String path2 = "d:\\test.jpg"; File file1 = new File(path1); File file2 = new File(path2); //此时只是在内存中产生了一个对象
-
new File(File parent, String child)
:根据父目录文件 + 子路径构建File parentFile1 = new File("d:\\"); String fileName1 = "test.txt"; File file3 = new File(parentFile1, fileName1);
-
new File(String parent, String child)
:根据父路径 + 子路径构建 -
creatNewFile()
:创建新文件try { file.createNewFile(); //这个场合,内存对象才写入磁盘 } catch (IOException e) { e.printStackTrace(); }
-
-
获取文件相关信息
-
getName()
:获取名称 -
getAbsolutePath()
:获取文件绝对路径 -
getParent()
:获取文件父级目录 -
long length()
:获取文件大小(字节) -
exists()
:文件是否存在 -
isFile()
:是不是一个文件 -
isDirectory()
:是不是一个目录 -
isAbsolute()
:是不是绝对路径 -
canRead()
:是否可读canWirte()
:是否可写 -
long lastModified()
:最后修改时间 -
String[] list()
:列出符合模式的文件名
-
-
目录的操作和文件删除
mkdir
:创建一级目录mkdirs
:创建多级目录delete
:删除空目录或文件boolean renameTo(File newName)
:更改文件名
其实目录(在内存看来)就是特殊的文件
注意事项:
- File 类可以获取文件的各种相关属性,可以对其进行改名,甚至删除。但除了文件名外的属性没有修改方法
- File 类可以用来描述一个目录,但不能改变目录名,也不能删除目录
17.2 IO流
- I / O 是 Input / Output 的缩写。IO 技术是非常实用的技术,用于处理数据传输。如 读 / 写 文件,网络通讯等。
- Java 程序中,对于数据的 输入 / 输出 操作以 “流(stream)”的方式进行
java.io
包下提供了各种 “流” 类和接口,用以获取不同种类的数据,并通过方法输入或输出数据- 输入(input):读取外部数据(磁盘、光盘、网络数据等)到程序(内存)中
- 输出(output):将程序(内存)数据输出到外部存储
17.2.1 IO流的分类
-
按操作数据单位不同分为:
- 字节流(8 bit):二进制文件用该方法,能确保文件无损
- 字符流(按照字符,字符的字节数由编码决定):文本文件,效率更高
-
按数据流的流向不同分为:
- 输入流:读取外部数据(磁盘、光盘、网络数据等)到程序(内存)中
- 输出流:将程序(内存)数据输出到外部存储
-
按流的角色不同分为:
- 节点流
- 处理流 / 包装流
Σ(っ °Д °;)っ 字节流 字符流 输入流 InputStream Reader 输出流 OutputStream Writer
Java 的 IO流 总共涉及 40多个类,实际上都是上述 4 类的抽象基类派生的
由这 4 个类派生的子类名称都是以其父类名作为子类名后缀
17.2.2 IO流 常用类
#17.2.2.1 FileInputStream
:文件字节输入流
-
构造器:
new FileInputStream(File file); //通过一个 File 的路径指定创建 new FileInputStream(String path); //通过一个路径指定创建 new FileInputStream(FileDescriptor fdObj); //通过文件描述符创建
-
方法:
-
available()
:返回目前可以从流中读取的字节数实际操作时,读取的字节数可能大于这个返回值
-
close()
:关闭文件输入流,释放资源 -
finalize()
:确保在不引用文件输入流时调用其close()
方法 -
getChannel()
:返回与此流有关的唯一的FileChannel
对象 -
getFD()
:返回描述符 -
read()
:从该输入流中读取一个数据字节如果没有输入可用,该方法会被阻止。返回 -1 的场合,说明到达文件的末尾。
File file = new File("d:\\test"); FileInputStream fileInputStream = null; int read; try { fileInputStream = new FileInputStream(file); while ((read = fileInputStream.read()) != -1){ System.out.print((char) read); } } catch (IOException e) { e.printStackTrace(); } finally { try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } //真 TM 复杂。throw 了算了
这个场合,效率较低
read(byte[] b)
:从该输入流中把最多 b.length 个字节的数据读入一个 byte 数组读取正常的场合,返回实际读取的字节数。
... byte[] b = new byte[8]; //一次读取 8 字节 try { fileInputStream = new FileInputStream(file); while ((read = fileInputStream.read(b)) != -1){ System.out.print(new String(b, 0, read)); //这一句看不懂请看[12.2 - 4] } catch ... finally ...
read(byte[] b, int off, int len)
:从该输入流中读取 len 字节数据,从数组下标 off 处起写入 -
skip(long n)
:从该输入流中跳过并去丢弃 n 个字节的数据 -
mark(int markArea)
:标记数据量的当前位置,并划出一个缓冲区。缓冲区大小至少为 markAreareset()
:将输入流重新定位到对此流最后调用mark()
方法时的位置markSupported()
:测试数据流是否支持mark()
和reset()
操作
-
#17.2.2.2 FileOutputStream
:文件字节输出流
-
构造器:
new FileOutputStream(File file); //通过一个 File 的路径指定创建 new FileOutputStream(File file, boolean append); //append = false,写入采用 覆盖原文件 方式 //append = true 的场合,写入采用 末尾追加 方式 new FileOutputStream(String path); //通过一个路径指定创建 new FileOutputStream(String path, boolean append); new FileOutputStream(FileDescriptor fdObj); //通过文件描述符创建
-
方法:
-
close()
:关闭文件输入流,释放资源 -
flush()
:刷新此输出流并强制写出所有缓冲的输出字节 -
finalize()
:确保在不引用文件输入流时调用其close()
方法 -
getChannel()
:返回与此流有关的唯一的FileChannel
对象 -
getFD()
:返回描述符 -
write(byte[] b)
:将 b.length 个字节从指定 byte 数组写入此文件输出流File file = new File("d:\\test1"); FileOutputStream fileOutputStream = null; try { fileOutputStream = new FileOutputStream(file); //此时,若文件不存在会被创建 fileOutputStream.write('a'); String str = "Melody"; fileOutputStream.write(str.getBytes()); } catch ... finally ...
write(byte[] b, int off, int len)
:将指定 byte 数组中下标 off 开始的 len 个字节写入此文件输出流write(int b)
:将指定字节写入此文件输出流
-
#17.2.2.3 FileReader
:文件字符输入流
与其他程序设计语言使用 ASCII 码不同,Java 使用 Unicode 码表示字符串和字符。ASCII 码的字符占用 1 字节,可以认为一个字符就是一个字节。但 Unicode 码用 2 字节表示 1 个字符,此时字符流和字节流就不相同。
-
构造器:
new FileRaeder(File file); new FileRaeder(String string);
-
方法:
read()
:读取单个字符。read(char[])
:批量读取多个字符到数组。
#17.2.2.3 FileWriter
:文件字符输出流
-
构造器:
new FileWriter(File path); new FileWriter(String path2); new FileWriter(File path3, boolean append); new FileWriter(String path4, boolean append);
-
方法:
write(int)
:写入单个字符write(char[])
:写入指定数组write(char[], off, len)
:写入指定数组的指定部分write(string)
:写入字符串write(string, off, len)
:写入字符串的指定部分flush()
:刷新该流的缓冲。如果没有执行,内容就不会写入文件close()
:等于flush()
+ 关闭
注意!
FileWriter
使用后,必须关闭(close)或刷新(flush),否则无法真正写入
#17.2.2.4 转换流 InputStreamReader
和 OutputStreamWriter
InputStreamReader
是Reader
的子类。可以把InputStream
(字节流)转换成Reader
(字符流)OutputStreamWriter
是Writer
的子类。可以把OutputStream
(字节流)转换成Writer
(字符流)- 处理纯文本数据时,如果使用字符流效率更高,并能有效解决中文问题,建议将字节流转换成字符流。
- 可以在使用时指定编码格式(UTF -8、GBK 等)
-
构造器
InputStreamReader isr = new InputStreamReader(fileInputStream, "UTF-8"); //传入 字节流 和 编码类型 BufferedReader br = new Bufferedreader(isr); //用另一个处理流包装
17.2.3 节点流和处理流
- 节点流:从一个特定数据源读写数据。
- 处理流(包装流):是 “连接” 在已存在的流(节点流或处理流)上,为程序提供更强大的读写功能。
#节点流和处理流的区别和联系
- 节点流是 底层流 / 低级流。直接和数据源相接。
- 处理流(包装流)包装节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法完成输入输出
- 处理流对节点流进行包装,使用了修饰器设计模式。不会直接与数据源相连
- 处理流的功能主要体现在
- 性能的提高:以增加缓冲的方式提高输入输出的效率
- 操作的便捷:处理流可能提供了一系列便捷方法来一次性输入大量数据,使用更加灵活方便
- 关闭时关闭外层流即可
#17.2.3.1 缓冲区流
缓冲区流是一种包装流。缓冲区字节流有 BufferedInputStream 和 BufferedOutputStream;缓冲区字符流有 BufferedWriter 和 BufferedReader。他们是在数据流上加了一个缓冲区。读写数据时,数据以块为单位进入缓冲区,其后的读写操作则作用于缓冲区。
这种方式能降低不同硬件设备间的速度差异,提高 I/O 效率。
构造器:
new BufferedReader(reader); //传入一个 Reader
new BufferedReader(reader, 1024); //传入 Reader 并指定缓冲区大小
new BufferedWriter(writer); //传入一个 Writer
new BufferedWriter(writer, 1024); //传入 Writer 并指定缓冲区大小
//追加还是覆盖,取决于 writer
方法:
-
bufferedReader.readLine()
:按行读取(不含换行符)。会返回一个字符串。返回 null 时,表示读取完毕。
String line; while (line = bufferedReader.readLine() != null){ ... } bufferedReader.close();
-
bufferedWriter.write(String str)
:插入字符串 -
bufferedWriter.newLine()
:插入一个(和系统相关的)换行
#17.2.3.2 数据数据流
除了字节或字节数组外,处理的数据还有其他类型。为解决此问题,可以使用 DataInputStream 和 DataOutputStream。它们允许通过数据流来读写 Java 基本类型,如布尔型(boolean)、浮点型(float)等
构造器:
new DataInputStream(inputStream);
new DataOutputStream(outputStream);
方法:
-
byte readByte()
:读取下一个 byteint readInt()
、double readDouble()
、String readUTF()
…… -
void writeByte(byte b)
:写入一个 bytevoid writeInt(int n)
、void writeUTF(String str)
……虽然有对字符串的读写方法,但应避免使用这些方法,转而使用字符输入/输出流。
#17.2.3.3 对象流
当我们保存数据时,同时也把 数据类型 或 对象 保存。
以上要求,就是能够将 基本数据类型 或 对象 进行 序列化·反序列化 操作
序列化和反序列化
- 把对象转成字符序列的过程称为序列化。保存数据时,保存数据的值和数据类型
- 把字符序列转成对象的过程称为反序列化。恢复数据时,恢复数据的值和数据类型
- 需要让某个对象支持序列化机制,则必须让其类是 可序列化的。由此,该类必须实现下列接口之一
Serializable
:推荐。因为是标记接口,没有方法Externalizable
:该接口有方法需要实现
transient 关键字
- 有一些对象状态不具有可持久性(如 Thread 对象或流对象),这样的成员变量必须用 transient 关键字标明。任何标有 transient 关键字的成员变量都不会被保存。
- 一些需要保密的数据,不应保存在永久介质中。为保证安全,这些变量前应加上 transient 关键字。
-
构造器:
new ObjectInputStream(InputStream inputStream); new ObjectOutputStream(OutputStream outputStream);
-
方法:
反序列化顺序需要和序列化顺序一致,否则出现异常。
-
writeInt(Integer)
:写入一个 intreadInt()
:读取一个 int -
writeBoolean(Boolaen)
:写入一个 booleanreadBoolean()
:读取一个 boolean -
writeChar(Character)
:写入一个 charreadChar()
:读取一个 char -
writeDouble(Double)
:写入一个 doublereadDouble()
:读取一个 double -
writeUTF(String)
:写入一个 StringreadUTF()
:读取一个 String -
writeObject(Serializable)
:写入一个 ObjreadObject()
:读取一个 Obj读取的场合,如果想要调用方法,需要向下转型。
为此,需要该类其引入,或将类的定义拷贝到可以引用的位置。
-
-
注意事项
-
读写顺序要一致
-
实现序列化或反序列化的对象,要实现
Serializable
或Externalizable
接口 -
序列化的类中建议添加
SerialVersionUID
以提高版本兼容性private static final long serialVersionUID = 1L;
有此序列号的场合,后续修改该类,系统会认为只是版本修改,而非新的类
-
序列化对象时,默认将其中所有属性进行序列化(除了
static
和tansient
修饰的成员) -
序列化对象时,要求其属性也实现序列化接口
-
序列化具备可继承性。某类若实现可序列化,则其子类也可序列化
-
#17.2.3.4 标准输入 / 输出流
Σ( ° △ °lll) | 编译类型 | 运行类型 | 默认设备 |
---|---|---|---|
System.in :标准输入 |
InputStream |
BufferedInputStream |
键盘 |
System.out :标准输出 |
PaintStream |
PaintStream |
显示器 |
#17.2.3.5 打印流 PaintStream
和 PaintWriter
打印流只有输出流,没有输入流
-
PaintStream
是OutputStream
的子类。PaintWriter
是Writer
的子类。 -
默认情况下,
System.out
输出位置是 标准输出(即:显示器)修改默认输出位置:
System.setOut(new PrintStream(path));
#17.2.3.6 Properties
类
-
Properties
是专门用于读写配置文件的集合类底层维护了一个
Entry
数组 -
配置文件格式:
键=值 键=值 …
注意:键值对不需要空格,值不需要引号(值默认
String
) -
常见方法:
-
load(InputStream)
load(Reader)
:加载配置文件的键值对到Properties
对象Properties properties = new Properties(); properties.load(new FileReader("d:\\data.data"));
-
list(PaintStream)
list(PaintWriter)
:将数据显示到指定设备properties.list(System.out); //在控制台显示
-
getProperty(key)
:根据键获取值properties.get("IQ");
-
setProperty(key, value)
:设置键值对到Properties
对象如果没有该 key,就是创建。如有,就是替换。
properties.set("IQ", 0); properties.set("Balance", 0);
-
store(Writer, String)
store(OutputStream, String)
:把Properties
中的键值对存储到配置文件。后面的
String
是注释。如有,会被用#
标记并写在文件最上方。注释可以为 null。IDEA 中,如果含有中文,会储存为 unicode 码
-
#17.2.3.7 随机访问文件
程序阅读文件时不仅要从头读到尾,还要实现每次在不同位置进行读取。此时可以使用 RandomAccessFile
构造器:
new RandomAccessFile(String name, String mode); //通过文件名
new RandomAccessFile(File file, String mode); //通过文件对象
参数 mode 决定以只读方式
mode = "r"
还是读写方式mode = "rw"
访问文件。
方法:
-
long getFilePointer()
:返回文档指针的当前位置 -
void seek(long pos)
:将文档指针置于指定的绝对位置 pos文档指针的位置从文档开始的字符处开始计算,
pos = 0L
表示文档的开始 -
long length()
:返回文件长度