1.1 File类型
1.1.1 简介
在程序中,我们使用java.io.File这个类来描述和操作磁盘上的一个文件或文件夹(目录)。
File这个类,能新建、删除、移动,重命名文件或文件夹,也能获取或者修改文件或文件夹的信息(如大小,修改时间等),但File不能访问文件里的内容。如果需要访问文件里的内容,则需要使用输入/输出流
1.1.2 绝对/相对路径
路径:用来描述一个文件或者文件夹所存在的位置,可以分为 绝对路径 和 相对路径。
1)绝对路径(Absolute Path):
从磁盘的根目录开始,一层层的向内查找,直到找到这个文件。在不同的操作系统中,根目录的表示方式可能略有不同。例如,在UNIX和Linux系统中,根目录用斜杠(/)表示,如/home/user/file.txt
;而在Windows系统中,根目录用驱动器名和冒号表示,如C:\Users\User\file.txt
。
2)相对路径(Relative Path):
是相对于当前工作目录或另一个已知位置的路径。它描述的是文件或目录与当前位置之间的相对关系。相对路径通常省略了根目录部分,直接从当前目录开始描述路径。例如,假设当前工作目录是/home/user,要访问该目录下的文件file.txt,可以使用相对路径file.txt,而不需要写出完整的绝对路径。
./ : 表示当前工作目录。 ./可以省略 ../ : 表示返回到上一层目录
举例:
1. 当前工作空间是 /home/user/michael/ -- 需求1. 访问/home/user/michael/file1.txt -- 需求2. 访问/home/user/michael/doc/file1.txt -- 需求3. 访问/home/user/lucy/file1.txt -- 需求4. 访问/home/user/lucy/doc/file1.txt
3)两者的主要区别
绝对路径 | 相对路径 | |
---|---|---|
完整性 | 绝对路径提供了完整的文件或目录路径,从根目录开始,可以唯一地确定位置 | 是相对于当前位置或已知位置的路径,它只提供了与当前位置或已知位置的相对关系。 |
简洁性 | 从根开始写,路径比较长。 | 相对路径相对于当前位置,通常比绝对路径更简洁,尤其是当文件或目录与当前位置在同一层级或子目录中时。 |
缺点 | 一旦换一个文件系统,此时这个路径表示 的文件将无法找到 | 只要两者的相对位置发生 了改变,这个文件将无法找到。 |
总之,绝对路径提供了完整的路径信息,而相对路径描述的是与当前位置的相对关系。选择使用哪种路径形式取决于具体的需求和使用场景。
1.1.3 File的静态属性
关于目录分隔符,在不同的操作系统中,不一样。在windows中,使用 \ 作为目录分隔符,但是,在 ⾮windows的操作系统中,例如: Linux、 Unix,使用 / 作为目录分隔符。
关于路径分隔符,在不同的操作系统中,不一样。在windows中,使用 ; 作为路径分隔符,但是,在 ⾮windows的操作系统中,例如: Linux、 Unix,使用 : 作为路径分隔符。
小贴士:虽然,在windows中,使用 \ 作为目录分隔符,但是大部分情况下,使用 / 也可以。
而File的静态常量会获取程序所在的系统环境中的具体分隔符。这样就省去了程序员来区分不同操作系统的麻烦。
System.out.println(File.separator);// 目录分隔符,用来分隔两个目录,返回一个 字符串 System.out.println(File.separatorChar);// 目录分隔符,用来分隔两个目录,返回一个字符 System.out.println(File.pathSeparator);// 路径分隔符,用来分隔两个路径,返回一个字符串 System.out.println(File.pathSeparatorChar);// 路径分隔符,用来分隔两个路径,返回一个字符
1.1.4 常用构造器
抽象路径应该尽量使用相对路径,并且目录的层级分隔符不要直接写/
或者\
,应该使用File.separator这个常量表示,以避免不同系统带来的差异
-
File(String pathname)
通过将指定字符串转换成抽象路径名来创建一个File实例
-
File(File parent,String child)
从父抽象路径名和子路径名字符串创建新的 File实例。
-
File(String parent, String child)
从父路径名字符串和子路径名字符串创建新的 File实例。
1.1.5 文件属性的方法
-
String getName()
返回由此抽象路径名表示的文件或目录的名称
-
String getPath()
将此抽象路径名转换为路径名字符串
-
File getAbsoluteFile()
返回此抽象路径名的绝对形式。
-
String getAbsolutePath()
返回此抽象路径名的绝对路径名字符串。
-
String getParent()
返回此抽象路径名的父路径名字符串,如果此路径名未指定父目录,则返回null。
-
boolean renameTo(File newName)
重命名由此抽象路径名表示的文件。
-
boolean exists()
测试此抽象路径名表示的文件或目录是否存在
-
boolean canWrite()
测试应用程序是否可以修改由此抽象路径名表示的文件
-
boolean canRead()
测试应用程序是否可以读取由此抽象路径名表示的文件
-
boolean isFile()
测试此抽象路径名表示的文件是否为普通文件
-
boolean isDirectory()
测试此抽象路径名表示的文件是否为目录
-
long lastModify()
返回此抽象路径名表示的文件上次修改的时间
-
long length()
返回由此抽象路径名表示的文件的长度
1.1.6 文件的查询
-
String[] list()
返回一个字符串数组,命名由此抽象路径名表示的目录中的文件和目录
-
String[] list(FilenameFilter filter)
返回一个字符串数组,命名由此抽象路径名表示的目录中满足指定过滤器的文件和目录
-
File[] listFiles()
返回一个抽象路径名数组,表示由该抽象路径名表示的目录中的文件
1.1.7 文件的创建与删除
-
boolean createNewFile()
当且仅当具有该名称的文件尚不存在时,创建一个由该抽象路径名命名的新的空文件
-
-boolean mkdir()
创建由此抽象路径名命名的目录
-
boolean mkdirs()
创建由此抽象路径名命名的目录,包括任何必需但不存在的父目录。
-
boolean delete()
删除由此抽象路径名表示的文件或目录。注意,删除目录时,必须保证此目录下是空目录,如果目录不是空的,需要先删除里面的东西,再删除目录。 还有一点是,删除是永久删除,不会进入回收站
1) 递归算法
如果想删除一个非空目录,则需要使用递归算法来实现
递归的定义
在数学与计算机科学中,递归(Recursion)是指在函数的定义中使用函数自身的方法。实际上,递归,顾名思义,其包含了两个意思:递 和 归,这正是递归思想的精华所在。
递归有两种
直接递归:自己调用自己
间接递归:A调用B,B调用A
如:斐波纳契数列,又称黄金分割数列,指的是这样一个数列:1、1、2、3、5、8、13、21、……
13.1.8 练习题
1、小练习:循环创建100层目录
String[] names = {"富强 ", "民主 ", "文明 ", "和谐 ", "自由 ", "平等 ", "公正 ", "法制 ", "爱国 ", "敬业 ", "诚信 ", "友善"}; StringBuilder sb = new StringBuilder("C:\\Users\\luds\\Desktop\\"); for (int i = 0; i < 1000; i++) { sb.append(names[i % names.length]).append(File.separator); } boolean result = new File(sb.toString()).mkdirs(); System.out.println(result);
2、小练习:将一个文件,移动到这100层的目录中
/ * 将文件移动到一个很深的目录中 */ private static void moveFile() { String path = getPath(); // 文件的移动 boolean ret = new File("files\\b\\b.txt").renameTo(new File(path, "b.txt")); System.out.println(ret); }
3、小练习:递归删除一个多级目录
/ * 递归删除一个路径下所有的子文件 * @param file 需要删除的文件夹 */ private static void deleteDir(File file) { // 1、获取一个路径下所有的子文件 File[] files = file.listFiles(); // 2、遍历这个数组,依次判断能否删除 for (File f : files) { // 判断是文件还是文件夹 if (f.isFile()) { f.delete(); } else if (f.isDirectory()) { deleteDir(f); f.delete(); } } file.delete(); }
2.2 IO流基础
2.2.1 IO流的概念
我们在编程时,除了自身定义一些数据信息外,经常还会引入外界的数据,或者是将自身的数据发送给外界。如:编程时,想读取硬盘上的某一个文件,又或者想将程序中的某些数据写入到硬盘上的一个文件里。这时,我们就要使用I/O流。
这个“外部”范围很广,包括诸如键盘、显示器、文件、磁盘、网络、另外一个程序等。“数据”也可以是任何类型的,例如一个对象、串字符、图像、声音等
IO流: Input Output Stream。
-
Input(输入):是指数据流入程序,通常我们读取外界数据时使用,所以输入是用来读取数据的。
-
Output(输出):是指数据从程序流出,通常我们需要写出数据到外界时使用,所以输出是用来写出数据的
-
一个流就是一个从数据源向目的地的数据序列
-
I/O流类一旦被创建就会自动打开
-
通过调用close方法,可以显式关闭任何一个流,如果流对象不再被引用,Java的垃圾回收机制也会隐式地关闭它
-
不论数据从哪来,到哪去,也不论数据本身是何类型,读写数据的方法大体上都是一样的
读 | 写 |
---|---|
1.打开一个输入流 2.读信息 3.关闭流 | 1.打开一个输出流 2.写信息 3.关闭流 |
2.2.2 IO流的分类
按照数据的流向分类:
-
输入流
-
输出流
按照处理数据的单位分类:
-
字节流
-
字符流
按照流的功能分类,
-
节点流:可以从一个特定的IO设备上读/写数据的流。也称之为低级流
-
处理流:是对一个已经存在的流的连接和封装,通过所封装的流的功能调用实现数据读/写操作。通常处理流的构造器上都会带有一个其他流的参数。也称之为高级流或者过滤流
2.2.3 IO流的应用场景
传统的文件File类,只能够对文件进行属性的操作,例如:创建、移动、删除、属性获取等操作。但是不能获取到文件中的内容
如果需要对文件中的内容进行读写操作,需要使用到IO流。
使用场景:对磁盘或者网络中的文件件进行读写操作。
3.3 字节流
13.3.1 字节流简介
InputStream是字节输入流的顶级父类,是抽象类。定义了基本的读取方法。OutputStream是字节输出流的顶级父类,也是抽象类,定义了基本的写出方法
- InputStream定义的方法
int read() 从输入流中读取一个字节,把它转换为0-255之间的整数,并返回这一整数,如果返回-1,说明读到文件末尾(EOF) int read(byte[] b) 从输入流中读取若干个字节,把它们保存到缓冲区b中,返回的整数表示读取的字节数,如果遇到输入流的结尾,返回-1 int read(byte[] b, int off, int len) 从输入流读取最多 len字节的数据到一个字节数组。从指定下标off开始存。返回的整数表示实际读取的字节数。如果遇到输入流的结尾,返回-1 void close() 关闭输入流 int available() 返回可以从输入流中读取的字节数目 long skip(long n) 从输入流中跳过参数n指定数目的字节 boolean markSupported() 测试这个输入流是否支持 mark和 reset方法。 void mark(int readLimit) 标记此输入流中的当前位置。 void reset() 将此流重新定位到上次在此输入流上调用 mark方法时的位置。
- OutputStream定义的方法
void write(int b) 向输出流写出一个字节 void write(byte[] b) 将 b.length字节从指定的字节数组写入此输出流 void write(byte[] b,int off, int len) 从指定的字节数组写入 len个字节,从偏移 off开始输出到此输出流 void close() 关闭输出流 void flush() OutputStream类本身的flush方法不执行任何操作,它的一些带缓冲区的子类覆盖了flush方法,该方法强制把缓冲区内的数据写到输出流中
3.3.2 常用字节流
1)文件流
FileOutputStream:是文件的字节输出流,以字节为单位写出数据到文件
-
重写模式构造方法
-
FileOutputStream(File file)
创建一个向指定file对象表示的文件中写出数据的文件输出流
-
FileOutputStream(String filename)
创建一个向具体指定名称的文件中写出数据的文件输出流
-
-
追加模式构造方法
-
FileOutputStream(File file,boolean append)
创建一个向指定file对象表示的文件中写出数据的文件输出流
-
FileOutputStream(String filename,boolean append)
创建一个向具体指定名称的文件中写出数据的文件输出流
以上两个构造器,当append为true时,通过该流写出的数据就会追加在文件末尾。
-
==小贴士==:
若指定的文件已经包含内容,那么当使用该流进行写出数据时,会将原有内容全部清除
若指定目录中的指定文件不存在,任何输出流都会自动将该文件创建出来,
FileInputStream:是文件的字节输入流,该流以字节为单位从文件中读取数据。
-
常用构造方法
-
FileInputStream(File file)
创建一个从指定File对象表示的文件中读取数据的文件输入流
-
FileInputStream(String name)
创建一个从指定路径名所指定的文件中读取数据的文件输入流
-
2)缓冲流
在向硬件设备做写出操作时,增大写出次数无疑会降低写出效率,为此,我们可以使用缓冲输出流来一次性批量写出若干数据来减少写出次数来提高写出效率。
BufferedOutputStream
-
该缓冲输出流内部维护着一个缓冲区,每当我们向该流写数据时,都会先将数据存储缓冲区,当缓冲区已满时,缓冲流会将数据一次性全部写出。
-
使用该流虽然可以提高写出效率,但是缺乏即时性,此时我们可以使用flush方法,清空缓冲区,强制写出。
-
常用构造器
-
BufferedOutputStream(OutputStream out)
创建一个新的缓冲输出流,以将数据写入指定的底层输出流。
-
BufferedOutputStream(OutputStream out, int size)
创建一个新的缓冲输出流,以便以指定的缓冲区大小将数据写入指定的底层输出流。
-
BufferedInputStream
-
读取数据时因为以字节为单位,往往会因为读取次数过于频繁而大大降低读取效率,因此我们可以通过提高一次读取的字节数量来减少读取次数,从而提高读取的效率
-
该缓冲输入流,内部维护着一个缓冲区。使用该流读取数据时,该流会尽可能多的一次性读取数据存入缓冲区,直到该缓冲区中的数据被全部读取完毕,会再次读取数据存入该缓冲区,反复进行。这样就减少了读取次数,从而提高效率。
-
常用构造器
-
BufferedInputStream(InputStream in)
以指定节点流in作为参数,创建一个缓冲输入流
-
BufferedInputStream(InputStream in, int size)
以指定节点流in和缓冲区大小作为参数,创建一个缓冲输入流。
-
3)数据流
DataOutputStream
-
该流是FilterOutputStream的子类,扩展了一些功能,提供了一些可以直接写出基本数据类型的方法
-
构造方法
-
DataOutputStream(OutputStream os)
-
DataInputStream
-
该流提供了一些可以直接读取基本数据类型的方法
-
构造方法
-
DataInputStream(InputStream is)
-
4)对象流
对象是存在于内存中的,有的时候我们需要将对象保存到硬盘上,又有时我们需要将对象传输到另一台计算机上等等这些的操作。
此时,我们需要将对象转换成一个字节序列,这个过程我们称之为序列化。
相反,我们将一个字节序列转换成对应的对象,这个过程我们称之为反序列化
我们可以通过ObjectOutputStream流的方法WriteObject(Object o)实现对象序列化,通过ObjectInputStream流的方法readObject()实现对象反序列化。
-
常用构造器
ObjectOutputStream(OutputStream os) ObjectInputStream(InputStream is)
-
Serializable接口
ObjectOutputStream在对对象进行序列化时有一个要求,就是需要对象所属的类型必须实现Serializable接口。此接口内什么都没有,只是作为可序列化的标识。
-
serailVersionUID
通常实现序列化接口的类需要提供一个常量serialVersionUID,表明该类的版本。若不显示的声明,在该对象序列化时也会根据当前类的各个方面计算该类的默认serialVersionUID。但是不同平台的编译器实现有所不同,所以想要跨平台,都应该显示的声明版本号。
如果类的对象序列化存入硬盘上面,之后随着需求的变化更改了类的属性(增加或减少或改名等),那么当反序列化时,就会出现异常(InvalidClassException),这样就造成了不兼容性的问题。
但当serialVersionUID相同时,就会将不一样的field以type的预设值反序列化,避免不兼容问题。
-
transient关键字
对象在序列化后得到的字节序列往往比较大,有时候我们在对一个对象序列化操作时,可以忽略某些不必要的属性,从而对序列化后得到的字节序列“瘦身”。
使用transient关键字修饰的属性在序列化时其值将被忽略
4.4 字符流
4.4.1 字符流简介
Reader是字符输入流的父类,抽象类;Writer是字符输出流的父类,抽象类。字符流是以字符(char)为单位读写数据的,一次处理一个unicode。字符流的底层仍然是基本的字节流
1)Reader的常用方法
int read() 读一个字符 int read(char[] cbuf) 将字符读入数组。 abstract int read(char[] cbuf, int off, int len) 将字符读入数组的一部分。
2)Writer的常用方法
void write(char[] cbuf) 将一个字符数组写出去。 abstract void write(char[] cbuf, int off, int len) 写入字符数组的一部分。 void write(int c) 写一个字符 void write(String str) 写一个字符串 void write(String str, int off, int len) 写一个字符串的一部分。
4.4.2 转换流
OutputStreamWriter
使用该流可以设置字符集,并按照指定的字符集将字符转换成字节后通过该流写出
-
常用构造器
-
OutputStreamWriter(OutputStream out,String charsetName)
基于给定的字节输出流以及字符集创建字符输出流对象
-
OutputStreamWriter(OutputStream out)
根据系统默认字符集创建字符输出流对象
-
InputStreamReader
使用该流可以设置字符集,并按照指定的字符集从流中按照该编码将字节数据转换为字符并读取
-
常用构造器
-
InputStreamReader(InputStream in,String charsetName)
基于给定的字节输入流以及字符集创建字符输入流
-
InputStreamReader(InputStream in)
该构造方法会根据系统默认字符集创建字符输入流
-
4.4.3 缓冲字符流
PrintWriter
PrintWriter是具有自动行刷新的缓冲字符输出流,其提供了比较丰富的构造方法,通常比BufferedWriter更实用。
-
常用构造方法
PrintWirter(File file) PrintWriter(String filename) PrintWriter(OutputStream out) PrintWriter(OutputStream out,boolean autoFlush) PrintWriter(Writer writer) PrintWriter(Writer writer,boolean autoFlush)
其中参数为OutputStream和Writer的构造方法提供了一个可以传入boolean的值参数,该参数用于表示PrintWriter是否具有自动行刷新
-
常用方法
除了write方法,PrintWriter提供了丰富的重载print和println方法。其中println方法在于输出目标数据后自动输出一个系统支持的换行符。若该流设置了自动行刷新,那么每当通过println方法写出的内容都会被实际写出,而不是进行缓存。
void println() 通过写入行分隔符字符串来终止当前行。 void println(boolean x) 打印一个布尔值,然后终止该行。 void println(char x) 打印一个字符,然后终止该行。 void println(char[] x) 打印字符数组,然后终止行。 void println(double x) 打印双精度浮点数,然后终止行。 void println(float x) 打印一个浮点数,然后终止该行。 void println(int x) 打印一个整数,然后终止该行。 void println(long x) 打印一个长整型,然后终止行。 void println(Object x) 打印一个对象,然后终止该行。 void println(String x) 打印一个字符串,然后终止行。
BufferedReader
BufferedReader是缓冲字符输入流,内部提供了缓冲区,可以提高读取效率。
-
常用构造器
-
BufferedReader(Reader reader)
-
-
常用方法
该类提供了一个可以便于读取一行字符串的方法
-
String readLine()
该方法连续读取一行字符串,直到读取到换行符位置,返回的字符串中不包含换行符
-
4.4.4 文件字符流
FileWriter
相当于OutputStreamWriter和FileOutputStream合起来的功能
-
构造方法
FileWriter(File file) FileWriter(File file,boolean append) FileWriter(String filepath) FileWriter(String filepath,boolean append)
-
常用方法
继承了OutputStreamWriter等父类的方法,内部也维护着一个缓存区,需要手动调用flush方法进行刷新。
FileReader
相当于InputStreamReader和FileInputStream合起来的功能,但是不能设置字符集
-
构造方法
FileReader(File file) FileReader(String filepath)
-
常用方法
继承了InputStreamReader等父类的方法,内部也维护着一个缓存区,可以提高读取效率。
4.4.5 其他流
System.out:
为PrintStream类型,代表标准输出流,默认的数据输出是控制台
System.in:
为InputStream类型,代表标准输入流,默认的数据源为键盘
System.err:
为PrintStream类型,代表标准错误输出也流,默认的数据输出是控制台
public static void main(String[] args) throws IOException { /* out是System类的一个属性,是一个流对象 */ PrintStream ps = System.out; /*指定PringStream与硬盘关联 */ PrintStream ps1 = new PrintStream(new FileOutputStream("Systemout.txt")); //使用指定的流给System类型的属性out //赋值 System.setOut(ps1); System.out.println("将数据书写到硬盘上了……"); //将之前默认的流对象赋值给out属性 System.setOut(ps); System.out.println("将数据书写到控制台上了吗"); }