I/O
一、File类的介绍与使用
存储在程序中的数据是暂时的,当程序终止时它们就会丢失。为了能够永久的保存程序中创建的数据,需要将它们存储到磁盘或其它永久存储设备的文件中。这样,这些文件其后可以被其它程序传送和读取。
在文件系统中,每个文件都存放在一个目录下。绝对文件名(absolute file name)是由它的完整路径和文件名组成的(例如,D:/home/Welcome.java,这里D:/home称为改文件的目录路径)。相对文件名是相对于当前工作目录的。对于相对文件名而言,完整目录被忽略(例如,Welcome.java是一个相对文件名,如果当前工作目录为D:/home,绝对文件名将是D:/home/Welcome.java)。
File类提供了文件和目录路径名的抽象表示。包含许多获取文件属性的方法,以及重命名和删除文件和目录的方法。(File类不包含读写文件内容的方法)
File(pathname: String) 为指定的路径名创建一个File对象。路径名可能是一个目录或者一个文件
File(parent: String, child: String) 在目录parent下创建一个子路径的File对象。子路径可能是一个目录或者一个文件
File(parent: File, child: String) 在目录parent下创建一个子路径的File对象(目录parent是一个File对象)。子路径可能是一个目录或者一个文件
exists(): boolean File对象代表的文件是否存在
canRead(): boolean File对象代表的文件存在且可读,返回true
canWrite(): boolean File对象代表的文件存在且可写,返回true
isDirectory(): boolean File对象代表的文件是否是一个目录
isFile(): boolean File对象代表的文件是否是一个文件
isAbsolute(): boolean File对象代表的文件是否采用绝对路径名创建
isHidden(): boolean File对象代表的文件是否隐藏
getAbsolutePath(): String 获取File对象代表的文件的绝对路径
getCanonicalPath(): String 和getAbsolutePath()相同。从路径中去掉冗余的名字(比如'.'和'..'),将盘符转换为标准的大写形式
getName(): String 获取文件名
getPath(): String 获取文件完整的目录和文件名
getParent(): String 获取文件的完整父目录
lastModified(): long 返回文件最后修改时间
length(): long 获取文件大小。如果不存在或者是一个目录,返回0
listFile(): File[] 返回一个目录下面的文件
delete(): boolean 删除文件,删除成功返回true。(如果目录不为空,不能成功删除)
renameTo(dest: File): boolean 重命名为目标文件的名称,成功返回true
mkdir(): boolean 创建File对象代表的目录
mkdirs(): boolean 和mkdir()相同,父目录不存在的情况下,将和父目录一起创建
public class FileDemo { public static void main(String[] args) { File file = new File("D:/home/images/sufei.jpg");//采用文件的绝对名称或者相对名称创建文件对象 //文件是否存在 System.out.println("文件是否存在:" + file.exists()); if(file.exists()) { System.out.println("该文件是否可读:" + file.canRead()); System.out.println("该文件是否可写:" + file.canWrite()); System.out.println("该文件是否可执行:" + file.canExecute()); System.out.println("是否为目录:" + file.isDirectory()); System.out.println("是否为文件:" + file.isFile()); System.out.println("是否为隐藏文件:" + file.isHidden()); System.out.println("是否为绝对文件:" + file.isAbsolute()); System.out.println("文件名称:" + file.getName()); System.out.println("绝对文件路径:" + file.getAbsolutePath()); System.out.println("getParent:" + file.getParent()); System.out.println("文件大小:" + file.length() + " bytes"); /* * long time = file.lastModified(); * Date date = new Date(time); * Instant instant = date.toInstant(); * LocalDateTime time2 = LocalDateTime.ofInstant(instant,ZoneId.systemDefault()); */ System.out.println("文件的最近修改时间:" + LocalDateTime.ofInstant(new Date(file.lastModified()).toInstant(), ZoneId.systemDefault())); } File f = new File("D:\\home\\images"); if(f.isDirectory()) { //得到目录下所有的文件 File[] files = f.listFiles(); for(File f1 : files) System.out.println(f1.getName()); } File file2 = new File("D:/home/images/demo.txt"); if(!file2.exists()) { file2.mkdirs(); File dest = new File("D:/home/images/peppa"); file2.renameTo(dest);//文件重命名 file2.deleteOnExit(); }else { file2.deleteOnExit(); } } }
File对象封装了文件或路径属性,但是不包含从/向文件读/写数据的方法。为了进行I/O操作,需要使用正确的Java I/O类创建对象。java有许多用于各种目的的I/O类。通常,可以将它们分为输入流和输出流。输入流包含读取数据的方法,而输出流包含写数据的方法。
使用PrintWriter写数据
基于上图分析,Writer作为字符输出流的抽象基类,使用一个具体的实现子类PrintWriter来向文本文件中写数据。PrintWriter可以创建一个文件并向文件写入数据并提供了方便的打印输出等方法
package edu.uestc.monster.io; import java.io.FileNotFoundException; import java.io.PrintWriter; public class WriteData { public static void main(String[] args) { // 将程序中的数据写入到文件中,需要 使用输出流,而当前写入的是文本数据,采用字符输出流 /* * PrintWriter可以创建一个文件并向文件写入数据 * 可以很方便的打印输出,类似于print(),println() */ PrintWriter writer = null; try { writer = new PrintWriter("score.txt"); writer.print("小猪peppa ");//输出不换行 writer.println(90);//输出完毕后换行 writer.print("小羊苏西 "); writer.println(85); //writer.flush();//将流中的数据刷新到文件 //流是资源(涉及文件),不是内存资源,所以需要手动释放资源 writer.close();//释放资源,其中包含了writer.flush() } catch (FileNotFoundException e) { e.printStackTrace(); } finally { //在finally里确保资源的释放 writer.close(); } } }
使用try-with-resource自动关闭资源
程序猿经常会忘记关闭资源,jdk提供了try-with-resource自动关闭资源
package edu.uestc.monster.io; import java.io.File; import java.io.IOException; import java.io.PrintWriter; public class WriteDataWithAutoClose { public static void main(String[] args) { /* * try(声明和创建资源){ * .... * } * 在()中可以声明多个资源,每个资源必须是Closeable类型,其中包含了close()方法 */ try(var writer = new PrintWriter(new File("students.txt"))){ writer.print("peppa "); writer.print("female "); writer.println(5); writer.print("emily "); writer.print("female "); writer.println(5); } catch (IOException e) { e.printStackTrace(); } } }
读取文本数据
字符输入流的抽象基类为Reader,使用具体的子类FileReader来读取文本数据,FileReader并没有提供新的方法,所有方法都来自于Reader,基于底层的字符读取
package edu.uestc.monster.io; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.Reader; import java.util.Scanner; public class ReadData { public static void main(String[] args) { read3(); } /** * 使用Reader的具体的节点流FileReader每次读取一个字符 */ public static void read1() { try(Reader reader = new FileReader("students.txt")){ //System.out.println((char)reader.read());//读取一个字符,读到文件的末尾,返回-1 int len = -1; while((len = reader.read()) != -1) { System.out.print((char)len); } }catch (IOException e) { e.printStackTrace(); } } /** * 使用Reader的具体的节点流FileReader每次字符 * reader.read(buff):将字符读取到缓冲数组buff中,返回的是读取的字符的长度,读到末尾返回-1 */ public static void read2() { try(Reader reader = new FileReader("students.txt")){ char[] buff = new char[20];//一次读取20个字符 int len = -1; while((len = reader.read(buff)) != -1) { System.out.print(new String(buff,0,len)); } }catch (IOException e) { e.printStackTrace(); } } }
使用Scanner读数据
在前面的使用中,java.util.Scanner类用来从控制台读取字符串和基本数据类型。Scanner可以将输入分为由空白字符分割的标记。为了从键盘读取数据,需要为System.in创建一个Scanner:
Scanner input = new Scanner(System.in);
为了从文件中读取,为文件创建一个Scanner:
Scanner input = new Scanner(new File(fileName));
public static void read3() { try(Scanner input = new Scanner(new File("students.txt"))){ //如果可以按照标记(空格)还有可以读取的内容,一直读 while(input.hasNext()) { //System.out.print(input.next() + " "); System.out.println(input.nextLine()); } }catch (IOException e) { e.printStackTrace(); } }
字节流
一个统一码由两个字节组成,writeChar(char c)方法将字符c的统一码写入输出流。writeChars(String s)方法将字符串中每个字符统一码的低字节写入到输出流中。统一码的高字节被丢弃。wirteBytes方法适用于由ASCII码字符构成的字符串,因为ASCII码仅存储统一码的低字节。如果一个字符串包含非ASCII码的字符,必须使用wirteChars写入这个字符串。
writeUTF(String s)方法将两个字节的长度信息写入输出流,后面紧跟的是字符串s中的每个字符的改进版UTF-8的形式。UTF-8是一种编码方案,允许系统同时操作统一码和ASCII码。ASCII码是统一码(Unicode)的子集。java使用统一码。由于许多应用只需要ASCII字符集,所以将一个字节(8位)的ASCII表示为两个字节(16位)的Unicode是很浪费的。UTF-8的修改版方案分别使用1字节,2字节或3字节来存储字符。如果一个字符编码值小于等于0x7F
,它由一个字节表示;如果一个字符编码大于0x7F小于
0x7FF
的范围内,那么它由两个字节表示;如果一个字符编码值大于等于0x7FF,那么它由三个字节表示。
writeUTF(String s)方法将字符串转化成UTF-8格式的一串字节。然后将它们写入到输出流。readUTF()方法读取一个使用writeUTF方法写入的字符串。UTF-8具有存储一个ASCII码就节省一个字节的优势。如果一个字符串的大多数字符都是普通的ASCII字符,采用UTF-8格式存储更加高效。