一、File类
File类是在整个java.io包之中唯一一个与文件本身操作有关的类,文件本身操作指的是文件的创建、删除、重命名等,但是如果要进行File类操作那么必须设置好要操作的文件或文件夹的路径,使用如下构造方法:
· 构造方法:public File(String pathname),传入完整的路径,WEB开发此方式比较好用;
· 构造方法:public File(File parent, String child),传入父路径和子路径。
范例:基本的文件操作
· 创建新的文件:public boolean createNewFile() throws IOException;
· 删除文件:public boolean delete();
· 判断文件是否存在:public boolean exists()。
package cn.mldn.demo; import java.io.File; public class TestDemo { public static void main(String[] args) throws Exception { File file = new File("D:\\demo.txt") ; if (file.exists()) { // 文件存在 file.delete() ; } else { file.createNewFile() ; } } } |
此时的程序的确完成了所有的文件基本操作,但是在本程序之中依然会存在有如下的几个问题。
问题一:Java的最大特征是可移植性,但是如果是文件编程此处就需要考虑一个平台问题,在windows之中使用“\”作为路径分割符,而在Linux中使用“/”作为路径分割符,那么在实际的工作之中,往往都会在windows下做开发,而后将项目部署到Linux之中,所以来讲就不能够将路径的分隔符写固定了,为此在File类中提供了一个常量:
public static final String separator |
如果按照命名规范来讲,separator应该使用大写字母表示,但是此处是小写字母,原因很简单 —— 历史原因。
File file = new File("D:" + File.separator + "demo.txt"); |
问题二:在程序执行完成之后文件的并不会立刻删除或者是创建,存在有一定的延迟,因为Java程序是通过JVM间接的调用系统函数实现的文件操作。
问题三:如果在进行文件创建的时候有目录,则需要先创建目录之后才可以创建文件。
· 找到父路径:public File getParentFile();
· 创建目录:public boolean mkdirs();
package cn.mldn.demo; import java.io.File; public class TestDemo { public static void main(String[] args) throws Exception { File file = new File("D:" + File.separator + "hello" + File.separator + "test" + File.separator + "haha" + File.separator + "demo.txt"); if (!file.getParentFile().exists()) { // 父路径不存在 file.getParentFile().mkdirs() ; } if (file.exists()) { // 文件存在 file.delete() ; } else { file.createNewFile() ; } } } |
除了以上的基本的文件和文件夹的操作之外,也提供有一些取得文件信息的操作方法:
· 判断路径是否是文件:public boolean isFile();
· 判断路径是否是文件夹:public boolean isDirectory();
· 最后一次修改日期:public long lastModified();
· 取得文件大小:public long length();
· 修改文件名称:public boolean renameTo(File dest)。
package cn.mldn.demo; import java.io.File; import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.util.Date; public class TestDemo { public static void main(String[] args) throws Exception { File file = new File("D:" + File.separator + "my.jpg"); if (file.exists()) { // 文件存在 System.out.println(file.isFile() ? "是文件" : "不是文件"); System.out.println(file.isDirectory() ? "是文件夹" : "不是文件夹"); System.out.println("最后一次修改日期:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS") .format(new Date(file.lastModified()))); System.out.println("文件大小:" + new BigDecimal((file.length() / (double) 1024 / 1024)) .divide(new BigDecimal(1), 2, BigDecimal.ROUND_HALF_UP) + "M"); file.renameTo(new File("D:" + File.separator + "hello.jpg")); } } } |
如果说现在给定的路径是一个文件夹,那么如果是文件夹则里面应该会包含有许多的文件或子文件夹,那么现在就可以利用以下的方法列出目录之中的所有内容:
· 列出目录内容:public File[] listFiles()。返回的是一个对象数组
范例:列出目录内容
package cn.mldn.demo; import java.io.File; public class TestDemo { public static void main(String[] args) throws Exception { File file = new File("D:" + File.separator + "testjava"); if (file.exists() && file.isDirectory()) { File result [] = file.listFiles() ; // 列出全部内容 for (int x = 0; x < result.length; x++) { System.out.println(result[x]); } } } } |
但是此时列出来的只是当前目录级别中的数据。
范例:列出一个目录之中的所有文件(包括所有子目录中的文件)
基本原则:首先要判断给定的File类对象是否是目录,如果是目录则继续列出,那么随后循环列出的File类数组,后依次进行判断是否是目录。这样的实现最好使用递归完成。
package cn.mldn.demo; import java.io.File; public class TestDemo { public static void main(String[] args) throws Exception { File file = new File("D:" + File.separator); print(file); } public static void print(File file) { if (file.exists() && file.isDirectory()) { File result[] = file.listFiles(); // 列出全部内容 if (result != null) { for (int x = 0; x < result.length; x++) { print(result[x]); // 递归调用 } } } System.out.println(file); } } |
如果此时的程序之中对于文件的路径不是输出而是执行删除呢?
二、字节流与字符流
使用File类只能够实现文件本身的操作,但是与文件内容的操作无关,如果要想进行文件内容操作的话则可以使用以下两组流完成:
· 字节流:InputStream、OutputStream;
· 字符流:Reader、Writer。
但是不管使用何种流,基本的操作流程是一样的,以文件操作为例;
· 确定操作文件的路径;
· 通过字节流或字符流的子类为字节流和字符流类对象实例化;
· 进行输入、输出的操作;
· 关闭流,流属于资源操作,资源操作完成一定要关闭。
1、字节输出流:OutputStream
java.io.OutputStream是可以进行字节数据(byte)的输出,这个类的定义结构如下:
public abstract class OutputStream extends Object implements Closeable, Flushable |
首先可以发现在OutputStream类之中实现了两个接口:
Closeable:JDK 1.5后提供、 |
Flushable:JDK 1.5后提供 |
public interface Closeable extends AutoCloseable |
public interface Flushable |
public void close() throws IOException |
public void flush() throws IOException |
从实际的开发来讲,对于Closeable和Flushable两个接口是属于后来再次抽象的产物,本身的创建意义是希望将所有存在有关闭资源操作的类进行统一的关闭管理(这类操作有些多余了),而在OutputStream类里面也定义有close()和flush()两个方法,而且这两个方法已经存在很多年了,那么在开发的时候就不会再去关注Closeable、Flushable两个接口了。
在OutputStream类之中存在有三个write()方法:
· 输出单个字节:public abstract void write(int b) throws IOException;
· 输出全部的字节:public void write(byte[] b) throws IOException;
· 输出部分字节:public void write(byte[] b, int off, int len) throws IOException。
但是OutputStream只是一个抽象类,所以如果要想取得本类的实例化对象,那么就必须利用子类进行实例化,本次操作将要进行的是文件操作,所以可以使用FileOutputStream子类。这个类定义了两个常用构造方法:
· 构造方法:public FileOutputStream(File file) throws FileNotFoundException,覆盖;
· 构造方法:public FileOutputStream(File file, boolean append) throws FileNotFoundException,追加。
范例:进行文件的输出操作
package cn.mldn.demo; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; public class TestDemo { public static void main(String[] args) throws Exception { File file = new File("D:" + File.separator + "test" + File.separator + "demo.txt"); // 通过File类定义要输出的路径 if (!file.getParentFile().exists()) { file.getParentFile().mkdirs() ; } // 通过OutputStream的子类对象为父类对象实例化 OutputStream output = new FileOutputStream(file) ; String msg = "Hello World ."; // 要输出的数据 byte data [] = msg.getBytes() ; // 将字符串变为字节数组 output.write(data); // 将字节数组数据输出 output.close();// 资源的对象一定要关闭 } } |
但是此时不管执行几次本程序,对于文件中的内容都只是一个覆盖。如果不希望出现覆盖,则可以使用追加的方式创建FileOutputStream类对象。
// 通过OutputStream的子类对象为父类对象实例化,追加内容 OutputStream output = new FileOutputStream(file, true); |
String msg = "Hello World .\r\n"; // 要输出的数据 |
以上是将内容全部进行了输出,那么也可以设置输出的范围。
output.write(data, 0, 5); // 将字节数组数据输出 |
如果觉得一组字节数据输出麻烦,那么也可以采用循环的方式进行单个字节的输出。
范例:单个字节
package cn.mldn.demo; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; public class TestDemo { public static void main(String[] args) throws Exception { File file = new File("D:" + File.separator + "test" + File.separator + "demo.txt"); // 通过File类定义要输出的路径 if (!file.getParentFile().exists()) { file.getParentFile().mkdirs() ; } // 通过OutputStream的子类对象为父类对象实例化 OutputStream output = new FileOutputStream(file); String msg = "Hello World ."; // 要输出的数据 for (int x = 0; x < msg.length(); x++) { output.write(msg.charAt(x)); } output.close();// 资源的对象一定要关闭 } } |
严格来讲通过charAt()方法返回的数据是字符数据,但是之所以可以直接使用writer()输出,是因为在writer()方法里面接收的是int型数据,而int型数据可以接收char或byte。不过从习惯上来讲,还是更加愿意使用字节数据完成。
byte data [] = msg.getBytes() ; for (int x = 0; x < data.length; x++) { output.write(data[x]); } |
在整个OutputStream类之中最为重要的输出方法:输出部分字节数句。
2、字节输入流:InputStream
使用OutputStream可以完成程序向文件的输出,而现在要通过程序读取文件内容,则必须采用InputStream类完成,那么此类的定义如下:
public abstract class InputStream extends Object implements Closeable |
InputStream类依然是Closeable接口的子类,但是由于InputStream类本身一直存在有close()方法,所以在使用时可以忽略掉Closeable。在InputStream类之中定义有三个read()方法:
· 读取单个字节:public abstract int read() throws IOException;
|- 每一次使用read()操作将读取一个字节数据,此时返回的是数据,如果数据已经读取完了,则int返回-1;
· 读取内容到字节数组:public int read(byte[] b) throws IOException;
|- 将内容读取到字节数组之中,返回读取的个数,如果读取完毕,则返回的是-1;
· 读取内容到部分字节数组:public int read(byte[] b, int off, int len) throws IOException;
|- 将指定长度的内容读取到字节数组之中,返回读取个数,如果读取完毕,返回-1。
但是InputStream类属于抽象类,抽象类如果要实例化则使用它的子类。那么可以使用FileInputStream子类完成,此类只关心构造方法:public FileInputStream(File file) throws FileNotFoundException。
范例:使用InputStream读取数据
package cn.mldn.demo; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; public class TestDemo { public static void main(String[] args) throws Exception { File file = new File("D:" + File.separator + "test" + File.separator + "demo.txt"); // 通过File类定义要输出的路径 if (file.exists()) { // 要操作的文件存在 InputStream input = new FileInputStream(file) ; // 实例化输入流对象 byte data[] = new byte[1024];// 此数组用于接收全部输入的数据 int len = input.read(data) ; // 将数据保存在数组之中 System.out.println("【" + new String(data, 0, len) + "】"); input.close() ; } } } |
此时是将整个数组都填充满了数据,那么下面使用部分读取。
int len = input.read(data,0,10) ; // 将数据保存在数组之中 |
如果此时部分的数据读取完毕,那么其它的数据将不再读取。
而在整个InputStream类之中最为重要的就是进行单个字节数据的读取操作,而且代码的难度也是最高的。如果单独使用read()每次读取一个字节,如果读取完毕返回的是-1。
范例:使用循环的方式读取单个字节数据(了解)
package cn.mldn.demo; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; public class TestDemo { public static void main(String[] args) throws Exception { File file = new File("D:" + File.separator + "test" + File.separator + "demo.txt"); // 通过File类定义要输出的路径 if (file.exists()) { // 要操作的文件存在 InputStream input = new FileInputStream(file) ; // 实例化输入流对象 byte data[] = new byte[1024];// 此数组用于接收全部输入的数据 int temp = 0 ; // 接收每次读取进来的数据 int foot = 0 ; // 定义数组的脚标 do { temp = input.read() ; // 读取单个字节 if (temp != -1) { // 读取的是真实数据 data[foot++] = (byte) temp; // 保存数据 } } while(temp != -1) ; // 如果没有读取到尾,那么继续读 input.close() ; System.out.println(new String(data, 0, foot)); } } } |
以上的程序使用的是do…while操作完成,而在开发之中是不建议使用do…while操作,都使用while代替,而且本操作之中是不确定循环次数,但是知道循环结束条件,一定使用while。
范例:使用while循环读取
package cn.mldn.demo; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; public class TestDemo { public static void main(String[] args) throws Exception { File file = new File("D:" + File.separator + "test" + File.separator + "demo.txt"); // 通过File类定义要输出的路径 if (file.exists()) { // 要操作的文件存在 InputStream input = new FileInputStream(file) ; // 实例化输入流对象 byte data[] = new byte[1024];// 此数组用于接收全部输入的数据 int temp = 0 ; // 接收每次读取进来的数据 int foot = 0 ; // 定义数组的脚标 // 组成一:temp = input.read(),表示读取单个的字节; // 组成二:(temp = input.read()) != -1,表示判断temp的内容是否是-1,如果不是,则继续读 while ((temp = input.read()) != -1) { data[foot++] = (byte) temp; } input.close() ; System.out.println(new String(data, 0, foot)); } } } |
以上的写法一定要习惯,日后进行读取的时候都会采用类似的形式完成。
3、字符输出流:Writer
InputStream和OutputStream两个类是在JDK 1.0的时候引入的,但是在JDK 1.1之后为了方便操作,又提供了一组字符操作流(Writer、Reader),字节输出流和字符输出流最大的区别在于,字节输出流是以byte类型为主的,而字符输出流是以char类型为主的,而且支持String的直接操作。下面首先来观察Writer类的继承结构:
public abstract class Writer extends Object implements Appendable, Closeable, Flushable |
在Writer类里面提供有一个最为重要的操作方法:
· 输出字符串:public void write(String str) throws IOException。
· 输出字节数组:public void write(char[] cbuf) throws IOException。
但是Writer是一个抽象类,如果要使用它进行文件操作必须使用FileWriter子类。
范例:使用Writer类实现内容的输出
package cn.mldn.demo; import java.io.File; import java.io.FileWriter; import java.io.Writer; public class TestDemo { public static void main(String[] args) throws Exception { File file = new File("D:" + File.separator + "test" + File.separator + "demo.txt"); // 通过File类定义要输出的路径 if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } Writer out = new FileWriter(file); String msg = "Hello World ."; out.write(msg); // 直接输出字符串 out.close(); } } |
通过这一操作就可以清楚的发现,使用字符输出流在进行字符串数据输出的时候还是非常方便的。
4、字符输入流:Reader
Reader是负责进行字符数据读取的,此类定义如下:
public abstract class Reader extends Object implements Readable, Closeable |
同时在这个类之中可以使用read()方法读取数据,但是没有可以直接返回String类型的读取操作,可以利用字符数组:
· 读取数据:public int read(char[] cbuf) throws IOException;
范例:读取数据
package cn.mldn.demo; import java.io.File; import java.io.FileReader; import java.io.Reader; public class TestDemo { public static void main(String[] args) throws Exception { File file = new File("D:" + File.separator + "test" + File.separator + "demo.txt"); // 通过File类定义要输出的路径 if (file.exists()) { // 文件存在 Reader in = new FileReader(file); char data[] = new char[1024]; int len = in.read(data); System.out.println(new String(data, 0, len)); in.close() ; } } } |
两种读取的操作本质上讲区别不大,只是字符流操作的都是char/String,而字节流只是byte。
5、字节流和字符流的区别
回顾:BLOB、CLOB数据类型。CLOB保存大文本数据,而BLOB保存大文本、图片、音乐等二进制数据。那么对于字节流和字符流也是一样,如果真的进行选择的话,可以给出如下的参考意见:
· 字符流:当程序处理中文的时候,字符流是最方便的;
· 字节流:当程序处理二进制数据(图片、音乐、电影)或进行网络传输,或者保存到磁盘数据一定都是字节;
除了以上的区别之外,字节流在进行操作的时候是直接与操作终端进行交互,而字符流需要经过缓冲区的处理后才可以进行操作,以OutputStream和Writer两个类输出文件为例,如果OutputStream输出的最后可以不关闭输出流,但是如果是Writer类输出如果没有关闭,那么保存在缓冲之中的数据将无法输出,或者强制性刷新缓冲区。
package cn.mldn.demo; import java.io.File; import java.io.FileWriter; import java.io.Writer; public class TestDemo { public static void main(String[] args) throws Exception { File file = new File("D:" + File.separator + "test" + File.separator + "demo.txt"); // 通过File类定义要输出的路径 if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } Writer out = new FileWriter(file); String msg = "Hello World ."; out.write(msg); // 直接输出字符串 out.flush();// 强制刷新缓冲区 } } |
所谓的缓冲实际上是一块内存,当数据读取进来之后会先进入到此内存区域之中进行处理,所以才可以更好的处理中文,所以日后在进行选择的时候,大部分情况都以字节流为主。
三、转换流
那么现在既然存在有字节和字符两种操作流,那么这两种流之间也是可以进行互相转换的,主要使用两个类:InputStreamReader、OutputStreamWriter。这两个类继承结构和构造方法如下:
InputStreamReader: |
OutputStreamWriter: |
public class InputStreamReader extends Reader |
public class OutputStreamWriter extends Writer |
public InputStreamReader(InputStream in) |
public OutputStreamWriter(OutputStream out) |
InputStreamReader是Reader的子类,所以InputStreamReader类对象可以自动转型为Reader类实例。
OutputStreamWriter是Writer的子类,所以OutputStreamWriter类对象可以自动转型为Writer类实例。
范例:观察转换
package cn.mldn.demo; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; public class TestDemo { public static void main(String[] args) throws Exception { File file = new File("D:" + File.separator + "test" + File.separator + "demo.txt"); // 通过File类定义要输出的路径 if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } OutputStream output = new FileOutputStream(file) ; // 字节输出流 Writer out = new OutputStreamWriter(output) ; // 字节流变为字符流 out.write("Hello World ."); out.close(); } } |
但是以上的代码存在的意义不大,而之所以介绍转换流主要的目的也在于两组类的继承结构上。
· 观察一:FileInputStream和FileOutputStream类的继承结构
FileInputStream: |
FileOutputStream: |
java.lang.Object java.io.InputStream java.io.FileInputStream |
java.lang.Object java.io.OutputStream java.io.FileOutputStream |
· 观察二:FileReader和FileWriter类的继承结构。
FileReader: |
FileWriter: |
java.lang.Object java.io.Reader java.io.InputStreamReader java.io.FileReader |
java.lang.Object java.io.Writer java.io.OutputStreamWriter java.io.FileWriter |
通过继承关系可以清楚的发现,所有的字符流数据实际上都经过了转换。