Java IO流 (Input&Output)
IO的核心组成分为6个部分
一个普通类(File) 、一个接口(Serializable)、四个抽象类(InputStream、OutputStream、Reader、Writer)
一、File类
File类是在整个java.io包里面唯一一个与文件本身有关的操作类, 与文件本身有关指的是这个类可以进行操作文件的路径指派,可以创建或者删除文件,以及还可以获取文件相关的信息内容,在使用File类的时候可以采用如下的构造方法进行实例化
File(String pathname)
通过将给定路径名字符串转换为抽象路径名来创建一个新 File 实例。(设置要操作文件的完整路径,要考虑路径分隔符)
File(File parent, String child)
根据 parent 抽象路径名和 child 路径名字符串创建一个新 File 实例。(设置要操作文件的父目录与子文件路径)
在使用File类指派操作文件的时候该文件的路径有可能不存在,只要不进行各种信息的获取操作,实际上是不会有任何问题,只是表示一个要操作的文件路径。
例:实例化File类对象
public class TestDemo_01 {
public static void main(String[] args) {
File file =new File("d:\\test.txt");
System.out.println(file);
}
}
执行结果:
d:\test.txt
File类常用方法:
boolean createNewFile()
当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。
(创建一个新的文件)
boolean delete()
删除此抽象路径名表示的文件或目录。 (删除文件)
boolean exists()
测试此抽象路径名表示的文件或目录是否存在。
下面基于给定的File类中的方法实现文件的创建与删除,在创建和删除之前进行判断,如果文件不存在则创建,存在则删除。
例:
public class TestDemo_01 {
public static void main(String[] args) {
File file = new File("d:\\test.txt");
if (file.exists()) {
System.out.println("文件存在执行删除操作");
file.delete();
} else {
try {
System.out.println("文件不存在执行创建操作");
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
执行结果:文件不存在执行创建操作
此时虽然可以实现文件的创建与删除,但是在使用File类的时候需要注意两个问题:
***问题一:***当前的程序是进行了磁盘文件的处理操作。
由于java程序距底层接口很远 操作存在延迟 重复提交创建删除代码可能出现文件已存在的情况
问题二:Java本身采用的是跨平台的编程模式,那么这种情况下就必须考虑各个系统的路径问题。
·windows系统的路径分隔符 " \ " ;
·Unix、类Unix(Linux、MacOS、AX)路径分隔符为“/”;
但是在编写路径分隔符的时候如果每一次都编写大量的“\”或者是“/”进行转义处理的时候如果每一次都编写大量的“\”或者是“//”进行转义处理则会显得 非常的麻烦,所以在File类中提供有一个常量 (最早的命名规范和现代的命名是有区别的):
public static final String separator
与系统有关的默认名称分隔符,为了方便,它被表示为一个字符串。此字符串只包含一个字符,即 separatorChar
早期的习惯是在进行路径分割符编写的时候都应该使用“File.separator”进行分隔符的定义
例:正确的路径编写。
File file=new File("d:"+File. separator+"demo. txt");
在以后所编写的代码之中,所有路径分隔符尽量都通过常量来进行定义。
文件目录的操作
当前的程序是直接在“D”盘的根目录下进行了文件的创建,但是在很多的情况下有可能需在一些子目录之中进行文件的创建,那么在java.io包里面,必须保证有父目录的情况下才可以创建子文件。
如果要想进行目录的创建,则需要使用到File类中的如下几个方法:
boolean mkdir()
创建此抽象路径名指定的目录。 (创建单级目录)
boolean mkdirs()
创建此抽象路径名指定的目录,包括所有必需但不存在的父目录。 (创建多级目录,多个子目录可以同时创建)
String getParent()
返回此抽象路径名父目录的路径名字符串;如果此路径名没有指定父目录,则返回 null。 (获取父路径信息)
File getParentFile()
返回此抽象路径名父目录的抽象路径名;如果此路径名没有指定父目录,则返回 null。 (获取父路径的File类对象)
例:进行父目录的创建
public class TestDemo_01 {
public static void main(String[] args) {
File file = new File("d:"+File.separator+"ZZ"+File.separator+"hello"+File.separator+"test.txt");
if(!file.getParentFile().exists()){ //如果父路径不存在
file.getParentFile().mkdirs(); //创建所有的父路径
}
if (file.exists()) {
System.out.println("文件存在执行删除操作");
file.delete();
} else {
try {
System.out.println("文件不存在执行创建操作");
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
执行结果:文件不存在执行创建操作
合理的父目录操作 (多线程优化)
虽然这个时候可以正常的实现了目录下的文件创建,但是如果每一次执行的时候都需要进行判断,那么就会影响程序的性能,所以最好的做法是让这个判断操作只执行一次。
对于当前的环境下可以考虑使用静态代码块的模式来完成,静态代码块的执行优先于主方法执行。
例:修改当前的目录创建操作。
public class TestDemo_01 {
private static File file = new File("d:"+File.separator+"ZZ"+File.separator+"hello"+File.separator+"test.txt");
static { //静态代码块只执行一次
if(!file.getParentFile().exists()){ //如果父路径不存在
file.getParentFile().mkdirs(); //创建所有的父路径
}
}
public static void main(String[] args) {
if (file.exists()) {
System.out.println("文件存在执行删除操作");
file.delete();
} else {
try {
System.out.println("文件不存在执行创建操作");
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
执行结果:文件不存在执行创建操作
在以后编写代码的过程之中,需要考虑各种目录的创建时机,所有代码的性能都是一点一点提升的。
获取文件信息
在File类里面除了提供有基本的文件与目录的创建之外,也提供有一些其它的操作信息,这些操作的方法如下:
boolean canExecute()
测试应用程序是否可以执行此抽象路径名表示的文件。 (是否可执行)
boolean canRead()
测试应用程序是否可以读取此抽象路径名表示的文件。 (是否可读)
boolean canWrite()
测试应用程序是否可以修改此抽象路径名表示的文件。 (是否可写)
File getAbsoluteFile()
返回此抽象路径名的绝对路径名形式。 (获取文件绝对路径实例)
String getName()
返回由此抽象路径名表示的文件或目录的名称。(获取文件或目录名称 **相对名 和相对路径** )
boolean isDirectory()
测试此抽象路径名表示的文件是否是一个目录。
boolean isFile()
测试此抽象路径名表示的文件是否是一个标准文件
long lastModified()
返回此抽象路径名表示的文件最后一次被修改的时间。 (需要格式化)
long length()
返回由此抽象路径名表示的文件的长度。 (需要格式化)
例:
import java.io.File;
import java.text.SimpleDateFormat;
public class TestDemo_01 {
private static File file = new File("d:"+File.separator+"ZZ"+File.separator+"hello"+File.separator+"test.txt");
static { //静态代码块只执行一次
if(!file.getParentFile().exists()){ //如果父路径不存在
file.getParentFile().mkdirs(); //创建所有的父路径
}
}
public static void main(String[] args) {
if (file.exists()) {
System.out.println("[文件是否能执行?]"+file.canExecute());
System.out.println("[文件是否能读?]"+file.canRead());
System.out.println("[文件是否能写?]"+file.canWrite());
System.out.println("[获取文件绝对路径]"+file.getAbsoluteFile());
System.out.println("[获取文件或目录名称]"+file.getName());
System.out.println("[当前路径是否是文件]"+file.isFile());
System.out.println("[当前路径是否是目录]"+file.isDirectory());
System.out.println("[当前文件最后一次被修改的日期是]"+file.lastModified());
System.out.println("[格式化后的日期是]"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(file.lastModified()));
//年 月 日 时 分 秒 毫秒
System.out.println("[获取文件大小]"+file.length());
System.out.println(String.format("[格式化文件大小]%5.2f",file.length()/1024/1024.0)+"MB");
//%5.2f 代表数据总长度为5位 小数点后面四舍五入保留2位 如果整数部分大于三位则自动在最前面补缺占位
}
}
}
执行结果:
[文件是否能执行?]true
[文件是否能读?]true
[文件是否能写?]true
[获取文件绝对路径]d:\ZZ\hello\test.txt
[获取文件或目录名称]test.txt
[当前路径是否是文件]true
[当前路径是否是目录]false
[当前文件最后一次被修改的日期是]1575724712793
[格式化后的日期是]2019-12-07 21:18:32.793
[获取文件大小]3802531
[格式化文件大小] 3.63MB (该文件已被提前手动写入内容方便读取文件大小进行演示)
注意:File类之中的返回的日期long就是毫秒数、而文件大小返回的就是字节个数,一定要使用long数据类型。
获取文件目录信息
使用File可以实现文件或者目录的操作,但是在很多时候有可能会有一些子目录存在。
现在有个需求: 要求列出一个目录内的所有子目录以及这些子目录中所包含的文件
此时就需要使用File类中的一个重要方法
String[] list()
返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。 (描述的是子路径的信息(不包含父目录))
File[] listFiles()
返回一个抽象路径名数组,这些路径名表示此抽象路径名表示的目录中的文件。(列出所有的路径信息(返回File对象数组))
观察两个列出方法的区别
例:观察list()方法的使用
import java.io.File;
import java.util.Arrays;
public class TestDemo_01 {
public static void main(String[] args) {
File file=new File("d:"+File.separator);
System.out.println(Arrays.toString(file.list()));
}
}
执行结果: [$RECYCLE.BIN, dev, Download] (这里只取几个进行比较)
此时只是列出了当前路径下的所有的子路径的信息,子路径可能是一个文件或者是一个目录,不存在父路径。
例:观察listFiles()方法的使用
import java.io.File;
import java.util.Arrays;
public class TestDemo_01 {
public static void main(String[] args) {
File file=new File("d:"+File.separator);
System.out.println(Arrays.toString(file.listFiles()));
}
}
执行结果: [d:$RECYCLE.BIN, d:\dev, d:\Download] (这里只取几个进行比较)
以当前的程序为例,因为一个目录下还可能会包含有若干级的子目录,既然要想全部列出,那么就需要获取完整的路径,所以此时肯定使用listFiles()方法列出会更方便。
例: 使用递归的形式实现路径的列出。
package cn.kinggm.file;
import java.io.File;
public class TestDemo_01 {
public static void main(String[] args) throws Exception{
File file=new File("d:"+File.separator);
listDir(file);
}
public static void listDir(File file){
if(file.isDirectory()) { //当前路径是个目录
File result[] = file.listFiles(); //列出目录组成
if (result != null) { //确定已经列出了内容
for (int i = 0; i < result.length; i++) {
listDir(result[i]);
}
}
System.out.println(file);
}
}
}
注意:
如果此时你执行的操作不是一个输出,而是一个删除 则会删除所有文件 (慎重)
文件更名
在使用File类的时候还有一项功能比较常用,实现文件的更名处理
boolean renameTo(File dest)
重新命名此抽象路径名表示的文件。 (可以为文件对象设置一个更改路径的信息。)
例: 文件更名
package cn.kinggm.file;
import java.io.File;
public class TestDemo_01 {
public static void main(String[] args) throws Exception{
File oldFile=new File("d:"+File.separator+"test.txt");
File newFile=new File("d:"+File.separator+"zzTest.txt");
oldFile.renameTo(newFile);
}
}
执行结果
这种更名的处理操作结合一些实际的开发是非常有用处的,现在假设有如下一个场景。
现有一个叫"zz_info"的目录
存了所有的相关日志的信息定义,而在个目录里面,由于初期的设计失误,导致该目录下的文件存放出现了问题,路径名称不统一。
例: 先创建文件名长度不同的文件若干
package cn.kinggm.file;
import javafx.scene.chart.PieChart;
import java.io.File;
import java.text.SimpleDateFormat;
public class TestDemo_01 {
public static void main(String[] args) throws Exception{
File fileDir=new File("d:"+File.separator+"zz_info");
for (int i = 0; i < 100; i++) {
//假设中间还有许多的文件内容的输出处理,所有的文件数据不是空
new File(fileDir,"log-"+getTimestamp()+"-"+i+".log").createNewFile();
}
}
public static String getTimestamp(){
//生成时间戳
return new SimpleDateFormat("yyyyMMddHHmmssSSS").format(new java.util.Date());
}
}
执行结果:
原始打算让所有的日志文件的名称的长度全部相同,但是由于初期代码设计的朱误缺少了文件名的补0操作,要求将所有的文件名称进行更名,保持相同的长度。
此时目录之中还可能会存在有一些其它的文件的信息。
实现:
package cn.kinggm.file;
import java.io.File;
class DirRenameUtil {
private int max = 0;//保存最大长度
private String maxFileName=null; //保存最大长度文件名称
private int sequenceLength=0; //整体序列的长度
public DirRenameUtil(File file){
this.init(file);
this.rename(file);
}
public void init(File file){ //初始化处理
if (file.isDirectory()) { //是否为目录
File[] result = file.listFiles(); // 列出所有的子路径
for (int i = 0; i < result.length; i++) {
init(result[i]);
}
} else {
if (file.isFile()) {//进行文件修改
if (file.getName().matches("log\\-\\d{17}\\-\\d+\\.log")) { //要修改的路径信息 正则表达式
this.getMaxLength(file.getName());
this.getFileSequenceLength();
}
}
}
}
public void rename(File file) { //需要考虑子目录问题
if (file.isDirectory()) { //是否为目录
File[] result = file.listFiles(); // 列出所有的子路径
for (int i = 0; i < result.length; i++) {
rename(result[i]);
}
} else {
if (file.isFile()) {//进行文件修改
if (file.getName().matches("log\\-\\d{17}\\-\\d+\\.log")) { //要修改的路径信息 正则表达式
String oldFile =file.getName().substring(file.getName().lastIndexOf("-")+1,
file.getName().lastIndexOf("."));
String newFileName = file.getName().substring(0,file.getName().lastIndexOf("-")+1)+
this.getNewFileName(oldFile)+
file.getName().substring(file.getName().lastIndexOf("."));
File newFile = new File("d:"+File.separator+"zz_info"+File.separator,newFileName);
file.renameTo(newFile);
}
}
}
}
public String getNewFileName(String oldName ){ //补0 得到新文件名
StringBuffer buffer=new StringBuffer(oldName);
while (buffer.length()<this.sequenceLength){
buffer.insert(0,0); //补0
}
return buffer.toString();
}
public int getMaxLength(String fileName) {
if (this.max < fileName.length() ){
this.max=fileName.length(); //获取文件名最大长度
this.maxFileName=fileName;
}
return max;
}
public void getFileSequenceLength(){ //获取截取部分最大长度 即文件编号最大长度
this.sequenceLength=this.maxFileName.substring(this.maxFileName.lastIndexOf("-")+1,
this.maxFileName.lastIndexOf(".")).length();
}
}
public class TestDemo_01 {
public static void main(String[] args){
File fileDir = new File("d:" + File.separator + "zz_info");
new DirRenameUtil(fileDir);
}
}
执行结果: