Java.io 包提供了所有同步 IO 的功能;
File对象
java 标准库 Java.io 提供了 File 对象来操作文件和目录;
构造 File 对象时,既可以传入绝对路径,也可以传入相对路径;
如:
File f = new File(“C:\\windows\notepad”);
注意:Windows 使用 ‘\’ 作为路径分隔符(java中用 \\ 表示),Linux 中使用 ‘/’;
File 对象的3种路径表示方式:
-
getPath() # 返回构造方法传入的路径;
-
getAbsolutePath() # 返回绝对路径;
-
getCanonicalPath() # 返回规范路径,不包含 . 和 … 的绝对路径;
获取系统分隔符: File.separator;
文件和目录:
File 对象既可以表示文件,也可以表示目录,构造File对象时,即使传入的文件或目录不存在,代码也不会报错,因为不会导致任何磁盘操作;
只有调用 File 对象的某些方法时,才会真正进行磁盘操作;
如:
isFile() # 判断该 File 对象是否是一个已存在的文件;
isDirectory() # 判断是否是一个已存在的目录;
判断文件的权限和大小:
-
boolean canRead()
-
boolean canWrite()
-
boolean canExecute()
-
long length() : 文件字节大小
对目录而言,是否可执行表示能否列出它包含的文件和子目录;
创建和删除文件:
当File对象表示一个文件时,可以通过 createNewFile() 创建一个新文件,用 delete() 删除该文件;
例:
File file = new File(“/path/to/file”);
file.createNewFile();
file.delete();
创建临时文件:
File f = File.createTempFile(“tmp-”,“.txt”); // 提供临时文件的前缀和后缀;
JVM 退出时自动删除该文件 : f.deleteOnExit();
遍历文件和目录:
当 File 对象表示一个目录时,可以使用 list() 和 listFiles() 列出目录下的文件和子目录名;
listFiles() 提供了一系列重载方法,可以过滤不想要的文件和目录;
例:
File f = new File(“C:\windows”);
File[] fs1 = f.listFiles();
过滤:
File[] fs2 = f.listFiles(new FilenameFilter(){ //仅列出 .exe 文件
public boolean accept(File dir,String name){
return name.endsWith(".exe"); //返回 true 表示接受该文件
}
});
File 对象如果表示一个目录:
-
boolean mkdir() : 创建当前 File 对象表示的目录;
-
boolean mkdirs() : 创建当前File对象表示的目录,并在必要时将不存在的父目录也创建出来;
-
boolean delete() : 删除当前 File 对象表示的目录,当前目录必须为空才能删除成功;
Paths 工具类
java.nio.file 包,提供了一个 Path 对象;
Path p1 = Paths.get(".","project","study"); // 构造一个 Path 对象;
Path p2 = p1.toAbsolutePath(); //转换为绝对路径;
Path p3 = p2.normalize(); //转换为规范路径;
File f = p3.toFile(); // 转换为 File 对象;
Files工具类
java.nio 包中的 Files 工具类,可以方便我们读写文件;
常用的有:
把一个文件的全部内容读取为一个 byte[]:
byte[] data = Files.readAllBytes(Paths.get(“/path/file.txt”));
把一个文件的全部内容读取为 String:
String content = Files.readString(Paths.get(“/path/to/file.txt”), StandardCharsets.UTF-8);
按行读取并返回每行内容:
List lines = Files.readAllLines(Paths.get(“/path/file.txt”));
写入一个二进制文件
byte[] data = …
Files.write(Paths.get(“/path/file.txt”),data)
写入文本并指定编码
Files.writeString(Paths.get(“/path/file.txt”),“文本内容”, StandardCharsets.UTF-8);
按行写入文本
List lines = …
Files.write(Paths.get(“/path/file.txt”),lines);
注意:Files提供的读写方法,受内存限制,只能读写小文件,例如配置文件等;
InputStream
InputStream 是一个抽象类,是所有输入流的超类;
Java 的 IO 标准提供的 InputStream 根据来源不同可以分为:
-
FileInputStream: 从文件读取数据;
-
ServletInputStream : 从 HTTP 请求读取数据;是一个抽象类;
-
Socket.getInputStream() : 从 TCP 连接读取数据;
最重要的方法:
read() : 读取输入流的下一个字节,返回字节表示的int值(0-255),如果读到末尾,则返回 -1;
缓冲
对于文件和网络流来说,利用缓冲区一次性读取多个字节,效率往往高很多;
InputStream 提供了两个重载方法,来支持读取多个字节:
int read(byte[] b) : 读取若干字节并填充到 byte[] 数组,返回读取的字节数(注意是字节数!与read方法不同);
int read(byte[] b , int off , int len ) : 指定 byte[] 数组的偏移量和最大填充数;
注意:
-
如果返回 -1 , 表示没有更多数据了;
-
java.io 中的流都是同步的,也就是说调用方法时会阻塞;
InputStream 的子类:
1、FileInputStream : 用来从文件流中读取数据;
InputStream input = new FileInputStream(“src/readme.txt”);
2、ByteArrayInputStream:用来在内存中模拟一个 InputStream;
byte[] data = {1,2,3,4};
InputStream input = new ByteArrayInputStream(data);
流的关闭:
打开的流,使用完毕后应该及时通过 close() 来关闭,释放底层资源;
方式一:
InputStream input = null;
try{
input = new FileInputStream("src/readme.txt");
int n ;
while(n = input.read() != -1){
}
}finally{
if(input!=null){
input.close();
}
}
方式二:
使用 try(resource) 的语法,编译器会自动帮我们关闭资源( 在 try(resource) 块的结束处自动关闭);
原理:编译器只看 try(resource=…) 中的对象是否实现了 java.lang.AutoCloseable 接口,如果实现了,就自动加上 finally 语句并调用 close() 方法;
try(InputStream input = new FileInputStream("src/readme.txt")){}
同时操作多个 AutoCloseable 资源时,在 try(resource){…} 语句中可以同时写出多个资源,用 ;隔开。
OutputStream
最重要的方法:
1、void write(int b) # 写入一个字节到输出流;
注意:虽然传入的是 int ,但只会写入 int 的最低 8 位;
2、void write( byte[] ) # 写入若干字节到输出流;// output.write(“hello”.getBytes(“UTF-8”));
3、close() # 关闭输出流;
4、flush() # 将缓冲区中的内容真正输出到目的地;
注意:缓冲区写满,或者调用 close() 都会自动触发 flush() 方法的调用;
OutputStream 的实现类:
1、FileOutputStream : 文件输出流;
例:
OutputStream outPut = new FileOutputStream(“out/read.txt”);
outPut.write(97);
2、ByteArrayOutputStream : 在内存中模拟一个输出流,把 byte 数组在内存中变成一个OutputStream;
例:
ByteArrayOutputStream outPut = new ByteArrayOutputStream();
byte[] data = null;
outPut.write(“hello”.getBytes(“UTF-8”));
data = outPut.toByteArray();
// outPut.toString(StandardCharsets.UTF-8);
sout(new String(data,“UTF-8”));
Filter 模式
JDK 中 InputStream 的继承关系:
InputStream : 顶级接口
基础输入流:
FileInputStream
ByteArrayInputStream
ServletInputStream【抽象类】
提供附加功能的输入流:
FilterInputStream:
BufferedInputStream : 提供缓冲;
DigestInputStream : 计算签名;
CipherInputStream:加密/解密;
使用:
InputStream file = new FileInputStream(“test.gz”);
增加缓冲区:
InputStream buffered = new BufferedInputStream(file);
读取解压缩的内容:
InputStream bufferded = new GZIPIputStream(buffered);
DigestInputStream使用示例
MessageDigest md = MessageDigest.getInstance(digestName);
InputStream input = new DigestInputStream(request.getInputStream(), md);
byte[] result = md.digest()
//将一个字节数组转换成 BigInteger ,然后转换成十六进制字符串
// BigInteger(int signum, byte[] magnitude) # 将一个字节数组转换为BigeInteger, signum=-1表示负, 0 表示 0, 1表示正;
new BigInteger(1,result).toString(16);
这种通过一个 “基础” 组件再叠加各种 “附加” 功能组件的模式,称之为装饰器模式;
优势:可以让我们通过少量的类来实现各种功能的组合(避免子类爆炸);
在叠加多个 FilterInputStream 时,我们只需要持有最外层的 InputStream, 当最外层的 InputStream 关闭时,内层 InputStream 的close( ) 方法也会被自动调用;
读取 classpath 资源
把资源存储在 classpath 中可以避免文件路径依赖;
try( InputStream input = getClass().getResourceAsStream(“/default.properties”) ){//TODO}
在 classpath 中的资源文件,路径总是以 / 开头;
如果资源文件不存在,它将返回 null;
序列化
序列化是指把一个 Java 对象变成二进制内容,本质上就是一个 byte[] 数组;
序列化后可以把 byte[] 保存到文件中,或者把 byte[] 通过网络传输到远程;
反序列化: 把一个 byte[] 数组,变回 java 对象;
如何序列化:
1、一个 java 对象要能序列化,必须实现一个特殊的 java.io.Serializable 接口,此接口没有任何方法,是一个空接口,又称标记接口;
2、使用: ObjectOutputStream 把一个 java 对象写入一个字节流;【ObjectOutputStream(OutputStream out)】
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
ObjectOutputStream ouput = new ObjectOutputStream(buffer);
//写入 int
output.writeInt(123);
//写入String
output.writeUTF(“hello”)
//写入 Object
output.writeObject(Double.valueOf(“123.1”));
如何反序列化:
使用 ObjectInputStream : 从一个字节流读取 Java 对象;【ObjectInputStream(InputStream in)】
try(ObjectInputStream input = new ObjectInputStream(…)){
int n = input.readInt();
String s = input.readUTF();
Double d = (Double) input.readObject();
}
readObject 可能抛出的异常有:
ClassNotFoundException : 没有找到对应的 class;
InvalidClassException:Class不匹配;
为了避免class定义变动导致的不兼容,Java 的序列化允许class定义一个特殊的 serialVersionUID 静态变量,用于标识 Java 类的序列化 “版本”, 通常可以由 IDE 自动生成,如果增加或修改了字段,可以改变 serialVersionUID的值,这样就能自动阻止不匹配的 class 版本;
注意:反序列化时,由 JVM 直接构造出 Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行;
更好的序列化方法是通过 JSON 这样的通用数据结构来实现,只输出基本类型(包括 String)的内容,而不存储任何与代码相关的信息;
Reader(抽象类)
java.io.Reader : 是所有字符输入流的超类;
InputStream 是字节流,以 byte(0-255)为单位读取,读到字节数组 int read(byte[] b);
而 Reader 是 字符流,以 char(2个字节0-65535)为单位读取,读到字符数组 int read(char[] c);
InputStreamReader 类继承于 Reader 抽象类,FileReader 继承于 InputStreamReader;
主要方法:
read() : 读取字符流的下一个字符,并返回字符表示的 int, 范围 0-65535,读到末尾返回-1;
int read( char[] c ):一次读取若干字符,并填充到 char[] 数组;返回实际读入的字符个数;
int read(char cbuf[] , int off, int len) : off:从字符数组的哪里开始存储字符,len:读取的字符数,
Reader的子类:
1、FileReader:
Reader reader = new FileReader(“src/reademe.txt”,StandardCharsets.UTF_8); // 需要指定字符编码;
2、CharArrayReader : 在内存中模拟一个 Reader,把一个 char[] 数组变成一个 Reader;
Reader reader = new CharArrayReader(“hello”.toCharArray);
3、StringReader : 可以直接把 String作为数据源,它和 CharArrayReader 几乎一样;
Reader reader = new StringReader(“Hello”)
4、BufferedReader 额外定义的方法;
String readLine() : 读取一行,不包含行终止符 和 null;
Stream lines() # Return a Stream, the elements of which are lines read from this BufferedReader;
注意:
readLine() 是一个阻塞函数,当没有数据读取时,就一直会阻塞在那,而不是返回 null;
readLine() 使用的 buffer 有 8192 个字符。
使用 socket 之类的数据流时,要避免使用 readLine(), 以免为了等待一个换行/回车符而一直阻塞;
InputStreamReader
普通的 Reader 实际上是基于 InputStream 构造的,因为 Reader 需要从 InputStream 中读入字节流(byte),然后,根据编码设置,再转换为 char 就可以实现字符流;
Reader 本质上是一个基于 InputStream 的 byte 到char的转换器;
InputStreamReader 就是这样一个转换器,它可以把任何 InputStream 转换为 Reader ;
例:
InputStream input = new FileInputStream(“src/readme.txt”);
//变换为 Reader,需要传入 InputStream ,还需要指定编码;
Reader reader = new InputStreamReader(input,“UTF-8”);
Writer
Writer 是带编码转换器的 OutPutStream, 把 char 转换为 byte 并输出;
Writer 是所有字符输出流的超类;
OutputStream:
字节流,以 byte 为单位;
写入字节:void write( int b )
写入字节数组:void write( byte[] b )
Writer:
字符流,以char为的那位;
写入字符(0-65535): void write(int c)
写入字符数组:void write(char[] c)
写入String: void write(String s);
Writer的实现类:
1、FileWriter : 向文件中写入字符流;
Writer writer = new FileWriter(“readme.txt”,StandardCharsets.UTF-8);
writer.write(“H”); 写入字符
writer.write(“hello”.toCharArray()); 写入 char[]
writer.write(“Hello”); 写入 String;
2、CharArrayWriter: 可以在内存中创建一个Writer,构造一个缓冲区,可以写入 char,最后得到写入的char[]数组;
CharArrayWriter writer = new CharArrayWriter();
writer.write(65);
writer.write(66);
char[] data = writer.toCharArray();
3、StringWriter : 基于内存的Writer;
OutputStreamWriter
1、Writer 实际上是基于 OutputStream 构造的,接收char, 然后在内部自动转换成byte,并写入 OutputStream;
2、OutputStreamWriter 是一个将任意 OutputStream 转换为 Writer 的转换器;
Writer writer = new OutputStreamWriter(new FileOutputStream(“readme.txt”),“UTF-8”);
PrintStream/PrintWriter
PrintStream
PrintStream 是一种 FilterOutputStream (继承了OutputStream),额外提供了一些写入各种数据类型的方法;
写入int: print(int)
写入 boolean : print(boolean)
写入String:print(String)
写入 Object:print(Object) // 相当于print(object.toString())
…
println() : 会自动加上换行符;
System.out : 是系统默认提供的 PrintStream;
System.err : 是系统默认提供的标准错误输出;
PrintWriter
PrintWriter 扩展了 Writer 接口,它的print() /println() 方法最终输出的是char数据;
StringWriter buffer = new StringWriter();
try(PrintWriter pw = new PrintWriter(buffer)){
pw.prinln("hello");
pw.println(1234);
pw.println(true);
}
System.out.println(buffer.toString());