文章目录
- 文件概述
- 常用的文件操作
- 创建文件
- 获取文件信息
- 目录的操作和文件删除
- 流的分类
- 各抽象类常用子类对象
- FileInputStream
- FileOutputStream
- FileReader
- FileWriter
- 节点流和处理流
- 概念
- BufferedReader
- BufferedWriter
- BufferedInputStream & BufferedOutputStream
- 对象流:ObjectInputStream & ObjectOutputStream
- 标准输入输出流:System.in & System.out
- 转换流:InputStreamReader & OutputStreamWriter
- 常用方法
- 打印流:PrintStream & PrintWriter
- 常用方法(PrintStream 和 PrintWriter通用 )
- 与直接使用 write 方法的区别
- Properties类
- 常用方法
文件概述
- 保存数据的地方
- 文件流:文件在程序中是以流的形式操作的
- 输入流:数据从数据源(文件)到程序(内存)的路径,输出流相反
常用的文件操作
创建文件
- new File(String pathname) 文件的路径
- new File(File Parent,String child ) 父目录文件+子路径(文件名)
- new File(String parent, String child)父目录+子路径
- 上面是创建文件对象,使用创建方法才能真正创建文件:createNewFile() [需要异常处理]
获取文件信息
- getName 文件名
- getAbsolutePath 绝对路径
- getParent 文件父级目录
- length 文件大小(字节)
- exist
- isFile
- isDirectory 文件是不是一个目录(文件夹)
目录的操作和文件删除
- delete (返回boolean 是否删除成功)
- 在Java中,目录也被当成文件
- mkdir 创建一级目录**(注意创建文件夹的方法和创建文件不同!)**
- mkdirs 创建多级目录
流的分类
-
操作数据单位不同:字节流(8 bit,操作二进制文件时无损操作),字符流(操作文本文件效率更高)
-
数据流的流向不同:输入流,输出流
-
流的角色不同:节点流,处理流/包装流
-
字节输入流:InputStream
字节输出流:OutputStream
字符输入流:Reader
字符输出流:Writer
上面四个都是抽象类
-
流和文件的关系:流是文件/程序之间传递信息的介质
各抽象类常用子类对象
FileInputStream
- read 一个个字节读,返回-1到达文件末尾,注意一个汉字有三个字节
- 优化减少读取的次数:read(byte[ ] b) 一次最多读取b.length字节的数据到字节数组,返回实际读取的字节数,返回-1说明读取完毕
- close 关闭文件
FileOutputStream
-
write 写入一个字节
-
String 的 getBytes 可以将字符串转换为字符数组,可以配合write写入字符串
-
write(byte[ ] b)
-
write(byte[ ] b,index1,index2) index为读取字节的起点和终点
-
new 的创建方式,当写入内容时,会覆盖原来的内容
-
new FileOutputStream(filePath,true)
的创建方式,写入内容时,在原来的文本后追加true 传给参数 append
FileReader
- 按照字符来操作io,汉字不会乱码
- new FileReader(File/String,(true)) 可选true,追加模式,需要处理异常
- 读取方式基本和FileInputStream相同
- read
- read(char[ ])
- new String (char[ ]) 将char转换成String
- new String(char[ ],off,len)
- close
FileWriter
- new FileWriter(File/String,(true)) 可选true,追加模式
- 注:写完之后必须关闭流(close)或者刷新(flush),才能真正写到文件中(看源码)
- write(int)
- write(char[ ])
- write(char[ ],off,len)
- write(string)
- write(string,off,len)(注:off为写入的起始位置)
节点流和处理流
概念
-
节点流:从某一特定数据源读写数据,如FileReader、FileWriter,但限制较多,功能不强大
-
处理流(包装流):对节点流进行包装,“连接”已存在的流(节点流或处理流),如BufferedReader、BufferedWriter
BufferedReader 里面有一个 Read 类型的对象,代表其以后可以实现 Read 的各子类如FileReader、CharReader、StringReader等,使其能够处理各种数据,功能更强大
使用了修饰器模式
-
区别和联系
- 节点流是底层流,直接与数据源相连
- 处理流包装节点流,通过一个节点流,传入不同的节点流,来统一处理文件的流(都使用处理流),即想处理字符串就把处理字符串的节点流(如StringReader)传入处理流(BufferedReader),想处理数组就把处理数组的节点流(ByteArrayInputStream)传入处理流(BufferedReader),否则每种节点流还需单独创建一个对象。并且还可以拓展输入输出的方式
- 处理流不直接于数据源相连,而是调用了节点流的方法**(多态和动态绑定)**
-
处理流优点
- 提高性能:以增加缓冲的方式来提高输入输出的效率
- 操作便捷:可能提供一系列便捷的方法来一次输入输出大批量的数据,使用更加灵活方便
-
要知道什么情况下用什么流!
BufferedReader
- 和 BufferedWriter 一样是字符流,尽量操作文本文件,不操作二进制文件(图片、音乐等),否则可能造成文件损坏
- 关闭时关闭外层流即可,即关闭BufferedReader即可,不用关闭内层的节点流(如FileReader),因为关闭外层流的close方法里面调用了节点流的close方法
- readline 按行读取,效率高,当返回 null 时,读取完毕(看手册),不带换行(用newline手动换行)
BufferedWriter
- newLine 插入一个和系统相关的换行(不同系统换行不同,因此不建议用\n换行符)
- 如果要追加,在传入节点流(FileWriter)时传入true,即
new BufferedWriter(new FileWriter( path, true))
BufferedInputStream & BufferedOutputStream
- 字节流,可以操作二进制文件也可以操作文本文件
- 操作与之前的几个类类似
对象流:ObjectInputStream & ObjectOutputStream
-
需求:保存数据时同时保存其数据类型(序列化,即把对象保存为文件。从文件恢复为对象称为反序列化)
-
必须让某个类是 可序列化的,有两个方式:
- 实现 Serializable 接口(推荐,标记接口,没有任何方法)
- 实现 Externalizable 接口
-
ObjectOutputStream 提供 序列化功能
-
ObjectInputStream 提供 反序列化功能
-
序列化后,保存的文件格式,是按照其实际格式保存
-
包装类(如 Integer、Double)和 String 都实现了 Serializable 接口
-
各种方法与之前的类相似
需要注意读写文件时需要加上数据类型,如:writeInt、writeDouble、readBoolean、readChar
-
读取(反序列化)的顺序需要和保存数据(序列化)的顺序一致
-
需要将自定义数据类型(类)的定义拷贝(或导包)到可以引用的位置(注意该类不要用 private 修饰),这样调用过后才能直接用这个自定义数据类型
-
如果要调用自定义数据类型的方法,需要向下转型,因为从文件调进来的是Object类型
-
序列化的类中建议添加 SerialVersionUID (序列版本号),提高序列化兼容性
-
static 和 transient(标记这个变量不应序列化) 修饰的成员不会被序列化
- transient 使用场景
- 安全:密码、安全令牌等不应该持久化或通过网络传输
- 性能:大对象或不重要的对象成员没必要序列化
- 逻辑需求:某对象只可能对特定上下文有意义,无需序列化
- 注意事项
- transient 只能用于变量,不能用于方法和类
- 局部变量(方法内变量)不能用 transien 修饰,因为局部变量本来就不会被序列化
- transient 使用场景
-
序列化对象时,要求里面属性的类型(如另一个类的对象)也需要实现序列化接口,即使其被 transient 修饰,不会被序列化,也要实现这个接口
-
序列化具有可继承性,如某类实现了序列化,则其子类默认实现序列化
-
注意每次 new 的时候都会调用writeStreamHeader()方法写入4个字节的StreamHeader(看源码),标记这个是对象处理流,可能导致的错误:如果在接收方每次都是new一个新的ObjectInputStream 来接收,而发送方只 new 过一个 ObjectOutputStream,之后每次都是用new过的 ObjectOutputStream对象来发送东西,这样就会导致之后并没有添加头部。而接受方每次都是新的 new,则每次接收都要检测头部,当发送方没有提供头部时,接收方就把内容当做了头部,导致出错。解决方法之一是每次使用 ObjectOutputStream 时都重新 new 一遍
标准输入输出流:System.in & System.out
-
标准输入(键盘):System.in,InputStream的流对象(编译类型),运行类型是BufferedInputStream
-
标准输出(显示器):System.out,编译类型和运行类型都是PrintStream,是PrintStream类的一个对象
-
不需要显式关闭
- 它们与程序生命周期有关,程序开始运行时由操作系统自动打开,程序终止时也由操作系统自动关闭,无需用户手动操作
- 标准流通过JVM管理
- 方便使用
- 避免以后需要调用时抛出异常
-
System.setOut(new PrintStream(filePath)) 修改标准输出流的打印位置,打印到文件
(
filePath
用System.out
代替,则打印到屏幕)
转换流:InputStreamReader & OutputStreamWriter
- InputStreamReader:将字节流转换成字符流输入到程序,并且根据指定的编码方式转换为字符
- OutputStreamWriter:将字符流转换成字节流输出到文件,并且根据指定的编码方式转换为字节(如果不用转换流,Java会用默认编码转换为字节)
- 默认情况下,读取文件是按照 utf-8 编码,如果文件不是 utf-8 编码,则可能出现乱码。根本问题:没有指定编码方式
- 转换流可以完成字节流和字符流的转换,并且转换流可以指定编码类型
- InputStreamReader 是 Reader 的子类,可以将 InputStream(字节输入流顶级父类) 转换为 Reader(字符输入流顶级父类)
- OutputStreamWriter 是 Writer 的子类,可以将 OutputStream 转换为 Writer
- 转换流相当于中间商,使用时将 字节流/节点流 传给 转换流 ,再将 转换流 传给 处理流 进行最终处理(也可以不用处理流,看需求),如:
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(filePath),"gbk")))
常用方法
- InputStreamReader(InputStream, Charset)
- OutputStreamWriter(OutputStream, Charset)
- 转换流使用的 read、write的方法,实际上还是使用的节点流的 read、write 方法
打印流:PrintStream & PrintWriter
-
只有输出流,没有输入流
-
可以打印到屏幕上(默认),也可以打印到文件里
-
PrintStream 是 OutputStream 的子类,是字节流
-
PrintWriter 是 Writer 的子类,是字符流
-
注意没有 close 流,仍然不会打印到文件
-
标准输出流 System.out 是 PrintStream 的一个实例化对象,可以使用的方法没有区别。只是标准输出流默认(且一般)将结果打印到屏幕,PrintStream 可以选择打印到文件还是打印到屏幕。如果需要改变标准输出流输出位置,用setOut
常用方法(PrintStream 和 PrintWriter通用 )
-
打印到某个文件时,创建对象:
PrintStream out = new PrintStream("D:\\test.txt");
若打印到屏幕则将地址换成
System.out
-
print:底层用的就是 write,代码:
public void print(String s) { write(String.valueOf(s));}
,将要print的内容转化为字符串的形式,再write出来 -
只不过多了个 null 判断,如果为空,打印 null
-
print方法相比write方法更侧重格式化输出,不需要关心数据的类型,会自动进行类型转换
与直接使用 write 方法的区别
- 打印流是格式化输出,可以直接打印各种类型的数据,不需要手动转换位字符串,println还会手动添加换行符,而write相比是一个更底层的方法,需要手动将数据转换为字节或字节数组
- 打印流本身自带了缓冲输入,而write还需要配合bufferedwriter等使用才有缓冲
- 打印流更适合文本文件写入,write更适合处理非文本数据(如二进制数据)
Properties类
- 需求:配置文件
- 传统方法的代码比较麻烦
- 配置文件的格式:键=值
- 父类是 HashTable
常用方法
-
load(Reader) 加载指定配置文件
-
list 显示k-v
-
getProperty(key) 返回的是字符串
-
setProperty(key, value) 如果该文件没有这个key,则是创建。否则是修改
-
store(Writer/OutputStream, comment)
保存的如果是中文,则会转换成unicode码
comment 是注释,如果为 null,则没有注释,如果写了,会在配置文件最上方写明