一、File

java.io提供了File对象来操作文件和目录。构造File对象时,可以传入绝对路径,也可以传入相对路径。

windows用\作为路径分隔符,java需要用\\表示一个\。linux使用/作为路径分隔符。用.表示当前目录,..表示上级目录。

File对象有一个静态变量用来表示当前平台的系统分隔符:separator

File对象有3种形式表示路径:

  • getPath():返回构造方法传入的路径
  • getAbsolutePath():返回绝对路径
  • getCanonicalPath():返回规范路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.example;
import java.io.*;

public class Main {
public static void main(String[] args) {
try {
File file=new File(".\\one.txt");
System.out.println(file.getAbsolutePath());
System.out.println(file.getPath());
System.out.println(file.getCanonicalPath());
}
catch (IOException e) {
throw new RuntimeException("getCanonicalPath()有问题",e);
}
}
}

File对象可以表示文件,也可以表示目录。

  • isFile():是否是一个存在的文件
  • isDirectory():是否是一个存在的目录
  • canRead():是否可读
  • canWrite():是否可写
  • canExecute():是否可执行,对于目录而言,表示能否列出包含的文件和子目录
  • length():文件字节大小
1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example;
import java.io.*;

public class Main {
public static void main(String[] args) {
File file=new File(".\\one.txt");
if(file.isFile()){
System.out.println(File.separator);
}else{
throw new RuntimeException("文件不存在");
}
}
}
  • craeteNewFile():创建文件
  • delete():删除文件,删除目录时只有目录为空才能删除成功
  • createTempFile():创建临时文件
  • deleteOnExit():在JVM退出时自动删除该文件
  • mkdir():创建目录
  • mkdirs():创建目录并且创建不存在的父目录
  • list():列出文件名
  • listFiles():列出文件对象,好分辨哪个是目录,哪个是文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package org.example;
import javax.annotation.processing.FilerException;
import java.io.*;


public class Main {
public static void main(String[] args) throws IOException {
File file=new File(".\\one.txt");
File dir=new File(".\\src\\main\\java\\demo");
File newFile=new File(dir,file.getName());
if(!dir.exists()){
dir.mkdirs();
}
if (!newFile.exists()) {
newFile.createNewFile();
}
File[] files=dir.listFiles();
printFile(files);
}
static void printFile(File[] file){
if(file == null || file.length == 0){
System.out.println("目录为空或不存在");
return;
}
for(File f:file){
try {
System.out.println(f.getCanonicalFile());
} catch (IOException e) {
throw new RuntimeException("路径有问题");
}
}
}
}

Path类

二、OutputStream

write方法会写入一个字节到输出流,虽然传入的是int参数,但只会写入一个字节。

flush()方法将缓冲区的内容输出到目的地。因为向磁盘、网络写入数据的时候,出于效率的考虑,操作系统并不是输出一个字节就立刻写入到文件或者发送到网络,而是把输出的字节先放到内存的一个缓冲区里(本质上就是一个byte[]数组),等到缓冲区写满了,再一次性写入文件或者网络。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.example;
import javax.annotation.processing.FilerException;
import java.io.*;


public class Main {
public static void main(String[] args) throws IOException {
File file=new File(".\\one.txt");
File dir=new File(".\\src\\main\\java\\demo\\");
File newFile=new File(dir,file.getName());
OutputStream out=new FileOutputStream(newFile);
out.write('a');
out.close();
}
}

可以通过重载void wirte(byte[])来一次性写入多个字节。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.example;
import javax.annotation.processing.FilerException;
import java.io.*;


public class Main {
public static void main(String[] args) throws IOException {
File file=new File(".\\one.txt");
File dir=new File(".\\src\\main\\java\\demo\\");
File newFile=new File(dir,file.getName());
OutputStream out=new FileOutputStream(newFile);
out.write("abc".getBytes("UTF-8"));
out.close();
}
}

上述代码如果发生异常就无法正确关闭资源,所以需要用try(resource)来保证无论是否发生IO错误都能正确关闭。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.example;
import javax.annotation.processing.FilerException;
import java.io.*;


