目录
File中对文件夹的操作:创建新文件,创建一级新文件夹,创建多级新文件夹,删除文件夹
使用递归查找在指定根目录下的文件,并进行操作,可以将输出代码改为删除等;
字节流非常适合做一切文件的复制操作(但是不适合文本文件,文本用字符流)
转换流 (InputStreamReader / OutputStreamWriter)
数据输入输出流:DataInputStream / DataOutputStream
序列化流:ObjectInputStream / ObjectOutputStream
编辑Java中也提供了一个原生的文件操作工具类: Files
1,什么是File和IO流,以及作用
File:代表文件或文件夹(对文件进行操作,比如创建,删除,获取大小,获取类型等,但是不能读写文件里面的内容)
IO流:读写数据(读写文件里面的内容数据的)
推荐使用哪种方式提高字节流读写数据的性能?
首先不推荐使用单个字节或字符的读写,因为慢的要死。第二:读取数据的性能取决于传入的字节数组大小,通常写法为byte[1024 * 8]等这种写法,传入的字节数组等于8时,低级输入输出流和buffered输入输出流的读写数据其实是差不多的。为什么不是数组越大越好呢,因为越大的话,提升的性能就微乎其微了,因为读取数据,调用系统资源也是需要时间的,基本上32就差不多了。
File详解:
-
创建File类对象常用构造器
- 在想获取项目中的某个文件夹时,推荐采用相对路径(new File("模块名/剩下的路径");)
-
File中的常用方法1,获取文件信息功能
获取文件的最后修改时间详细使用代码
//通过file的相对地址获取file对象 File file = new File("day0819\\src\\abc.text"); long l = file.lastModified(); //创建一个SimpleDateFormat对象接一下 SimpleDateFormat formatter = new SimpleDateFormat("yyyy年M月d日 HH:mm:dd"); String format = formatter.format(l); System.out.println(format);
注意:使用File创建文件夹的对象时,调用 file.length();的话,只会显示文件夹的大小,而操作系统中显示是file中包括内容的大小,所以获取的length和系统中显示的可能有所差异
-
File中的常用方法2,遍历文件夹功能
-
File中常用方法3:补充方法:文件重命名
- 想要重命名的File对象实例.renameTo(new File(新地址新名字));
public static void main(String[] args) throws IOException {
File file = new File("D:\\resource");
String[] list = file.list();
for (String s : list) {
System.out.println(s);
}
//把resource中,19,张三.text 28,李四.text里面的这种无序的编号头改为有序的从1开始文件头比如 1,张三.text 2,李四.text
System.out.println("------------");
//1,使用正则表达式,获取开头的文件名编号,并创建一个正则表达式匹配器
String course = "(\\d+)";
Pattern compile = Pattern.compile(course);
Matcher matcher = null;
//2,定义一个想设置的开始编号;从1开始
int index = 1;
File[] files = file.listFiles();
for (File file1 : files) {
//通过匹配器查找匹配的值并取出
matcher = compile.matcher(file1.getName());
matcher.find();
String name = matcher.group();
//调用String的截取功能,截取匹配值索引之后的字段
String lastName = file1.getName().substring(name.length());
//对名字进行一个拼接,并且使用file的重命名方法对文件重命名;
file1.renameTo(new File("D:\\resource\\"+ index + lastName));
//设置文件头加一
index++;
}
System.out.println("---------------");
for (String s : file.list()) {
System.out.println(s);
}
}
-
File中对文件夹的操作:创建新文件,创建一级新文件夹,创建多级新文件夹,删除文件夹
注意:如果创建新文件和新文件那里如果文件存在,则会返回false。
-
使用递归查找在指定根目录下的文件,并进行操作,可以将输出代码改为删除等;
public static void main(String[] args) throws IOException {
File file = new File("D:\\sakura");
//在指定的根目录下,寻找文件,找到后输出在控制台;
FileDem2.findFile(file,"sakura.exe");
}
/**
*
* @param file 需要从哪里进行查找的目录
* @param fileName 需要查找的文件名称
*/
public static void findFile(File file,String fileName) throws IOException {
//1,把非法参数排除掉(file为空,或 不存在,或文件,因为文件肯定没办法进行查找文件嘛,必须得是文件夹)
if (file == null || !file.exists() || file.isFile()) {
return;
}
//2,file不是null并且一定是文件夹
//获取文件夹中的子文件
File[] files = file.listFiles();
//3,判断是否存在下一级目录,是的话判断里面的文件是否为文件夹,是的话递归遍历,不是的话,停止;
if (files != null){
for (File f1 : files) {
//判断是否为文件,是否是我们要找的
if (f1.isFile()) {
if (f1.getName().contains(fileName)) {
System.out.println("找到了:" + f1.getPath());
}
}else {
findFile(f1,fileName);
}
}
}else {
return;
}
}
字符集编码,解码
- ASCII字符集:只有英文,数字,符号等,占1个字节
- GBK字符集:汉字占2个字节,英文,数字占1个字节,兼容ASCII字符集
- UTF-8字符集:全世界通用,汉字占3个字节,英文,数字占1个字节
- UTF-32字符集:全世界通用,都占4个字节(不使用)
最先是ASCII字符集,然后有了中文用的GBK,后来全世界为了统一编码推出了UTF-32,但是因为所有的字符都占4个字节,效率低,所以就推出了UTF-8。
注意:字符编码时使用的字符集,和解码时使用的字符集必须一致,否则会出现乱码,英文和数字一般不会乱码,因为很多字符会兼容ASCII编码
String类提供的编码和解码的方法;
public static void main(String[] args) throws UnsupportedEncodingException {
//获取str的解码
String str = "a我n";
//方法: str.getBytes() 获取他的默认平台用的比字符集编码
byte[] bytes = str.getBytes();
System.out.println(Arrays.toString(bytes));
//方法: str.getBytes(charsetName); 通过指定字符集获取编码,这个方法会抛出UnsupportedEncodingException异常,因为怕你字符集的名字写错了
byte[] gbks = str.getBytes("GBK");
System.out.println(Arrays.toString(gbks));
//默认使用平台的编码来解码;
String s1 = new String(bytes);
System.out.println(s1);
//因为gbks是用的GBK编码的,所以解码也要GBK来解
String s2 = new String(gbks,"GBK");
System.out.println(s2);
}
IO流详解(先了解他的体系分类,再学每个IO流的作用用法)
- 概念:I指input,称为输入流,负责把数据读到内存中去;O指output,输出流
- IO流分类:字符流,字节流,又对应着输入流,输出流
字节输入流FileInputStream
FileInputStream的创建,以及常用方法,因为字节流每次只能读取一个字节,所以读汉字这种3个字节的会乱码,所以一般不用来读取数据而是做文件的复制等操作。
主要方法:.read() , .read(byte[] byte) , .readAllBytes()
public static void main(String[] args) throws IOException {
//两种创建字节流FileInputStream的方法,使用的多态承接 推荐第一种就可以了
InputStream is = new FileInputStream("day0819\\src\\abc.text");
//InputStream is2 = new FileInputStream(new File("day0819\\src\\abc.text"));
//is.read() 方法读取一个字节,如果没有了,就返回1; 注意:因为只能读一个字节,而汉字是3字节,所以读汉字会乱码;
/*
int b;
while ((b = is.read()) > -1) {
System.out.print((char) b);
}
*/
//is.read(byte[] bytes) 每次用一个字节数组去读取,可以提高效率,读不到就显示-1 ,但是读汉字也有问题;
byte[] bytes = new byte[3];//创建一个长度为3 的字节数组;当然其他长度也行
int len;
while ((len = is.read(bytes)) > -1) {
//注意这里:因为使用bytes读取的,所以读多少个,就取多少个,否则如果全取的话,第一次读了3字节abc,第二次只读了2个字节de,全取就成了dec
String s = new String(bytes, 0, len);
System.out.print(s);
}
//InputStream类提供的readAllBytes();方法,也可以直接读取整个文件,比buffer方便;
//byte[] bytes = is.readAllBytes();
//System.out.println(new String(bytes));
//关闭流
is.close();
/* is2.close();*/
}
补充:读取文件FileInputStream的父类InputStream还提供了readAllBytes()方法,用于读取整个文件。如果文件太大,就容易内存不够等
字节输出流FileOutputStream
注意:FileOutputStream有两种构造器,一种是覆盖管道,一种是追加管道,覆盖管道输出时,会把文件中原来的东西覆盖掉,而追加管道,会在后面进行追加;追加管道就是再构造器参数文件名后面加个参数true就可以了,看上图构造器下面的两个。
补充:输出时换行符为 os.write("\r\n".getBytes());
字节流非常适合做一切文件的复制操作(但是不适合文本文件,文本用字符流)
任何文件的底层都是字节,字节流做复制是一字不漏的转移完全不的字节,只要复制后的文件格式一致就没问题
对于复制操作的汉字来讲,哪怕没有读完一个汉字的3个字节,也没关系,因为你都是把读到的字节输入到要复制去的目标文件中了,不会中途转为字符。读取完之后把目标文件一打开,里面还是一样的。
public static void main(String[] args) throws IOException {
//把一个文件复制到另一个文件中取
//思路:把一个文件读取出来,然后通过输出流复制到另一个文件中去
//获取源文件的输入流
InputStream is = new FileInputStream("D:\\resource\\屏幕截图 2023-08-21 223415.png");
//获取复制到的目标文件的输出流
OutputStream os = new FileOutputStream("D:\\resource - 副本\\223415.png");
//创建一个数组,为1024的大小
byte[] bytes = new byte[1024];
//定义一个len来接受is.read(byte[])的返回值,如果为-1,则停止
int len;
while ((len = is.read(bytes)) > -1) {
//读取多少输出多少,os.write(bytes,0,len)的意思是输出bytes数组中第0个字节开始的到len为止,len是读取了多少字节的数字
os.write(bytes,0,len);
}
//完事关闭各种流,先关里面的再关外面的
os.close();
is.close();
}
封装文件复制类
package test;
import java.io.*;
public class CopyDemo1 {
/**
* 这是一个复制文件的方法,只能用来放文件地址
* @param srcFile 原文件地址
* @param descFile 需要复制去的地址;
*/
public void copyFile(String srcFile,String descFile) {
//先创建两个输入输出流:
BufferedInputStream srcBuffer =null;
BufferedOutputStream descBuffer = null;
try {
//给Buffered输入输出流创建对象
srcBuffer = new BufferedInputStream(new FileInputStream(srcFile));
//使用FileOutputStream 匿名类来接受文件地址,因为如果没有这个文件的话,他会尝试给你创建一个
descBuffer = new BufferedOutputStream(new FileOutputStream(descFile));
byte[] bytes = new byte[1024];
int len;
while ((len = srcBuffer.read(bytes)) != -1) {
//读多少,取多少;
descBuffer.write(bytes,0,len);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
//关闭流:
if (srcBuffer != null){
try {
srcBuffer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (descBuffer != null){
try {
descBuffer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 此方法用来进行复制整个文件夹或文件到新地址去。
* @param srcPath 原地址所在地
* @param descPath 复制去的目标地址
*/
public void copyDir(String srcPath , String descPath){
//获取传进来的地址位置文件或文件夹的对象;
File file = new File(srcPath);
//获取想要复制去的目标地址
File descFile = new File(descPath);
if (file.isDirectory()) {
//如果file是文件夹的话,那么先把目标地址作为文件夹创建出来;
descFile.mkdir();
//获取file中的文件对象:
File[] files = file.listFiles();
for (File f1 : files) {
//判断,如果f1是文件夹的话,那么递归调用本方法;否则执行复制文件的方法;
if (f1.isDirectory()) {
//参数为原地址+地址分隔符+子文件夹的名字,复制到的目标文件夹同理
copyDir(srcPath + File.separator + f1.getName(),descFile + File.separator + f1.getName());
}else {
//调用复制文件的方法;
copyFile(srcPath + File.separator + f1.getName(),descFile + File.separator + f1.getName());
}
}
}
}
}
关于资源的释放的两种方法;
- try - catch - finally 方法 如上图中的代码
- JDK7 之后的 try with resource 方法。
- try(定义资源1,定义资源2){ }catch{ }
上图中对copyFile方法中的try-catch-finally进行修改后
/**
* 这是一个复制文件的方法,只能用来放文件地址
* @param srcFile 原文件地址
* @param descFile 需要复制去的地址;
*/
public void copyFile(String srcFile,String descFile) {
try (//给Buffered输入输出流创建对象
//先创建两个输入输出流:
BufferedInputStream srcBuffer = new BufferedInputStream(new FileInputStream(srcFile));
//使用FileOutputStream 匿名类来接受文件地址,因为如果没有这个文件的话,他会尝试给你创建一个
BufferedOutputStream descBuffer = new BufferedOutputStream(new FileOutputStream(descFile))
//这种是try with resource 的写法,作用是使用完资源后关闭资源的功能,比起try caty finally更简洁
//try with resource 中try() 的括号中放的只能是资源对象;
//什么是资源呢:资源都是会最终实现AutoCloseable接口。资源都有一个close()方法
// 并且资源放到括号里,用完之后会自动调用close的方法完成资源的释放操作
) {
byte[] bytes = new byte[1024];
int len;
while ((len = srcBuffer.read(bytes)) != -1) {
//读多少,取多少;
descBuffer.write(bytes,0,len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
字符输入流FileReader构造器&方法
字符输出流FileWriter构造器&方法![](https://i-blog.csdnimg.cn/blog_migrate/d9649c72e20c2957a5c0ca38ef425f23.png)
注意:使用字符输出流时,必须要调用字符输出流的刷新(flush())或者关闭(close())方法才可以写出数据。原因是因为假如我们需要依次写出100字符到文件中去,那么就需要调用100次系统资源,是个很大的资源浪费。而字符输出流是优化了这个部分,他会在内存中开辟出一个缓冲区,他并不是直接把字符写出到文件中去,而是先写出到缓冲中去,因为缓冲区在内存中,所以写入速度是很快的,再通过调用刷新或者关闭字符输出流的方法,来一次性写出到文件中去。所以如果不调用刷新或者关闭的话,就不会写道文件中去。另外,如果缓冲区满了,他会自动写出到文件中去。
关闭close方法中保护flush方法;
字节缓冲流:BufferedInputStream
BufferedInputStream(InputStream is) :把低级的字节输入流包装成一个高级的缓冲字节输入流,从而提高读数据的性能;
BufferedOutputStream(OutputStream os) :把低级的字节输出流包装成一个高级的缓冲字节输出流,从而提高写数据的性能:
补充:缓冲池都是在内存中的,所以使用1KB数组在内存中把输入缓冲流中的东西读取到输出缓冲流中去的速度会很快,16KB的文件复制使用低级数组调用系统资源了32次(16*2),而使用缓冲流则是了4次(2*2)
字符缓冲流:BufferedReader
BufferedReader(FileReader fr) :创建一个对象;
比较实用的一个方法readLine()方法,每次读取一行数据,在实际开发中,需要读取一行的"密码等于12346"这种,就需要这个方法;
BufferedWriter(FileWriter fw) : 创建一个对象;
bw.newLine()提供了换行操作;
try ( BufferedReader bufferedReader = new BufferedReader(new FileReader("day0819\\src\\abc.text"));){
//比较实用的readLine()方法,每次读取一行数据。
String len ;
while ((len = bufferedReader.readLine()) != null) {
System.out.println(len);
}
} catch (IOException e) {
e.printStackTrace();
}
一个案例:把原始数据复制到新文件中,并且要按序号排列好输出;
原始数据
2,我是第二。
1,我是第一。
5,我是第5、
6,我是第6、
4,我是第4。
3,我是第三。
------------
复制到新文件夹之后的数据
1,我是第一。
2,我是第二。
3,我是第三。
4,我是第4。
5,我是第5、
6,我是第6、
思路,先通过BufferedReader().readLine()方法读取每一行的数据保存到ArrayList()数组中,
然后通过Collections.sort(list)方法进行排序,然后通过BufferedWriter()方法,把整理好的数据
逐行输出到新文件夹;
---------------------------------------------------------------------------------
public static void main(String[] args) {
try (
BufferedReader bufferedReader = new BufferedReader(new FileReader("day0819\\src\\abc.text"));
BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("day0826\\src\\abc.text")))
{
//创建一个用来接受读取到数据的数组
List<String> list = new ArrayList<>();
//读取每一行
String len;
while ((len = bufferedReader.readLine()) != null) {
//把读取到的东西方法数组里面
list.add(len);
}
//对数组内的数据进行排序
Collections.sort(list);
//然后输出到新文件夹里面去;
for (String str : list) {
bufferedWriter.write(str);
bufferedWriter.newLine();
}
} catch (IOException e) {
e.printStackTrace();
}
}
转换流 (InputStreamReader / OutputStreamWriter)
字符输入转换流:InputStreamReader(InputStream is , String charset)
字符输出转换流:OutputStreamWriter(OutputStream os , String charset)
补充:对于字符串输出为指定的字符集,也可以用字节流操作,对输出的字符串调用.getBytes(String charset)就可以获取指定的byte数组,然后输出也可以 ;但是对文件操作,还是用转换流吧
public static void main(String[] args) {
try (
//面对不同的编码集的文件,想要读取很容易乱码,所以这时候需要使用字符输入转换流;
//得到文件的原始字节流;
InputStream is = new FileInputStream("day0826\\src\\abcGBK.text");
//把原始字节流按照指定的字符集编码转换为字符输入流;
Reader isr = new InputStreamReader(is,"GBK");
//把字符输入流包装成缓冲字节输入流
BufferedReader br = new BufferedReader(isr);
){
//然后输出就不会乱码了.
String len;
while ((len = br.readLine()) != null) {
System.out.println(len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
------------------------------------------------------------------------------
public static void main(String[] args) {
try (
//获取源字节输出流
FileOutputStream fo = new FileOutputStream("day0826\\src\\abcGBK22.text");
//获取输出指定字符集为GBK的字符输出转换流
Writer writer = new OutputStreamWriter(fo,"GBK");
//把GBK的封装到Buffered里面;
BufferedWriter bw = new BufferedWriter(writer);
) {
bw.write("我爱你中国");
byte[] gbks = "我哎你".getBytes("GBK");
bw.write("I 爱你 china");
} catch (IOException e) {
e.printStackTrace();
}
}
-------------------------------------------------------------------------------
public static void main(String[] args) {
try (
//使用字节输出流byte,也能起到写入指定字符集的效果;
OutputStream os = new FileOutputStream("day0826\\src\\abcGBK23.text")
) {
byte[] gbks = "我啊你哈哈".getBytes("GBK");
os.write(gbks);
} catch (IOException e) {
e.printStackTrace();
}
}
打印流:(PrintStream / PrintWriter)
这两个流一个是继承OutputStream , 一个是继承Writer, 他们的核心功能都是println()方法。这里面也封装了buffered输出流,所以性能也和Buffered差不多。
另外注意,如果不想覆盖操作,想在文件后面添加,需要使用 PrintWriter(new FileOutPutstream( "文件地址" , true))来操做,因为这个高级流没有,需要用高级流包装低级流来实现;
打印流的一种应用,
在实际开发中,可能不能把信息打印到控制台,那我们可以让信息打印到一个文件中,这就需要打印流,只要改掉系统默认的打印流就可以了:
上面的代码
查看System.out.println()的源码,会看到里面是其实也是PrintStream打印流,只不过输出地址是控制台而已;
public static void main(String[] args) {
try ( //创建一个打印流
PrintStream ps = new PrintStream(new FileOutputStream("day0826\\src\\abcGBK24.text"));
) {
//把系统默认打印流对象设为自己的打印流对象:
System.setOut(ps);
System.out.println("haha1");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
数据输入输出流:DataInputStream / DataOutputStream
可以连带数据的类型一起写出去,在通讯场景中好像经常被使用。 但是要注意,读取的顺序不能错,比如这个是int类型的,就不能用字符串方法来读;
序列化流:ObjectInputStream / ObjectOutputStream
想要被序列化的流必须实现Serializable接口,就是javaBean要实现的那个接口。这个接口类似于一个标记,实现了这个接口虚拟机就会帮你序列号,然后会启动装载程序,把对象存到文件中去。
注意:如果javaBean中有不想被序列化然后存储的数据比如密码等敏感信息,可以使用transient来进行修饰
想要类可以被序列号,需要实现Serializable接口
public class Student implements Serializable {
private int age;
private String name;
private double money;
//加上transient修饰符,就代表这个成员变量不参与序列化;
private transient String password;
省略一些get ,set方法,toString等,构造器
}
-----------------------------------------------------------------------------------
public static void main(String[] args) {
//创建一个学生对象 ,要求对密码不进行序列化,不对某个属性序列号,需要在类中对该属性使用修饰符transient
Student student = new Student(15, "张三", 250.0 , "1313556");
try (
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("day0826\\src\\abcGBK25.text"));
) {
oos.writeObject(student);
} catch (IOException e) {
e.printStackTrace();
}
}
存入的结果为(这不是乱码,而是带有数据类型的一种存储方式)
�� sr test.Student�`�mH�� I ageD moneyL namet Ljava/lang/String;xp @o@ t 张三
-------------------------------------------------------------------------------------
读取操作
public static void main(String[] args) {
try (
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("day0826\\src\\abcGBK25.text"));
){
Object o = ois.readObject();
System.out.println(o);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
IO流框架 commons-io-2.13.0.jar
一些方法介绍,具体其他方法可以去找API
Java中也提供了一个原生的文件操作工具类: Files
具体请参照java的Api。 据说不如上面的第三方框架功能强大,不过可能够用了。
复制文件 Files.copy(Path.of("源文件地址"),Path.of("目标文件地址");
把文件内容读出来 Files.readString("源文件地址") 返回一个String对象