文章目录
截至之前我们存储数据的方式是在内存中存储数据的,在内存中存储的数据可以用来处理、修改、运算,但不能长久保存
那么,我们怎样才能实现数据的永久存储呢?
在计算机中,有一块硬件可以永久存储数据,即磁盘中数据的形式就是文件,文件是数据的载体。
那么我们就需要提到与文件有关的File类
File类
File类概述
- File类在包java.io.File下,代表操作系统的文件对象 (文件、文件夹)
- File类提供了诸如:定位文件,获取文件本身的信息、删除文件、创建文件(文件夹)等功能
File类创建对象
名称 | 说明 |
---|---|
public File(String pathname) | 根据文件路径创建文件对象 |
public File(String parent,String child) | 从父路径名字符串和子路径名字符串创建文件对象 |
public File(File parent,String child) | 根据父路径对应文件对象和子路径名字符串创建文件对象 |
- File创建对象,支持绝对路径、支持相对路径
- File对象可以定位文件和文件夹
- File封装的对象仅仅是一个路径名,这个路径可以是存在的,也可以是不存在的
(我只说第一种创建对象的方法
想看其他构造方法的操作和作用可以看 Anthony_tester 博主的这篇博客
链接: Java中File类-7-File类和构造方法)
格式
File file =new File(“文件/文件夹/绝对路径/相对路径”);
构造方法代码如下
//D:\ 这是一个文件夹(D盘就相当于一个文件夹)
File f=new File("D:\\");//直接加入
File类的作用
- 创建对象定位文件,可以删除、获取文件信息等。但是不能读写文件内容
绝对路径和相对路径
- 绝对路径是带盘符的,依赖当前系统
- 相对路径是不带盘符的,默认相对到工程下开始寻找文件
File类常用方法
方法名称 | 说明 |
---|---|
public boolean isDirectory() | 测试此抽象路径名表示的File是否为文件夹 |
public boolean isFile() | 测试此抽象路径名表示的File是否为文件 |
public boolean exists() | 测试此抽象路径名表示的File是否存在 |
public String getAbsolutePath() | 返回此抽象路径名的绝对路径名字符串 |
public String getPath() | 将此抽象路径名转换为路径名字符串 |
public String getName() | 返回由此抽象路径名表示的文件或文件夹的名称 |
public long lastModified() | 返回文件最后修改的时间毫秒值 |
public long length() | 返回文件字节个数 |
我们先创一个文件
方法的使用代码如下
File f=new File("Object/abc123.txt");
System.out.println(f.getAbsoluteFile()); //获取绝路径
System.out.println(f.getPath()); //获取文件定义的时候使用的路径(相对路径)
System.out.println(f.length()); //获取文件大小,即字节个数
System.out.println(f.isDirectory()); //是否是文件夹
System.out.println(f.isFile()); //是否是文件
System.out.println(f.exists()); //文件是否存在
System.out.println(f.getName()); //获取文件名,带后缀
System.out.println(new SimpleDateFormat("yyyy年MM月dd日 " +
"HH时mm分ss秒").format(f.lastModified()));
//获取文件最后一次修改的时间毫秒值,我把他改成了我想要看到的时间形式
结果如下
File类创建和删除文件的功能
File类创建文件的功能
方法名称 | 说明 |
---|---|
public boolean createNewFile() | 创建一个新的空的文件 |
public boolean mkdir() | 只能创建一级文件夹 |
public boolean mkdirs() | 可以创建多级文件夹 |
代码展示
基本步骤都是定义一个File类对象,有参构造方法中填入你想要创建的文件,然后调用方法即可创建,创建成功返回true,失败返回false
File f=new File("Object/abc1234.txt");
System.out.println(f.createNewFile()); //几乎不用的,因为以后文件都是自动创建的!
File f1=new File("D:/aaa");
System.out.println(f1.mkdir());//了解一下
File f2=new File("D:/bbb/ccc");
System.out.println(f2.mkdirs());//重点了解,调用它既可以创建一级文件,也可以创建多级文件
File类删除文件的功能
方法名称 | 说明 |
---|---|
public boolean delete() | 删除由此抽象路径名表示的文件或空文件夹 |
这个方法直接用File类对象调用就行,删除成功返回true,失败返回false
代码如下
System.out.println(f.delete());
System.out.println(f1.delete());
System.out.println(f2.delete());
- delete方法直接删除不走回收站;如果删除的是一个文件,且文件没有被占用则直接删除
- delete方法默认只能删除空文件夹
比如说我们上面所创建的File f2=new File(“D:/bbb/ccc”); 这个文件夹,如果用该文件夹对象调用delete方法时,只会删除ccc这个文件,前提是ccc文件是空文件
递归
- 方法直接调用自己或者间接调用自己的形式称为方法递归 ( recursion)
- 递归做为一种算法在程序设计语言中广泛应用。
递归的形式
- 直接递归: 方法自己调用自己
- 间接递归: 方法调用其他方法,其他方法又回调方法自己
问题
- 递归如果没有控制好终止,会出现递归死循环,导致栈内存溢出现象
递归解决问题的思路:
- 把一个复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解
递归算法三要素大体可以总结为:
- ①递归的公式: f(n)= f(n-1) * n
- ②递归的终结点: f(1)
- ③递归的方向必须走向终结点
一个简单的案例: 求1!+2!+3!+…n!的结果
我们先来捋一下思路:
1.我们要先考虑一个数的阶层的公式:f(n)=n*f(n-1),f(1)=1;
2.我们需要考虑1到n的和的公式:g(n)=n+g(n-1),g(1)=1;
3.我们把这两个公式结合起来:g(n)=f(n)+g(n-1),f(n)=g(n)=1;
4.返回g(n)即可
代码展示
public class Demo {
public static void main(String[] args){
//求1!+2!+3!+..n!的结果
while (true) { //加入死循环,便于多次检验
Scanner sc=new Scanner(System.in);
System.out.println("请输入n的值");
int n=sc.nextInt();
System.out.println(sum(n));
}
}
/**
g(n)=n+g(n-1),g(1)=1;
*/
public static int sum(int n){
if (n==1){
return num(1);
}else {
return num(n)+sum(n-1); //g(n)=f(n)+g(n-1),f(n)=g(n)=1;
}
}
/**
f(n)=n*f(n-1),f(1)=1;
*/
public static int num(int n){
if (n==1){
return 1;
}else {
return n*num(n-1);
}
}
}
File类的遍历功能与递归结合用于文件搜索
File类的遍历功能
方法名称 | 说明 |
---|---|
public String[ ] list() | 获取当前目录下所有的“一级文件名称”到一个字符串数组中去返回 |
public File[ ] listFiles()(常用) | 获取当前目录下所有的”一级文件对象”到一个文件对象数组中去返回(重点) |
listFiles方法注意事项:
- 当调用者不存在时,返回null
- 当调用者是一个文件时,返回null
- 当调用者是一个空文件夹时,返回一个长度为0的数组
- 当调用者是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回
- 当调用者是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏内容
!!!File类的遍历功能与递归结合用于多级文件搜索!!!
我想把logback.xml找出来
分析:
- 先定位出的应该是一级文件对象
- 遍历全部一级文件对象,判断是否是文件
- 如果是文件,判断是否是自己想要的
- 如果是文件夹,需要继续递归进去重复上述过程
代码展示
public class Demo {
public static Scanner sc=new Scanner(System.in);
public static void main(String[] args){
File f=new File("D:/");
System.out.println("请输入文件名");
String fileName=sc.nextLine();
file(f, fileName);
}
public static void file(File f,String fileName){
//先定位出的应该是一级文件对象
if (f!=null&&f.isDirectory()){
File[] files=f.listFiles();
if(files != null && files.length > 0) {
//遍历全部一级文件对象
for (File file : files) {
//判断是否是文件
if (file.isFile()) {
if (file.getName().contains(fileName)) {
System.out.println("路径为:" + file.getAbsoluteFile());
}
//如果是文件夹,需要继续递归进去重复上述过程
} else {
file(file, fileName);
}
}
}
}else {
System.out.println("没有该文件");
}
}
}
字符集
字符集基础知识:
- 计算机底层不可以直接存储字符的。计算机中底层只能存储二进制(0,1)
- 二进制是可以转换成十进制的
结论:计算机底层可以表示十进制编号。计算机可以给人类字符进行编号存储,这套编号规则就是字符集
ASCII字符集
- ASCII(American Standard Code for lnformation lnterchange,美国信息交换标准代码): 包括了数字、英文、符号
- ASCII使用1个字节存储一个字符,一个字节是8位, 总共可以表示128个字符信息,对于英文,数字来说是够用的。
GBK:
- window系统默认的码表。兼容ASCII码表,也包含了几万个汉字,并支持繁体汉字以及部分日韩文字
- 注意:GBK是中国的码表,一个中文以两个字节的形式存储。但不包含世界上所有国家的文字
Unicode码表:
- unicode (又称统一码、万国码、单一码)是计算机科学领域里的一项业界字符编码标准
- 容纳世界上大多数国家的所有常见文字和符号
- 由于Unicode会先通过UTF-8,UTF-16,以及 UTF-32的编码成二进制后再存储到计算机,其中最为常见的就是UTF-8
注意
- Unicode是万国码,以UTF-8编码后一个中文一般以三个字节的形式存储
- UTF-8也要兼容ASCII编码表
- 编码前和编码后的字符集需要一致,否则会出现中文乱码
- 技术人员都应该使用UTF-8的字符集编码
模型展示
(英文和数字在任何国家的编码中都不会乱码)
String编码,解码
String编码
方法名称 | 说明 |
---|---|
byte[ ] getBytes() | 使用平台的默认字符集将该 String编码为一系列字节,将结果存储到新的字节数组中 |
byte[ ] getBytes(String charsetName) | 使用指定的字符集将该 String编码为一系列字节,将结果存储到新的字节数组中 |
String解码
方法名称 | 说明 |
---|---|
String(byte[ ] bytes) | 通过使用平台的默认字符集解码指定的字节数组来构造新的String |
String(byte[ ] bytes, String charsetName) | 通过指定的字符集解码指定的字节数组来构造新的String |
String name="你好是hello";
byte[] b= name.getBytes(); //默认字符集得到一系列字节
byte[] b1= name.getBytes("GBK"); //指定GBK字符集得到一系列字节
String s=new String(b); //默认字符集解码
String s1=new String(b1,"GBK");//指定GBK字符集解码
IO流
IO流也称为输入、输出流,就是用来读写数据的。
- I表示Input,是数据从硬盘文件读入到内存的过程,称之输入,负责读
- O表示Output,是内存程序的数据从内存到写出到硬盘文件的过程,称之输出,负责写
就是:文件—>内存—>文件的过程
IO流的分类
-
按流的方向分
-
按流中的数据最小单位分为
总的来说分为四大类: -
字节输入流(InputStream):以内存为基准,来自磁盘文件/网络中的数据以字节的形式读入到内存中去的流称为字节输入流
-
字节输出流(OutputStream):以内存为基准,把内存中的数据以字节写出到磁盘文件或者网络中去的流称为字节输出流
-
字符输入流(Reader):以内存为基准,来自磁盘文件/网络中的数据以字符的形式读入到内存中去的流称为字符输入流
-
字符输出流(Writer):以内存为基准,把内存中的数据以字符写出到磁盘文件或者网络介质中去的流称为字符输出流
以下是总体系和它们各自的一个实现类
文件字节输入流:FilelnputStream
作用:以内存为基准,把磁盘文件中的数据以字节的形式读取到内存中去
构造器 | 说明 |
---|---|
public FilelnputStream(File file) | 创建字节输入流管道与源文件对象接通 |
public FilelnputStream(String pathname) | 创建字节输入流管道与源文件路径接通 |
//获取File类对象后加入参数列表中
File f=new File("Object/abc123.txt");
FileInputStream fis=new FileInputStream(f);
//直接把路径加入参数列表中
FileInputStream fis1=new FileInputStream("Object/abc123.txt");
方法名称 | 说明 |
---|---|
①public int read() | 每次读取一个字节返回读取的字节,如果字节已经没有可读的返回-1 |
②public int read(byte[ ] buffer) | 每次读取一个字节数组返回读取字节的个数,如果字节已经没有可读的返回-1 |
①一个个读取太慢,我们可以进行一个判断进行循环
代码如下
int len;
while ((len=fis.read())!=-1){
System.out.println(len);
}
②循环并读取
代码如下
byte[] buffer=new byte[3];
int len;
while ((len=fis.read(buffer))!=-1){
System.out.print(new String(buffer,0,len));
}
每次读取一个字节数组存在的问题:
- 虽然读取的性能得到了提升,但读取中文字符输出无法避免乱码问题
解决方案
- ①自己定义一个字节数组与文件的大小一样大,然后使用读取字节数组的方法,一次性读取完成
byte[] buffer=new byte[(int) f.length()];
int len;
while ((len=fis.read(buffer))!=-1){
System.out.print(new String(buffer,0,len));
}
- ②官方为字节输入流Inputstream提供了如下API可以直接把文件的全部数据读取到一个字节数组中
构造器 | 说明 |
---|---|
public byte[ ] readAllBytes() throws IOException | 直接将当前字节输入流对应的文件对象的字节数据装到一个字节数组返回 |
byte[] buffer= fis.readAllBytes();
System.out.println(new String(buffer));
(但是,直接把文件数据全部读取到一个字节数组虽然可以避免乱码,但理想情况下,如果文件过大,定义的字节数组可能引起内存溢出。)
文件字节输出流:FileOutputStream
作用:以内存为基准,把内存中的数据以字节的形式写出到磁盘文件中去的流
构造器
构造器 | 说明 |
---|---|
public FileOutputStream(File file) | 创建字节输出流管道与源文件对象接通 |
public FileOutputStream(File file,boolean append) | 创建字节输出流管道与源文件对象接通可追加数据 |
public FileOutputStream(String filepath) | 创建字节输出流管道与源文件路径接通 |
public FileOutputStream(String filepath,boolean append) | 创建字节输出流管道与源文件路径接通,可追加数据 |
代码说明
//创建一个File对象
File f=new File("Object/abc.txt");
//创建字节输出流管道与源文件对象接通
//(每次启动先清空之前的数据,再写新数据进入)
FileOutputStream fos=new FileOutputStream(f);
//创建字节输出流管道与源文件对象接通,加入判断true可追加数据
//(即再次启动程序后内容不会被清空)
FileOutputStream fos2=new FileOutputStream(f,true);
//创建字节输出流管道与源文件路径接通
FileOutputStream fos3=new FileOutputStream("Object/abc.txt");
//创建字节输出流管道与源文件路径接通,加入判断true可追加数据
FileOutputStream fos4=new FileOutputStream("Object/abc.txt",true);
文件字节输出流(FileOutputStream)写数据出去的API
方法名称 | 说明 |
---|---|
public void write(int a) | 写一个字节出去 |
public void write(byte[] buffer) | 写一个字节数组出去 |
public void write(byte[] buffer ,int pos ,int len) | 写一个字节数组的一部分出去 |
fos.write(97);
fos.write('a');
byte[] b={99,'a','c'};
fos.write(b);
fos.write(b,1,2); //写数组b中的'a','c'
fos.write("我喜欢你".getBytes());
//结果:aacacac我喜欢你
由于这个结果是连起来的,我们想把它换行就要用到 “\r\n”,不用 “\n” 是担心在其他的系统中不能使用
如下
fos.write("\r\n".getBytes());
我们只这样写的话有一部分数据可能会滞留在缓存区而不能被写入,我们如果想要使写出去的数据能成功生效,就要用到下面两种方法
方法名称 | 说明 |
---|---|
flush() | 刷新流,刷新后还可以继续写数据 |
close() | 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
字节流小结
模型
文件拷贝
字节流适合做文件数据的拷贝
- 任何文件的底层都是字节,拷贝是一字不漏的转移字节,只要前后文件格式、编码一致没有任何问题
代码展示
public static void main(String[] args) throws Exception{
try ( FileInputStream fis=new FileInputStream("D:\\code\\logback.xml");
FileOutputStream fos=new FileOutputStream("D:\\code\\logback.xml1");){
int len;
byte[] buffer=new byte[1024];
while ((len=fis.read(buffer))!=-1){
fos.write(buffer,0,len);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
资源释放的方式
目的:释放不必要的内存占用
try-catch-finally
- finally: 在异常处理时提供finally块来执行所有清除操作,比如说IO流中的释放资源
- 特点: 被finally控制的语句最终一定会执行,除非JVM退出
- 异常处理标准格式: try…catch…finally
代码展示
public static FileInputStream fis=null;
public static FileOutputStream fos=null
public static void main(String[] args) {
try {
fis=new FileInputStream("D:\\code\\logback.xml");
fos = new FileOutputStream("D:\\code\\logback.xml1");
int len;
byte[] buffer = new byte[1024];
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}catch (Exception e){
e.printStackTrace();
}finally {
try {
fos.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
try {
fis.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
(我们使用这个方法是要先把字符输入,输出流定义静态成员化,不然finally处无法关流)
try-catch-resource
作用:自动释放资源、代码简洁
finally虽然可以用于释放资源,但是释放资源的代码过于繁琐,而try-catch-resources可以自动释放资源、代码简洁
JDK7改进方案:
代码展示
public static void main(String[] args) {
try ( FileInputStream fis=new FileInputStream("D:\\code\\logback.xml");
FileOutputStream fos = new FileOutputStream("D:\\code\\logback.xml1")) {
int len;
byte[] buffer = new byte[1024];
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}catch(Exception e){
e.printStackTrace();
}
}
(也就是try后面多了一个(),然后把把字节输入输出流加入就行了)
JDK9改进方案:
代码展示
public static void main(String[] args)throws Exception {
FileInputStream fis=new FileInputStream("D:\\code\\logback.xml");
FileOutputStream fos = new FileOutputStream("D:\\code\\logback.xml1");
try (fis;fos) {
int len;
byte[] buffer = new byte[1024];
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len);
}
}catch(Exception e){
e.printStackTrace();
}
}
(但这个还要处理异常,并不太好)
三种格式
注意
- JDK 7以及JDK 9的()中只能放置资源对象,否报错
- 资源都是实现了Closeable/AutoCloseable接口的类对象
字节流读取中文输出可能会乱码或者内存溢出。那么读取中文输出字符流更合适,因为它的最小单位是按照单个字符读取的
文件字符输入流:FileReader
作用:以内存为基准,把磁盘文件中的数据以字符的形式读取到内存中去
构造器
构造器 | 说明 |
---|---|
public FileReader(File file) | 创建字符输入流管道与源文件对象接通 |
public FileReader(String pathname) | 创建字符输入流管道与源文件路径接通 |
代码展示
//创建字符输入流管道与源文件对象接通
File f=new File("Object/abc123.txt");
FileReader fr=new FileReader(f);
//创建字符输入流管道与源文件路径接通
FileReader fr1=new FileReader("Object/abc123.txt");
常用方法
方法名称 | 说明 |
---|---|
public int read() | 每次读取一个字符返回,如果字符已经没有可读的返回-1 |
pubrc int read(char[ ] buffer) | 每次读取一个字符数组,返回读取的字符个数,如果字符已经没有可读的返回-1 |
FileReader fr1=new FileReader("Object/abc123.txt");
char[] buffer=new char[1024];
int len;
while ((len=fr1.read(buffer))!=-1){
System.out.print(new String(buffer,0,len));
}
字符流的好处
- 读取中文字符不会出现乱码(如果代码和文件编码一致)
文件字符输出流:FileWriter
作用:以内存为基准,把内存中的数据以字符的形式写出到磁盘文件中去的流
构造器 | 说明 |
---|---|
public FileWriter(File file) | 创建字符输出流管道与源文件对象接通 |
public FileWriter(File file,boolean append) | 创建字符输出流管道与源文件对象接通可追加数据 |
public FileWriter(String filepath) | 创建字符输出流管道与源文件路径接通 |
public FileWriter(String filepath,boolean append) | 创建字符输出流管道与源文件路径接通,可追加数据 |
代码展示
File f=new File("Object/abc.txt");
//创建字符输出流管道与源文件对象接通
FileWriter fw=new FileWriter(f);
//创建字符输出流管道与源文件对象接通可追加数据
FileWriter fw1=new FileWriter(f,true);
//创建字符输出流管道与源文件路径接通
FileWriter fw2=new FileWriter("Object/abc.txt");
//创建字符输出流管道与源文件路径接通,可追加数据
FileWriter fw3=new FileWriter("Object/abc.txt",true);
文件字符输出流(FileWriter) 写数据出去的API
方法名称 | 说明 |
---|---|
void write(int c) | 写一个字符 |
void write(char[ ] cbuf) | 写入一个字符数组 |
void write(char[ ] cbuf, int off, int len) | 写入字符数组的一部分 |
void write(String str) | 写一个字符串 |
void write(String str, int off, int len) | 写一个字符串的一部分 |
void write(int c) | 写一个字符 |
流的关闭与刷新
代码展示
方法名称 | 说明 |
---|---|
flush() | 刷新流,还可以继续写数据 |
close() | 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据 |
fw.write(97);//a
fw.write('a');
fw.write('我');
char[] chars={'a',98,'c','d'};
fw.write(chars); //abcd
fw.write(chars,0,2); //ab
fw.write("你好,小明");
fw.write("你好,小明",0,3);//你好,
fw.close();
//结果:aa我abcdab你好,小明你好,
(一定要记得关流)
字节、字符流小总结
字节流、字符流适合场景
- 字节流适合做一切文件数据的拷贝(音视频,文本)
- 字节流不适合读取中文内容输出
- 字符流适合做文本文件的操作(读,写)
字符、字节输出流写完数据后必须刷新
- flush()刷新数据,还可以继续写数据
- close()方法是关闭流,关闭包含刷新,关闭后流不可以继续使用了