如果希望在程序中操作文件和目录,都可以通过 File 类来完成,不管是文件还是目录都是使用 File 类操作,File 能新建、删除、重命名文件和目录,File 不能访问文件内容本身,如果需要访问文件内容本身,则需要使用输入/输出流。 File 类可以使用文件路径字符串来创建File 实例,该文件路径字符串既可以是绝对路径,也可以是相对路径。默认情况下,系统总是依据用户的工作路径来解释相对路径,这个路径有系统属性“user.dir”指定,通常也是运算 Java 虚拟机时所在的路径。 File 类提供了很多方法来操作文件和目录。
访问文件名相关的方法:
String getName():返回此 File 对象所表示的文件名或者路径名(如果是路径,则返回一级子路径名)。String getPath():返回此 File 对象所对应的路径名。File getAbsoluteFile():返回此 File 对象的绝对路径。String getAbsolutePath():返回此 File 对象的绝对路径名。String getParent():返回此 File 对象所对应目录(最后一级子目录)的父目录名。boolean renameTo(File newName):重命名此 File 对象所对应的文件或者目录,如果重命名成功,则返回 true,否则返回 false。文件检测相关的方法
boolean exists():判断 File 对象所对应的文件或目录是否存在。boolean canWrite():判断 File 对象所对应的文件和目录是否可写。boolean canRead():判断 File 对象所对应的文件和目录是否可读。boolean isFile():判断 File 对象所对应的是否是文件,而不是目录。boolean isDirectory():判断 File 对象所对应的是否是目录,而不是文件。boolean isAbsolute():判断 File 对象所对应的文件或目录是否是绝对路径。获取常规文件信息:
long lastModified():返回文件的最后修改时间。long length():返回文件内容的长度。文件操作相关的方法:
boolean createNewFile():当此 File 对象所对应的文件不存在时,该方法将新建一个该 File 对象所指定的新文件,如果创建成功则返回 true,否则返回 false。boolean delete():删除 File 对象所对应的文件或者路径。static File createTempFile(String prefix,String suffix):在默认的临时文件目录中创建一个临时的空文件,使用给定前缀、系统生成的随机数和给定后缀作为文件名。这是一个静态方法,可以直接通过 File 类来调用。prefix 参数必须至少3字节长。static File createTempFile(String prefix,String suffix,File directory):在 directory 所指定的目录中创建一个临时的空文件,使用给定前缀、系统生成的随机数和给定后缀作为文件名。void deleteOnExit():注册一个测试钩子,指定当 Java 虚拟机退出时,删除 File 对象所对应的文件和目录。目录操作相关的方法:
boolean mkdir():试图创建一个 File 对象所对应的目录,如果创建成功,则返回 true,否则返回 false。调用该方法时 File 对象必须对应一个路径,而不是一个文件。String[] list():列出 File 对象的所有子文件和路径,返回 String 数组。File[] listFiles():列出 File 对象的所有子文件和路径,返回 File 数组。static File[] listRoots():列出系统所有的根路径。File 类中的 list() 方法可以接收一个 FilenameFilter 参数,通过该参数可以只列出符合条件的文件。
Java 的 IO 流是输入/输出的基础,它可以方便地实现数据的输入/输出操作,在 Java 中把不同的输入/输出源(键盘、文件、网络连接等)抽象表述为“流”,通过流的方式允许 Java 程序使用相同的方式类访问不同的输入/输出源。
注意: 上面的基类都是抽象基类,无法直接创建实例。
字节流和字符流的用法几乎完全一样,区别在于操作的数据单元不同。
字节流:操作的数据单元是 8 位的字节,由 InputStream 和OutputStream 作为基类。字符流:操作的数据单元是 816位的字符,由 Reader 和Writer 作为基类。Java 使用处理流来包装节点流是一种典型的装饰器设计模式,通过使用处理流来包装不同的节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入/输出功能。因此,处理流也被称为包装流。
InputStream 和 Reader 是所有输入的流的抽象基类,本身并不能创建实例来执行输入,但它们将成为所有输入流的模板,所以它们的方法是所有输入流都可以使用的方法。
InputStream中的方法:
int read():从输入流中读取单个字节,返回所读取的字节数据。int read(byte[] b):从输入流中最多读取 b.length 个字节的数据,并将其存储在字节数组 b 中,返回实际读取的字节数。int read(byet[] b,int off,int len):从输入流中最多读取 len 个字节的数据,并将其存储在数组 b中,从数组 b 中时,并不是从数组起点开始,而是从 off 位置开始,返回实际读取的字节数。Reader中的方法:
int read():从输入流中读取单个字符,返回所读取的字符数据。int read(char[] cbuf):从输入流中最多读取 cbuf.length 个字符的数据,并将其存储在字符数组 cbuf 中,返回实际读取的字符数。int read(char[] cbuf,int off,int len):从输入流中最多读取 len个字符的数据,并将其存储在字符数组 cbuf 中,放入数组 cbuf 中时,并不是从数组起点开始,而是从 off 位置开始,返回实际读取的字符数。InputStream 和 Reader 都是抽象类,本身不能创建实例,但它们分别有一个用于读取文件的输入流 :FileInputStream 和 FileReader ,它们都是字节流,会直接和文件管理。
使用字节流读取文件,手动关闭资源:
public class test{ public static void main(String[] args) { //创建字节输入流实例,构造器中传入文件绝对路径或者相对路径 FileInputStream fis = new FileInputStream("D:\john\study\java\study\HelloWorld.java"); //创建容器,装入读取的字节数据 byte[] bbuf = new byte[1024]; //用保存读取的字节数 int hasRead = 0; //循环重复读取文件 while((hasRead = fis.read(bbuf)) > 0){ // 将字节转换为字符串输出 System.out.print(new String(bbuf,0,hasRead)); } //关闭文件输入流,最后放在 finally 块中 fis.close(); } }使用字符流,自动关闭资源:
public class test{ public static void main(String[] args) { //创建字符输入流实例,构造器中传入文件绝对路径或者相对路径,并使用try包裹 try(FileReader fis = new FileReader("D:\\john\\study\\java\\study\\HelloWorld.java");){ //创建容器,装入读取的字节数据 char[] bbuf = new char[32]; // 将字节转换为字符串输出 int hasRead = 0; //循环重复读取文件 while((hasRead = fis.read(bbuf)) > 0){ System.out.print(new String(bbuf,0,hasRead)); } }catch (IOException ex){ //打印异常 ex.printStackTrace(); } } }字节流和字符流读取文件并没有太大的差别。只改变了容器和读取类而已。 除此之外,InputStream 和 Reader 还支持几个方法来移动指针。
void mark(int readAheadLimit):在记录指针当前位置记录一个标记(mark)。booleam markSupported():判断此输入流是否支持 mark() 操作,即是否支持记录标记。void reset():将此流的记录针重新定位到上一次记录标记(mark)的位置。long skip(long n):记录指针向前移动 n 个字节/字符。OutputStream 与 Writer 提供的三个方法:
void write(int c):将指定的字节/字符输出到输出流中,其中 c 既可以代表字节,也可以代表字符。void write(byte[] / char[] buf):将字节数组/字符数组中的数据输出到指定输出流中。void write(byte[] / char[] buf ,int off,int len):将字节数组/字符数组中从 off 位置开始,长度为 len 的字节/字符输出到输出流中。因为字符流直接以字符作为操作单位,所以 Writer 可以使用字符串来代替字符数组,即以 String 对象作为参数。Writer 里还包含如下两个方法:
void write(String str):将 str 字符串里包含的字符输出到指定输出流中。void write(String str,int off ,int len):将 str 字符串里从 off 位置开始,长度为 len 的字符输出到指定输出流中。下面程序使用 FileInputStream 来执行输入,并使用 FileOutputStream 来执行输出:
public class test{ public static void main(String[] args) { try( //创建字节输入流 FileInputStream fis = new FileInputStream("D:\\john\\study\\java\\study\\HelloWorld.java"); //创建自己输出流 FileOutputStream fos = new FileOutputStream("D:\\john\\study\\java\\study\\HelloWorld.txt") ){ //创建容器,放入读取的字节 byte[] bbuf = new byte[32]; //记录当前读取的字节数 int hasRead = 0; //循环读取文件取出数据 while((hasRead = fis.read(bbuf)) > 0){ //每读取一次,即写入文件输出流 fos.write(bbuf,0,hasRead); } }catch (IOException ex){ //打印异常,已开启了自动关闭资源,无需手动关闭 ex.printStackTrace(); } } }如果希望直接输出字符串内容,则可以使用 Writer 效果更好。
public class test{ public static void main(String[] args) { try( //创建字符输出流 FileWriter fos = new FileWriter("D:\\john\\study\\java\\study\\HelloWorld.txt") ){ fos.write("如梦令 - 李清照\r\n"); fos.write("昨夜雨疏风骤,\r\n"); fos.write("浓睡不消残酒,\r\n"); fos.write("试问卷帘人,\r\n"); fos.write("却道海棠依旧。\r\n"); fos.write("知否,知否?应是绿肥红瘦。\r\n"); }catch (IOException ex){ //打印异常,已开启了自动关闭资源,无需手动关闭 ex.printStackTrace(); } } }处理流的典型思路是,使用处理流来包装节点流,程序通过处理流来执行输入/输出功能,让节点流与底层的 I/O 设备、文件交互。 实际识别处理流非常简单,只要流的构造器参数不是一个物理节点,而是已经存在的流,那么这种流就一定是处理流。所有的节点流都是直接以物理 IO 节点作为构造器参数的。
处理流的优势有两点:
对开发人员来说,使用处理流进行输入/输出操作更简单。使用处理流的执行效率更高。 public class test{ public static void main(String[] args) { try( //创建字符输出流 FileOutputStream fos = new FileOutputStream("D:\\john\\study\\java\\study\\HelloWorld.txt"); //将上方的输出流作为构造器参数传入 PrintStream ps = new PrintStream(fos); ){ //直接使用 PrintStream 执行输出 ps.println("这就是一个普通的字符串"); //直接使用 PrintStream 输出对象 ps.println(new test()); }catch (IOException ex){ //打印异常,已开启了自动关闭资源,无需手动关闭 ex.printStackTrace(); } } }上面的代码中,使用了 PrintStream 类包装 FileOutputStream,最后使用 PrintStream 输出字符串、输出对象。 注意: PrintStream 的输出功能非常强大,System.out.println(); 的类型就是 PrintStream 。
从代码中可以看出,程序使用处理流非常简单,通常只需要在创建处理流时传入一个节点流作为构造器即可,这样创建的处理就是包装了该节点流的处理流。
ps: 在使用处理流包装了底层节点流之后,关闭输入/输出流资源时,只要关闭最上层的处理流即可。关闭最上层的处理流时,系统会自动关闭该处理流包装的节点流。
通常来说,字节流的功能比字符流的功能强大,因为计算机里的所有数据都是二进制的,而字节流可以处理所有的二进制文件,但问题是,如果使用字节流来处理文本文件,则需要使用合适的方式把字节转换为字符,这就增加了编程的复杂度。所以通常有一个规则,如果输入/输出的内容是文本内容,则应该考虑使用字符流,如果进行输入/输出的内容是二进制内容,则应该考虑使用字节流。
上面程序中,InputStreamReader 将 Systm.in 包装成 BufferedReader ,BufferedReader 具有缓冲功能,它可以一次读取一行文本,以换行符为标志,如果它没有读到换行符,则程序阻塞,等到读取到换行符为止。
PS: 由于 BufferedReader 具有一个 readLine() 方法,可以非常方便的一次读入一行内容,所以经常把读取文本内容的输入流包装成 BufferedReader ,用来方便的读取输入流的文本内容。
在输入/输出流体系中,有两个特殊的流与众不同,即 PushbackInputStream 和 PushbackReader ,它们都提供了三个方法。
void unread(byte[]/char[] buf):将一个字节/字符数组内容推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。void unread(byte[]/char[] b , int off , int len):将一个字节/字符数组里从 off 开始,长度 len 字节/字符的内容推回到推回缓冲区中,从而允许重复读取刚刚读取的内容。void unread(int b):将一个字节/字符推回到推回缓冲区里,从而允许重复读取刚刚读取的内容。这两个推回输入流都有一个推回缓冲区,当程序调用这两个推回输入流的 unread() 方法时,系统将会把指定数组的内容推回到该缓冲区里,而推回输入流每次调用 read() 方法时总是先从推回缓冲区读取,只有完全读取了推回缓冲区的内容后,但还没有装满 read() 所需要的数组时才会总原输入流中读取。
public class test{ public static void main(String[] args) { try( //创建一个 PushbackReader 对象,指定推回缓冲区的长度为 64 PushbackReader pr = new PushbackReader(new FileReader("D:\\john\\study\\java\\study\\HelloWorld.txt"),64); ){ char[] buf = new char[32]; //用以保存上次读取的字符串内容 String lastContent = ""; int hasRead = 0; //循环读取文件内容 while((hasRead = pr.read(buf)) > 0 ){ // 将读取的内容转换成字符串 String content = new String(buf,0,hasRead); int targetIndex = 0; //将上次读取的字符串拼起来,查看是否包含目标字符串 if ((targetIndex = (lastContent + content).indexOf("普通的字符串")) > 0 ){ // 将本次内容和上次内容推回缓冲区 pr.unread((lastContent + content).toCharArray()); //重新定义一个长度为 targetIndex 的 char 数组 if (targetIndex > 32){ buf = new char[targetIndex]; } //再次读取指定长度的内容 pr.read(buf,0,targetIndex); //打印读取的内容 System.out.println(new String(buf,0,targetIndex)); System.exit(0); }else { //打印上次读取的内容 System.out.println(lastContent); //将本次内容设为上次读取的内容 lastContent = content; } } }catch (IOException ex){ //打印异常,已开启了自动关闭资源,无需手动关闭 ex.printStackTrace(); } } }上面程序中 D:\john\study\java\study\HelloWorld.txt 文件中的内容是 “这就是一个普通的字符串!”,这个程序实现了将指定内容退回到缓冲区,于是当程序再次调用 read() 方法是,实际上只是读取了推回到缓冲去的部分内容,从而实现了只打印目标字符串签名内容的功能。这个程序最后打印的内容是 “这就是一个”。
System 类中提供了三个重定向标准输入/输出的方法:
static void setErr(PrintStream err):重定向“标准”错误输出流。static void setIn(InputStream in):重定向“标准”输入流。static void setOut(PrintStream out):重定向“标准”输出流。 public class test{ public static void main(String[] args) { try( //创建 PrintStream 的输入流 PrintStream ps = new PrintStream(new FileOutputStream("D:\\john\\study\\java\\study\\HelloWorld.txt")) ){ //将标准输入重定向到 ps 输出流 System.setOut(ps); //向标准输出输出一个字符串 System.out.println("我是龙傲天!!!"); //向标准输出输出一个对象 System.out.println(new Param()); }catch (IOException ex){ //打印异常,已开启了自动关闭资源,无需手动关闭 ex.printStackTrace(); } } }运行上面的程序,将看不到任何的输出,这意味着标准输出已经不再输出到屏幕中,而是输出到 HelloWorld.txt 文件中。
public class test{ public static void main(String[] args) { try( //创建 FileInputStream 的输入流 FileInputStream fis = new FileInputStream("D:\\john\\study\\java\\study\\HelloWorld.txt"); ){ //将标准输入重定向到 fis 输入流 System.setIn(fis); //使用 System.in 创建 Scanner 对象,用于获取标准输入 Scanner sc = new Scanner(System.in); //设置为只把回车当做分隔符 sc.useDelimiter("\n"); //判断是否还有下一个输入项 while (sc.hasNext()){ //输出输入项 System.out.println("文件中的内容是:" + sc.next()); } }catch (IOException ex){ //打印异常,已开启了自动关闭资源,无需手动关闭 ex.printStackTrace(); } } }运行上面的程序,程序不会等待用户输入,而是直接输出了文件中的内容,这表明程序不再使用键盘作为标准输入,而是使用 HelloWord.txt 文件作为输入源。
RandomAccessFile 是 Java 输入/输出流体系中功能最丰富的文件内容访问类,它提供了众多的方法来访问文件内容,既可以读取文件内容,也可以向文件输出数据。 由于 RandomAccessFile 支持“随机访问”的方式读取数据,也就意味着 RandomAccessFile 可以不从开始的地方开始输出,当然也可以向已经存在的文件追加数据。
long getFilePointer():返回文件记录指针的当前位置。void seek(long pos):将文件指针定位到 pos位置。RandomAccessFile 类有两个构造器,其实这两个构造器基本相同,只是指定文件的形式不通而,一个使用 String 参数来指定文件名,一个使用 File 参数来指定文件本身。除此之外,创建 RandomAccessFile 类还需要指定一个 mode 参数,该参数指定 RandomAccessFile 的访问模式。 ,该参数有如下四个值:
“r”:以只读方式打开指定文件。如果试图对该 RandomAccessFile 执行写入方法,抛出 IO 异常。“rw”:以读、写方式打开指定文件。如果该文件不存在,则尝试创建该文件。“rws”:以读、写方式打开指定文件。相对于“rw”模式,还要求对文件的内容或者元数据的每个更新都同步写入到底层存储设备。“rwd”:以读、写方式打开指定文件。相对于“rw”模式,还要求对文件内容的每个更新都同步到底层存储设备。RandomAccessFile 的读取操作:
public class test{ public static void main(String[] args) { try( //创建 RandomAccessFile 的输入流 RandomAccessFile raf = new RandomAccessFile ("D:\\john\\study\\java\\study\\HelloWorld.txt","r"); ){ //获取 RandomAccessFile 指针的位置,初始位置是0 System.out.println("RandomAccessFile 的文件指针的初始位置:" + raf.getFilePointer()); //移动指针的位置 raf.seek(300); byte[] buf = new byte[1024]; int hasRead = 0; while ((hasRead = raf.read()) > 0){ //从指针位置取出数据 System.out.println(new String(buf,0,hasRead)); } }catch (IOException ex){ //打印异常,已开启了自动关闭资源,无需手动关闭 ex.printStackTrace(); } } }RandomAccessFile 的输出操作:
public class test{ public static void main(String[] args) { try( //创建 RandomAccessFile 的输入流 RandomAccessFile raf = new RandomAccessFile ("D:\\john\\study\\java\\study\\HelloWorld.txt","rw"); ){ //将指针移动到文件的最后 raf.seek(raf.length()); //将字符串转换成 byte 后方可输出 raf.write("追加的内容!\r\n".getBytes()); }catch (IOException ex){ //打印异常,已开启了自动关闭资源,无需手动关闭 ex.printStackTrace(); } } }注意: RandomAccessFile 类可以向文件最后追加内容,但不能向文件中部追加内容,如果这么做,那么文件中部后面的的内容将会被追加的内容覆盖,如果需要向中间添加内容,只能向将会被覆盖的内容存入缓冲区中,如何在中部添加内容,再将缓存区的内容取出来。
public class test{ public static void main(String[] args) { File tmp = File.createTempFile("tmp",null); tmp.deleteOnExit();; try( //创建 RandomAccessFile 的输入流 RandomAccessFile raf = new RandomAccessFile ("D:\\john\\study\\java\\study\\HelloWorld.txt","rw"); //使用临时文件来保存插入后的数据 FileOutputStream tmpOut = new FileOutputStream(tmp); FileInputStream tmpIn = new FileInputStream(tmp); ){ //插入的位置 raf.seek(300); //******************下面代码将插入点后的内容读取到临时文件中保存************* byte[] buf = new byte[64]; int hasRead = 0; while ((hasRead = raf.read(buf)) > 0){ tmpOut.write(buf,0,hasRead); } //******************下面代码将用于插入内容************* //移动指针到插入的位置 raf.seek(300); //插入内容 raf.write("插入的内容!".getBytes()); //追加临时文件中的内容 while ((hasRead = tmpIn.read()) > 0){ raf.write(buf,0,hasRead); } }catch (IOException ex){ //打印异常,已开启了自动关闭资源,无需手动关闭 ex.printStackTrace(); } } }上面的方法中,先创建了用于保存被插入文件的临时文件,程序先将文件中插入点后的内容添加到临时文件中,然后重新定位到插入点,将需要插入的内容添加到文件后面,最后将临时文件的内容添加到文件后面,通过这个过程可以向指定文件、指定位置插入内容。