前言
IO流是用于读写文件中的数据,要读写文件之前可以创建文件获取文件对象再创建IO流,正文会先介绍File类,通过File类的构造方法获取文件的对象,创建文件或目录以及File类的一些方法获取文件对象的属性。后面还介绍了相关的IO流体系,字节流和字符流的区别用法。
目录
前言
一、File
1.常用的成员方法
(1) 判断、获取
(2)创建、删除
(3)获取并遍历
2. 实操
(1)遍历C盘下的以.avi结尾的文件
(2)删除一个多级文件夹(里面的有些文件有内容)
(3)统计各种文件的数量
二、IO流分类
三、IO体系
1.字节流
2.字符流
四、字节流
1.字节输出流
(1)用法
(2)进阶用法(换行和续写)
2.字节输入流
(1)用法
3.拷贝
五、try-with-resources
六、字符集(ASCLL、GBK、Unicode)
七、读取出现乱码
1.原因
2.解决方法
3.编码和解码
八、字符流
1.特点
2.使用场景
3.字符输出流
(1)用法
(2)底层原理
4.字符输入流
(1)用法
(2)底层原理
九、字节流和字符流实战
1.拷贝文件夹
2.加密解密
3.将文件排序
一、File
1.常用的成员方法
(1) 判断、获取
注意: 1.length返回文件的大小(字节数量)
细节1:这个方法只能获取文件的大小,单位是字节
如果单位我们要是M,G,可以不断的除以1024
细节2:这个方法无法获取文件夹的大小
如果我们要获取一个文件夹的大小,需要把这个文件夹里面所有的文件大小都累加在一起。
2.getName获取名字
细节1:file是文件会返回文件的文件名和扩展名(如.txt .cpp)
细节2:file是文件夹就返回文件夹的名字。
(2)创建、删除
注意:1.createNewFile创建一个新的空的文件
细节1:如果当前路径表示的文件是不存在的,则创建成功,方法返回true
如果当前路径表示的文件是存在的,则创建失败,方法返回false
细节2:如果父级路径是不存在的,那么方法会有异常IOException
细节3:createNewFile方法创建的一定是文件,如果路径中不包含后缀名,则创建一个没有后缀的文件
2.mkdir创建一个文件夹(目录)
细节1:windows当中路径是唯一的,如果当前路径已经存在,则创建失败,返回false
细节2:mkdir方法只能创建单级文件夹,无法创建多级文件夹。
3. mkdirs创建多级文件夹
细节:既可以创建单级的,又可以创建多级的文件夹
4.delete删除文件、空文件夹
细节:如果删除的是文件,则直接删除,不走回收站。
如果删除的是空文件夹,则直接删除,不走回收站
如果删除的是有内容的文件夹,则删除失败
(3)获取并遍历
注意:细节:
//1.创建File对象
File f = new File("D:\\aaa");
//2.listFiles方法
//作用:获取aaa文件夹里面的所有内容,把所有的内容放到数组中返回
File[] files = f.listFiles();
for (File file:files) {
//file依次表示aaa文件夹里面的每一个文件或者文件夹
System.out.println(file);
}
2. 实操
(1)遍历C盘下的以.avi结尾的文件
递归,一定要先用if判断文件数组是否为空,否则会抛出空指针异常
(2)删除一个多级文件夹(里面的有些文件有内容)
(3)统计各种文件的数量
/*
* 作用:
* 统计一个文件夹中每种文件的个数
* 参数:
* 要统计的那个文件夹
* 返回值:
* 用来统计map集合
* 键:后缀名 值:次数
*
* a.txt
* a.a.txt
* aaa(不需要统计的)
*
*
* */
public static HashMap<String,Integer> getCount(File src){
//1.定义集合用来统计
HashMap<String,Integer> hm = new HashMap<>();
//2.进入src文件夹
File[] files = src.listFiles();
//3.遍历数组
for (File file : files) {
//4.判断,如果是文件,统计
if(file.isFile()){
//a.txt
String name = file.getName();
String[] arr = name.split("\\.");
if(arr.length >= 2){
String endName = arr[arr.length - 1];
if(hm.containsKey(endName)){
//存在
int count = hm.get(endName);
count++;
hm.put(endName,count);
}else{
//不存在
hm.put(endName,1);
}
}
}else{
//5.判断,如果是文件夹,递归
//sonMap里面是子文件中每一种文件的个数
HashMap<String, Integer> sonMap = getCount(file);
//hm: txt=1 jpg=2 doc=3
//sonMap: txt=3 jpg=1
//遍历sonMap把里面的值累加到hm当中
Set<Map.Entry<String, Integer>> entries = sonMap.entrySet();
for (Map.Entry<String, Integer> entry : entries) {
String key = entry.getKey();
int value = entry.getValue();
if(hm.containsKey(key)){
//存在
int count = hm.get(key);
count = count + value;
hm.put(key,count);
}else{
//不存在
hm.put(key,value);
}
}
}
}
return hm;
}
}
二、IO流分类
纯文本文件,就是Windows自带的记事本打开能读懂的。比如(.txt .md)
三、IO体系
1.字节流
文件操作是程序通过IO流来读写文件。
输入的意思是将文件的数据读进来,进到程序来,加上操作的对象是本地文件,所以实现类FileInputStream叫做操作本地文件的字节输入流。
相反的,输出的意思就是,文件的操作者——程序,将修改的数据写到文件里面,即输出到文件去。加上操作的对象是本地文件,所以实现类FileOutputStream叫做操作本地文件的字节输出流。
字节流不仅有文件流 ,还有缓冲流BufferedInputStream、BufferedOutputStream,数据流DataInputStream、DataOutputStream,对象流ObjectInputStream、ObjectOutputStream,打印流PrintStream等等还有很多。
2.字符流
字符流的输入和输出和字节流的输入输出一个意思,不同的就是字符流的底层是字节流+字符集。字节流是一个字节一个字节读取的,而字符流是普通字符就一个字节,遇到中文就按字符集的规则多个字节读。
字符流不仅有文件字符流,还有缓冲字符流等等
四、字节流
以文件流FileInputStream、FileOutputStream为例
1.字节输出流
(1)用法
1.创建字节输出流对象
2.写数据
3.释放资源
详解:
1.创建字节流对象(2+2)
下面是两个构造方法 传递一个绝对/相对路径的字符串 或者 传递一个File对象
//通过文件的路径创建文件输出流 FileOutputStream fos = new FileOutputStream("files\\test1.txt"); //通过文件对象创建文件输出流 File file = new File("files\\test1.txt"); FileOutputStream fos1 = new FileOutputStream(file);
细节:参数是字符串表示的路径或者是File对象都是可以的
如果文件不存在构造方法会创建一个新的文件,但是要保证父级路径是存在的。
如果文件存在构造方法会清空文件
如果我们不想要创建字节流的时候把原本文件内容清空,我们必须使用另外的两个构造器,
在原来一个参数的构造器的基础上,加上第二个参数,true表示续写。
2.写数据(write方法3种参数)
细节:write方法的参数是整数,但是实际上写到本地文件中的是整数在ASCII上对应的字符
一个一个查ASCLL这样子写下来会不会太麻烦了?
还有更简便的方法,将想要的数据传入一个String字符串,再通过String类的getBytes()方法将字符串转换为字节数组,最后将字节数组传递给write方法。
3.释放资源
每次使用完流之后都要释放资源
细节:如果流不关闭,并且当前使用流的程序没有停止,那么这个文件就不能被删除。
(2)进阶用法(换行和续写)
1.换行写:
再次写出一个换行符就可以了(每一个操作系统的换行符都不一样)
windows: \r\n
Linux: \n
Mac: \r
写入文件的内容为:
kankelaiyezuishuai
666
2.续写
续写就是创建输出流的时候选用有第二个参数append的构造器。如下
2.字节输入流
(1)用法
1.创建字节输入流对象
2.读数据
3.释放资源
1.创建字节输入流对象
通过输入流的构造方法,创建输入流对象,一样都是传入一个绝对/相对路径的字符串或File对象
细节:如果文件不存在,就直接报错。
Java为什么会这么设计呢?
看前面的输出流:不存在,就创建,把数据写到文件当中
而为什么输入流不存在是报错呢?
因为创建出来的文件是没有数据的,没有任何意义。所以Java就没有设计这种无意义的逻辑,文件不存在直接报错。
2.读数据(read方法3个)
int read()读取一个字节,读完后指针后移一位
int read(byte[] b)从该输入流读取最多
b.length
字节的数据到字节数组。以整数形式返回实际读取的字节数。细节:无参的read方法一次读一个字节,读出来的是数据在ASCII上对应的数字
读到文件末尾了,read方法返回-1。
有参的read方法一次读一个字节数组的数据,读到的数据是以覆盖的形式覆盖原本的字节数组,比如说一个文件内容为abcde以长度为2的字节数组读取
FileInputStream fis = new FileInputStream("files\\test1.txt"); byte[] buffer = new byte[2]; int len; //字节个数是2 读到的数据ab覆盖空数组 len = fis.read(buffer);//2 System.out.println("读取了" + len + "个字节,数据为"+ new String(buffer,0,2));//ab //字节个数是2 读到的数据cd覆盖掉原本的ab len = fis.read(buffer);//2 System.out.println("读取了" + len + "个字节,数据为"+ new String(buffer,0,2));//cd //字节个数是1 读取的数据e只覆盖掉原本的cd的第一个字节 len = fis.read(buffer);//1 System.out.println("读取了" + len + "个字节,数据为"+ new String(buffer,0,2));//ed //字节个数是-1 无覆盖还是ed len = fis.read(buffer);//-1 System.out.println("读取了" + len + "个字节,数据为"+ new String(buffer,0,2));//ed
优化后代码(实际上用的)
3.释放资源
每次使用完流必须要释放资源。
注意:用字节流读取文件中的中文,会输出乱码。原因后面第七有讲
3.拷贝
顾名思义就是将一个文件的内容复制到另外一个文件。
拷贝的核心思想就是边读边写
一个字节从文件读到程序,程序写到文件
上面这种拷贝仅适用于拷贝小文件,内存比较小的。
弊端:一次读写一个字节,太慢了,大文件拷贝不适用
优化后:一个5M的字节数组读写拷贝
五、try-with-resources
注意:try后面括号里面的流类型必须实现AutoCloseable接口,它爹实现也行。
六、字符集(ASCLL、GBK、Unicode)
ASCLL:一个字节,0~127一共128个字符
GBK:两个字节,第一个字节的第一个二进制数一定是1,所以第一个字节一定是负数,第二个字节可正可负
Unicode:不同编码规则不同字节数,以UTF-8为例 是1~4个字节(一个英文占一个字节,一个中文占3个字节)
七、读取出现乱码
1.原因
1.读取数据时未读完整个汉字
2.编码和解码的方式不统一
2.解决方法
1.不要使用字节流读取文本文件
2.编码和解码的时候使用同一个码表,即同一个编码方式
注意:拷贝的时候,出现乱码跟使用字节流无关,使用字节流出现乱码的情况是读取单个数据的时候,拷贝是整个文件的所有字节。如果拷贝出现乱码原因就只有编码和解码的方式不统一 。
3.编码和解码
idea默认编码方式是utf-8 中文占3个字节
Eclipse则是GBK 中文占2个字节
示例
String str = "ai你余"; //编码 byte[] bytes = str.getBytes(); System.out.println(Arrays.toString(bytes)); //以GBK解码 System.out.println(new String(bytes,"GBK"));
运行结果
编码和解码的编码格式不一样,导致乱码。
八、字符流
1.特点
字符输入流:一次读一个字节,遇到中文读多个字节 (具体读几个与字符集有关)
字符输出流:底层会把数据按照指定的编码方式进行编码,变成字节再写到文件中
2.使用场景
对于纯文本文件进行读写
3.字符输出流
(1)用法
1.创建字符输出流对象
2.写数据
3.释放资源
详解:
1.创建字符输出流对象
前两个构造方法打开一个文件的输出流就会清空文件,如果文件不存在就创建文件,后面两个第二个参数传入true表示不会清空文件
2.写数据
细节:如果write方法的参数是整数,但是实际上写到本地文件中的是整数在字符集上对应的字符
虽然字节流也有一样的int参数的write方法,但是字节流的只能传入0~127的范围,不然写到文件中的是乱码。而字符流写入文件会按照字符集来编码,超过127的编码成中文。
(2)底层原理
字符流写数据的时候是先将数据编码成字节写到缓冲区,缓冲区达到上面三种情况,就会写到文件里面。 刷新缓冲区flush和关闭流close是有区别的。
4.字符输入流
(1)用法
1.创建字符输入流对象
2.读取数据
3.释放资源
详解:
1.创建字符输入流对象
细节:如果文件不存在,就直接报错
2.读取数据
使用无参的read方法是返回的是一个或多个(中文)的字节码的解码(已解码转十进制),要得到文件原本的内容就必须强转 (char)返回值
使用有参的read方法与字节流不同的是读到的数据用字符数组接收。
细节: 按字节进行读取,遇到中文,一次读多个字节,读取后解码,返回一个整数
读到文件末尾了,read方法返回-1。
3.释放资源
(2)底层原理
细节:如果缓冲区的8192个字节读完,缓冲区就会刷新,重新向文件读取新的字节数据,如果新的字节数据长度没有达到8192,新的字节数据只会覆盖缓冲数组前面的部分,后面部分保持原来的数据。
九、字节流和字符流实战
应用场景
字节流 拷贝任意类型的文件
字符流 拷贝纯文本文件
1.拷贝文件夹
public static void main(String[] args) throws IOException {
File src = new File("files");
File dest = new File("copy");
copy(src,dest);
}
private static void copy(File src,File dest) throws IOException {
if (src.exists()){
return;
}else if (src.isDirectory()){
File[] files = src.listFiles();
if (files != null) {
for (File file : files) {
String name = file.getName();
String destPath = dest.getAbsolutePath();
String path = destPath+ File.separator+name;
File file1 = new File(path);
if (file.isDirectory()){
file1.mkdir();
copy(file,file1);
}else {
try (FileWriter fileWriter = new FileWriter(file1,true);
FileReader fileReader = new FileReader(file)) {
int len;
while ((len = fileReader.read()) != -1) {
fileWriter.write(len);
fileWriter.flush();
}
}
}
}
}
}
}
2.加密解密
加密就是,将一个文件写到另外一个文件的时候,对流中的每一个字节做处理,比如下面的例子就是异或一个2
解密就是,将一个无法解读的文件,写到另外一个文件,对每一个字节做解密处理,下面的例子加密时是异或一个2,那么解密就是继续异或一个2异或回来
3.将文件排序
文件里的内容是[1-9-2-6-5-4-3]
重新排序成[1-2-3-4-5-6-9]
//1.获取文件中的数据 FileReader fr = new FileReader("files\\test1.txt"); StringBuilder sb = new StringBuilder(); int len; while ((len = fr.read()) != -1) { sb.append((char) len); } //2.排序 String[] strings = sb.toString().substring(1, sb.length() - 1).split("-"); Integer[] arr = Arrays.stream(strings) .map(Integer::parseInt) .sorted() .toArray(Integer[]::new); //3.写出 FileWriter fw = new FileWriter("files\\test1.txt"); fw.write(Arrays.toString(arr).replace(", ", "-")); fr.close(); fw.close();