public class Main {
public static void main(String[] args){
File file=new File(".\\one.txt");
File dir=new File(".\\src\\main\\java\\demo\\");
File newFile=new File(dir,file.getName());
try(OutputStream out=new FileOutputStream(newFile)) {
out.write("abc".getBytes("UTF-8"));
}catch (IOException e){
e.printStackTrace();
}
}
}

ByteArrayOutputStream

ByteArrayOutputStream实际上是把一个byte[]数组在内存中变成一个 OutputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.example;
import javax.annotation.processing.FilerException;
import java.io.*;


public class Main {
public static void main(String[] args){
try(ByteArrayOutputStream out=new ByteArrayOutputStream()) {
out.write("Hello ".getBytes("UTF-8"));
out.write("World".getBytes("UTF-8"));
byte[] data=out.toByteArray();
System.out.println(new String(data,"UTF-8"));
}catch (IOException e){
e.printStackTrace();
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package org.example;
import javax.annotation.processing.FilerException;
import java.io.*;


public class Main {
public static void main(String[] args){
File file=new File(".\\one.txt");
File dir=new File(".\\src\\main\\java\\demo\\");
File newFile=new File(dir,file.getName());
try(ByteArrayOutputStream out=new ByteArrayOutputStream()) {
out.write("Hello ".getBytes("UTF-8"));
out.write("World".getBytes("UTF-8"));
byte[] data=out.toByteArray();
if(newFile.exists()){
FileOutputStream fis=new FileOutputStream(newFile);
fis.write(data);
}
}catch (IOException e){
e.printStackTrace();
}
}
}

三、InputStream

InpuStream是一个抽象类,是所有输入流的超类。read是该类中最重要的方法。这个方法会读取输入流的下一个字节,并返回字节表示的int值,如果读到末尾会返回-1表示不能继续读取。

FileInputStreamInpuStream的一个子类。

InpuStream提供了两个重载方法来支持读取多个字节:

  • int read(byte[] b):读取若干个字节并填充到byte[]数组,返回读取的字节数
  • int read(byte[] b,int off,int len):指定byte[]数组的偏移量和最大填充数

InputStream也有缓冲区。读取一个字节时,操作系统往往会一次性读取若干字节到缓冲区,并维护一个指针指向未读的缓冲区。每次我们调用read()读取下一个字节时,可以直接返回缓冲区的下一个字节,避免每次读一个字节都导致IO操作。当缓冲区全部读完后继续调用read(),则会触发操作系统的下一次读取并再次填满缓冲区。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package org.example;
import javax.annotation.processing.FilerException;
import java.io.*;


public class Main {
public static void main(String[] args) throws IOException {
File file=new File(".\\one.txt");
File dir=new File(".\\src\\main\\java\\demo\\");
File newFile=new File(dir,file.getName());
try(InputStream in=new FileInputStream(newFile)){
byte[] buffer=new byte[1024];
int len=in.read(buffer);
for(int i=0;i<len;i++){
System.out.print((char)buffer[i]);
}

}
}
}

ByteArrayInputStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.example;
import javax.annotation.processing.FilerException;
import java.io.*;


public class Main {
public static void main(String[] args) throws IOException {
File file=new File(".\\one.txt");
File dir=new File(".\\src\\main\\java\\demo\\");
File newFile=new File(dir,file.getName());
byte[] data = { 72, 101, 108, 108, 111, 33 };
try (InputStream input = new ByteArrayInputStream(data)) {
int n;
while ((n = input.read())!= -1) {
System.out.println((char)n);
}
}
}
}

练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package org.example;
import javax.annotation.processing.FilerException;
import java.io.*;


public class Main {
public static void main(String[] args) throws IOException {
File file=new File(".\\one.txt");
File dir=new File(".\\src\\main\\java\\demo\\");
File newFile=new File(dir,file.getName());
if(!dir.exists()){
dir.mkdirs();
}
if(!newFile.exists()){
newFile.createNewFile();
}
try(OutputStream out=new FileOutputStream(newFile)){
out.write("Hello World".getBytes());
out.flush();
try(InputStream in=new FileInputStream(newFile)){
byte[] buffer=new byte[1024];
int i=in.read();
while(i!=-1){
System.out.print((char)i);
i=in.read();
}
}
}
}
}

四、Filter模式

JDK将InputStreamOutputStream分为两大类,避免过多的子类继承导致子类爆炸:

  • 直接提供数据的基础类,如:FileInputStreamByteArrayInputStreamServletInputStream
  • 提供额外附加功能的类,如:BufferedInputStreamCipherInputStream
1
2
InputStream file = new FileInputStream("test.gz");
InputStream buffered = new BufferedInputStream(file);

不管包装多少次,得到的对象始终是InputStream就可以正常读取。这种通过一个“基础”组件再叠加各种“附加”功能组件的模式,称之为Filter模式或者装饰器模式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
package org.example;
import javax.annotation.processing.FilerException;
import java.io.*;


public class Main {
public static void main(String[] args) throws IOException {
byte[] data="hello world".getBytes("UTF-8");
try(CountInputStream input=new CountInputStream(new ByteArrayInputStream(data))){
int n;
while((n=input.read())!=-1){
System.out.print((char)n);
}
System.out.println("\n"+"总共"+input.getBytesRead()+"个byte");
}
}
}
class CountInputStream extends FilterInputStream {
private int count=0;
CountInputStream(InputStream in) {
super(in);
}
public int getBytesRead(){
return this.count;
}
public int read() throws IOException {
int n=in.read();
if(n!=-1){
this.count++;
}
return n;
}
public int read(byte[] b) throws IOException {
int n=in.read(b);
if(n!=-1){
this.count+=n;
}
return n;
}
}

序列化

序列化必须实现java.io.Serializable接口。序列化本质就是将一个java对象转化为byte[]数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package org.example;
import javax.annotation.processing.FilerException;
import java.io.*;
import java.util.Arrays;

public class Main {
public static void main(String[] args) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try(ObjectOutputStream oos = new ObjectOutputStream(baos)){
oos.writeInt(12345);
oos.writeObject(Double.valueOf(123.345));
byte[] bytes = baos.toByteArray();
System.out.println(Arrays.toString(bytes));
}
}
}

五、Reader

是java的IO库中提供的另一个输入流接口。与InputStream的区别在于:

  • IntputStream是一个字节流,以byte为单位读取;
  • Reader是一个字符流,以char为单位读取。

FileReaderReader的一个子类,可以打开文件并获取Reader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package org.example;
import javax.annotation.processing.FilerException;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public class Main {
public static void main(String[] args) throws IOException {
try(Reader reader=new FileReader(".\\src\\main\\java\\demo\\one.txt", StandardCharsets.UTF_8)) {
char[] buffer=new char[1024];
int len=reader.read(buffer);
for(int i=0;i<len;i++) {
System.out.print(buffer[i]);
}
}
}
}

CharArrayReader

ByteArrayInputStream类似。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package org.example;
import javax.annotation.processing.FilerException;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public class Main {
public static void main(String[] args) throws IOException {
char[] body="hello world".toCharArray();
try(Reader reader=new CharArrayReader(body)){
char[] chars=new char[body.length];
int len=reader.read(chars);
for(int i=0;i<len;i++){
System.out.print(chars[i]);
}
}
}
}

StringReader可以直接把String作为数据源。

InputStreamReader

除了CharArrayReaderStringReader,普通的Reader都是基于InputStream构造的,都需要从InputStream中读入byte,再根据编码设置转换为char

InputStreamReader可以把任何InputStream转换为Reader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package org.example;
import javax.annotation.processing.FilerException;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public class Main {
public static void main(String[] args) throws IOException {
byte[] bytes = "Hello World".getBytes(StandardCharsets.UTF_8);
InputStream input=new ByteArrayInputStream(bytes);
Reader reader=new InputStreamReader(input, StandardCharsets.UTF_8);
char[] buf=new char[bytes.length];
int len=reader.read(buf);
for(int i=0;i<len;i++){
System.out.print(buf[i]);
}
reader.close();
input.close();
}
}

六、PrintStream和PrintWriter

PrintStream是一种FilterOutputStream,在OutputStream的接口上额外提供了一些写入各种数据类型的方法。

System.out是系统默认提供的PrintStreamSystem.err是系统默认提供的标准错误输出。

PrintStreamOutputStream相比,除了添加了一组print() / println()方法,可以打印各种数据类型,比较方便外,它还有一个额外的优点,就是不会抛出IOException,这样我们在编写代码的时候,就不必捕获IOException

1
2
3
4
5
6
7
8
9
10
11
12
package org.example;
import javax.annotation.processing.FilerException;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public class Main {
public static void main(String[] args) throws IOException {
PrintStream out = new PrintStream(System.out, true, "UTF-8");
out.println(123);
}
}

PrintStream最终输出的总是byte数据,而PrintWriter则是扩展了Writer接口,它的print() / println()方法最终输出的是char数据。两者的使用方法几乎是一模一样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.example;
import javax.annotation.processing.FilerException;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

public class Main {
public static void main(String[] args) throws IOException {
StringWriter sw = new StringWriter();
try(PrintWriter pw = new PrintWriter(sw)){
pw.println("Hello World");
}
System.out.println(sw);
}
}

七、Files与Paths

Paths

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package org.example;
import java.io.*;
import java.nio.file.*;


public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
//创建路径
Path path=Paths.get("src","java","demo\\two.txt");
Path path1=Paths.get("src\\main\\java\\demo\\two.txt");
Path path2=Paths.get("D:\\IDEA\\test\\IO\\src\\main\\java\\demo\\two.txt");

//拼接路径
Path path3=Paths.get("D:\\IDEA\\test\\IO");
Path path4=path3.resolve("src\\main\\java\\demo\\two.txt");
Path path5=path3.resolve(path1);

//计算两个路径之间的距离,路径类型要一致,如:两个都是相对路径
Path path6=Paths.get("D:\\");
Path path7=path6.relativize(path2);
}
}

