一、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(); } } }
|
InpuStream是一个抽象类,是所有输入流的超类。read是该类中最重要的方法。这个方法会读取输入流的下一个字节,并返回字节表示的int值,如果读到末尾会返回-1表示不能继续读取。
FileInputStream是InpuStream的一个子类。
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]); } } } }
|
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将InputStream和OutputStream分为两大类,避免过多的子类继承导致子类爆炸:
- 直接提供数据的基础类,如:
FileInputStream、ByteArrayInputStream、ServletInputStream等
- 提供额外附加功能的类,如:
BufferedInputStream、CipherInputStream等
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为单位读取。
FileReader是Reader的一个子类,可以打开文件并获取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作为数据源。
除了CharArrayReader与StringReader,普通的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是系统默认提供的PrintStream,System.err是系统默认提供的标准错误输出。
PrintStream和OutputStream相比,除了添加了一组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