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