获取路径信息:

调用方法 在 Solaris 操作系统中返回 在 Microsoft Windows 中返回 说明
toString /home/joe/foo C:\home\joe\foo 返回的字符串表示形式 。如果使用 Filesystems.getDefault().getPath(String) 或 Paths.get(后者是一种方便的 getPath 方法)创建路径,则该方法将执行较小的语法清理。例如,在 UNIX 操作系统中,它会将输入字符串 “//home/joe/foo” 更正为 “/home/joe/foo”
getFileName foo foo 返回名称元素序列的文件名或最后一个元素
getName(0) home home 返回与指定索引对应的路径元素。第0个元素是最靠近根的路径元素
getNameCount 3 3 返回路径中的元素数
subpath(0,2) home/joe home\joe 返回Path由开始和结束索引指定的(不包括根元素)的子序列
getParent /home/joe \home\joe 返回父目录的路径
getRoot / C:\ 返回路径的根

路径转换

  • Path toAbsolutePath():将相对路径转换为绝对路径。
  • Path normalize():规范化路径,去除冗余的名称元素,如 "."".."
  • Path toRealPath():路径必须真实存在且能完全解析,才会返回路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package org.example;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.file.*;


public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
Path path=Paths.get(".\\src\\main\\java\\demo\\one.txt");
Path parent=path.getParent();
if(parent!=null&&Files.notExists(parent)){
Files.createDirectories(parent);
}
if(Files.notExists(path)){
Files.createFile(path);
}
try(Writer writer=new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path.toFile()),Charset.forName("UTF-8")))) {
String body= """
Hello World!
How are you?
""";
writer.write(body);
}


}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package org.example;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;

public class Main {
public static void main(String[] args) throws IOException {
File file=new File(".\\src\\main\\java\\demo\\one.txt");
byte[] data= Files.readAllBytes(file.toPath());
System.out.println(data);
String dataString=Files.readString(file.toPath(), StandardCharsets.UTF_8);
System.out.println(dataString);
}
}

Files

创建和删除

  • static Path createFile(Path path, FileAttribute<?>... attrs):创建一个新文件。
  • static Path createDirectory(Path dir, FileAttribute<?>... attrs):创建一个新目录。
  • static Path createDirectories(Path dir, FileAttribute<?>... attrs):递归地创建目录,包括不存在的父目录。
  • static void delete(Path path):删除指定的文件或目录。如果路径是目录,则目录必须为空。
  • static boolean deleteIfExists(Path path):删除指定的文件或目录,如果存在的话。
  • exists(Path path, LinkOption... options):检查文件或目录是否存在。
  • notExists(Path path, LinkOption... options):检查文件或目录是否存在。

复制文件或目录

public static Path copy( Path source,Path target,CopyOption ... options):复制文件或目录。如果目标文件存在,则复制失败,除非指定REPLACE_EXTSTING选项。

public static long copy(InputStream in,Path target,CopyOption... options):将所有字节从输入流复制到文件。

public static long copy(Path source,OutputStream out):将文件中所有字节复制到输出流。

移动文件或目录

public static Path copy(Path source,Path target,CopyOption ... options):移动文件或目录。

此方法采用varargs参数 - 支持以下StandardCopyOption枚举:

  • REPLACE_EXISTING - 即使目标文件已经存在,也执行移动。如果目标是符号链接,则替换符号链接,但指向的内容不受影响。
  • ATOMIC_MOVE - 作为原子文件操作执行移动。如果文件系统不支持原子移动,则抛出异常。使用 ATOMIC_MOVE 您可以将文件移动到目录中,并确保观看目录的任何进程访问完整的文件。

文件和文件存储属性

方法 说明
size(Path) 以字节为单位返回指定文件的大小。
isDirectory(Path, LinkOption) 如果指定 Path 的文件是目录,则返回 true 。
isRegularFile(Path, LinkOption...) 如果指定 Path 的文件是常规文件,则返回 true 。
isSymbolicLink(Path) 如果指定的 Path 位置是一个符号链接的文件,则返回 true 。
isHidden(Path) 如果指定 Path 的文件系统被视为隐藏的文件,则返回 true 。
getLastModifiedTime(Path, LinkOption...) setLastModifiedTime(Path, FileTime) 返回或设置指定文件的上次修改时间。
getOwner(Path, LinkOption…) setOwner(Path, UserPrincipal) 返回或设置文件的所有者。
getPosixFilePermissions(Path, LinkOption...) setPosixFilePermissions(Path, Set<PosixFilePermission>) 返回或设置文件的 POSIX 文件权限。
getAttribute(Path, String, LinkOption...) setAttribute(Path, String, Object, LinkOption...) 返回或设置文件属性的值。

读、写

  • readAllBytes
  • readAllLines
  • public static Path write(Path path,byte[] bytes,OpenOption... options)
  • public static Path write(Path path,Iterable<? extends CharSequence> lines,Charset cs,OpenOption... options)

options选项:APPEND表示追加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package org.example;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
Path path=Paths.get(".\\src\\main\\java\\demo\\one.txt");
Path parent=path.getParent();
if(parent!=null&&Files.notExists(parent)){
Files.createDirectories(parent);
}
if(Files.notExists(path)){
Files.createFile(path);
}
byte[] body="直接用write写入".getBytes();
//覆盖写入单行
Files.write(path,body);
//覆盖写入多行
List<? extends CharSequence> list= Arrays.asList("first","second","third");
Files.write(path,list, StandardCharsets.UTF_8);
}
}

无缓冲流

public static BufferedReader newBufferedReader(Path path,Charset cs)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package org.example;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


public class Main {
public static void main(String[] args) throws IOException, InterruptedException {
Path path=Paths.get(".\\src\\main\\java\\demo\\one.txt");
Path parent=path.getParent();
if(parent!=null&&Files.notExists(parent)){
Files.createDirectories(parent);
}
if(Files.notExists(path)){
Files.createFile(path);
}
try(InputStream inputStream=Files.newInputStream(path)){
BufferedReader br=new BufferedReader(new InputStreamReader(inputStream,StandardCharsets.UTF_8));
String line=null;
while((line=br.readLine())!=null){
System.out.println(line);
}
}
}
}

八、URL