java-IO流
1,初识IO流
注意:File类只能对文件本身进行操作,不能读写文件里面存储的数据。而IO流可以进行数据读取,写入,传输
IO流:用于读写文件中的数据(可以读写文件,或网络中的数据…)
2,流的分类
- 按照读写数据的基本单位不同分为:字节流(8 bit) 二进制文件,字符流(16 bit,两个字节) 文本文件
- 按照数据的流向不同分为:输入流 和 输出流 (电脑输出到设备,设备输入到电脑)
- 按照流的角色不同分为:节点流、处理流/包装流
注:word,excel不属于纯文本文件
3,基本字节流
一个字节一个字节读取,遇到中文,可能会乱码,中文在UTF-8编码格式中是占3个字节
3.1,FileOutputStream
字节输出流,把程序中的数据写到本地文件上
字节输出流写出数据的细节
1.创建字节输出流对象
细节1:参数是字符串表示的路径或者File对象都是可以的
细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的。
细节3:如果文件已经存在,则会清空文件
2.写数据
细节: write方法的参数是整数,但是实际上写到本地文件中的是整数在ASCII上对应的字符
比如上面的97,对应写入a.txt文件中就是字母a
3.释放资源
细节:每次使用完流之后都要释放资源,解除了资源的占用
FileOutputStream写数据的3种方式
void write(int b) //一次写一个字节数据
void write(byte[] b) //一次写一个字节数组数据,提高传输效率,一个字节一个字节的传输太慢了
void write(byte[] b,int off,int len) //一次写一个字节数组的部分数据
3.2,FileInputStream
字节输入流,可以把本地文件的数据读取到程序中
使用步骤及注意细节
1.创建字节输入流对象
细节1:如果文件不存在,就直接报错。
2.读取数据
细节1:一次读一个字节,读出来的是数据在ASCII上对应的数字
细节2:读到文件末尾了, read方法返回-1。read :表示读取数据,而且是读取一个数据就移动一次指针
3.释放资源
细节1∶每次使用完流必须要释放资源。
循环读取
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("IO\\src\\main\\resources\\hello.txt");
int b = 0;
//read :表示读取数据,而且是读取一个数据就移动一次指针
while ((b = fis.read()) != -1){//注意要用变量接收
System.out.print((char) b);
}
fis.close();
}
4,文件拷贝
边读边写,下面这种方式只适用于小文件拷贝
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("IO\\src\\main\\resources\\1.jpg");
FileOutputStream fos = new FileOutputStream("IO\\src\\main\\resources\\bak.jpg");
//边度边写
int b = 0;
while (( b = fis.read() ) != -1){
fos.write(b);
}
//释放资源注意:先开的最后关闭
fos.close();
fis.close();
}
耗时慢,因为FilelnputStream一次读写一个字节,拷贝文件有多少个字节,这里就要循环多少次
解决方法:一次读取多个数据
注意:一次读一个字节数组的数据,每次读取会尽可能把数组装满
public static void main(String[] args) throws IOException {
FileInputStream fis = new FileInputStream("IO\\src\\main\\resources\\1.jpg");
FileOutputStream fos = new FileOutputStream("IO\\src\\main\\resources\\bak.jpg");
int len = 0;
byte[] bytes = new byte[1024 * 1024];
while ((len = fis.read(bytes)) != -1){
//由于数组每次元素不会清空,只是在读时,把读到的元素覆盖到数组对应索引中,若没读满的情况,数组剩余位置元素还是老数据没清除
fos.write(bytes, 0, len); //读多少,写多少
}
fos.close();
fis.close();
}
这个用数组拷贝比上面的单个字节拷贝快了近80多倍
5,字符集
ASCII字符集存储英文的情况,ASCII表中对应的最大数字时127,所以英文都可以刚好用一个字节就能表示出来,ASCII表中是没有汉字的,所以存储汉字,也需要有一个像ASCII表那样的存储规则,然后GBK和Unicode出手了。
汉字之所以是用两个字节(16位二进制)存储,是因为一个字节只能表示8位二进制,也就是256个汉字,明显不够存储所有汉字种类,所以规定用2个字节存储,也就是2的16次方, 转十进制为65535,6万个已经够表示全部汉字种类了。
GBK完全兼容ASCII字符集,为了和英文区别开
- 核心1:GBK中,一个英文字母一个字节,二进制第一位是0
- 核心2:GBK中,一个中文汉字两个字节,二进制第一位是1
Unicode字符集的UTF-8编码格式
- 一个英文占一个字节,二进制第一位是0,转成十进制是正数
- 一个中文占三个字节,二进制第一位是1,第一个字节转成十进制是负数
6,乱码原因
原因1:读取数据时未读完整个汉字
以字节流读取为例,UTF-8中汉字占3个字节,字节流每次读取一个字节,当然会乱码
原因2:编码和解码时的方式不统一
上面既然说到了乱码原因,那么解决方法也随之而来,第二个原因就不说了,手动统一格式就完了,主要是原因一的,下面字符流就带着解决方法来了
7,基本字符流
一个字符一个字符读取,可以读取中文而不乱码,主要是对纯文本文件进行读写操作
7.1,FileReader
空参Read方法使用如下
public static void main(String[] args) throws IOException {
FileReader fr = new FileReader("IO\\src\\main\\resources\\go.txt");
int ch;
while ((ch = fr.read()) != -1){
System.out.print((char) ch);
}
fr.close();
}
read ()细节:
1.read():默认也是一个字节一个字节的读取的,如果遇到中文就会一次读取多个
2.在读取之后,方法的底层还会进行解码并转成十进制。
最终把这个十进制作为返回值
这个十进制的数据也表示在字符集上的数字,如下:
英文:文件里面二进制数据0110 0001
read方法进行读取,解码并转成十进制97
中文:文件里面的二进制数据11100110 10110001 10001001
read方法进行读取,解码并转成十进制27721
字节输入流除了一个字节一个字节的,还可以通过数组读;当然,这个字符输入流也不输它,字符输入流也可以使用数组读,代码格式如下
FileReader fr = new FileReader("IO\\src\\main\\resources\\go.txt");
char[] chars = new char[2];//用数组的方式,每次读两个
int len = 0;
while ((len = fr.read(chars)) != -1){
System.out.println(new String(chars, 0, len));
}
fr.close();
read带参方法底层实现
read(chars):读取数据,解码,强转三步合并了,把强转之后的字符放到数组当中
相当于空参的read +强转类型转换
字符输入流底层原理:读取数据,会先放入缓冲区,缓冲区读满后还在继续读数据,就会再从头开始覆盖缓冲区前面的数据
注意:字节流没有缓冲区,字符流有缓冲区
7.2,FileWriter
构造方法
public FileWriter(File file) //创建字符输出流关联本地文件
public FileWriter(String pathname) //创建字符输出流关联本地文件
public FileWriter(File file,boolean append) //创建字符输出流关联本地文件,续写
public FileWriter(String pathname ,boolean append) //创建字符输出流关联本地文件,续写
成员方法
void writer(int c) //写出一个字符
void writer(String str) //写出一个字符串
void writer(String str,int off,int len)
void writer(char[] c) //写出一个字符数组
void writer(char[] c,int off,int len)
各种细节规则和字节输出流的基本一致,字符输出流,写出数据,是先写到缓冲区的。
经典案例Demo
案例:拷贝文件夹及其子文件到目标文件夹中
public static void main(String[] args) throws IOException {
/*
文件夹拷贝
*/
//创建源文件对象和目标文件对象
File src = new File("D:\\a");
File dest = new File("D:\\b");
copyDir(src,dest);
}
public static void copyDir(File src, File dest) throws IOException {
dest.mkdirs();
File[] files = src.listFiles();
if (files == null || files.length == 0){
System.out.println("没有访问权限");
}
for (File file : files) {
if (file.isFile()){
int len = 0;
byte[] bytes = new byte[1024];
FileInputStream fis = new FileInputStream(file);
FileOutputStream fos = new FileOutputStream(new File(dest, file.getName()));
while ((len = fis.read(bytes)) != -1){
fos.write(bytes, 0, len);
}
fos.close();
fis.close();
}else {
copyDir(file, new File(dest, file.getName()));
}
}
}
案例:文件加密与解密
public static void JiaMi() throws IOException {
FileInputStream fis = new FileInputStream("IO\\src\\main\\resources\\1.jpg");
FileOutputStream fos = new FileOutputStream("IO\\src\\main\\resources\\bak.jpg");
//加密处理,进行异或
int b = 0;
while((b = fis.read()) != -1){
fos.write(b^2);
}
fos.close();
fis.close();
}
对文件中的数字进行排序
public static void main(String[] args) throws IOException {
//对test.txt中的数字排序 2-4-5-1-3
//变成1-2-3-4-5
FileReader fr = new FileReader("IO\\src\\main\\resources\\test.txt");
StringBuffer sb = new StringBuffer();
int ch;
while ((ch = fr.read()) != -1){
sb.append((char)ch);//用StringBuffer来接收读入的数据
}
fr.close();
System.out.println(sb);
//转为字符串
String str = sb.toString();
//进行切割,获取数字
String[] strings = str.split("-");
//存入list集合排序
ArrayList<Integer> list = new ArrayList<>();
if (strings == null || strings.length == 0){
System.out.println("文件中没有数据");
}
for (String s : strings) {
int i = Integer.parseInt(s);
list.add(i);
}
Collections.sort(list);
System.out.println(list);
//把排序后的数据写出
FileWriter fw = new FileWriter("IO\\src\\main\\resources\\test.txt");
for (int i=0; i < list.size(); i++){
if (i == list.size() - 1){
fw.write(list.get(i) + "");
}else {
fw.write(list.get(i) + "-");
}
}
fw.close();
}
8,高级流
由于基本输入输出流的效率太低,所以基于基本流又出现了缓冲流,转换流,序列化流,打印流等等
8.1 缓冲流
缓冲流分为字符缓冲流和字节缓冲流:
字符缓冲流包括 BufferedReader (字符缓冲输入流) 和 BufferedWriter (字符缓冲输出流)
字节缓冲流包括 BufferedInputStream (字节缓冲输入流) 和 BufferedOutputStream (字节缓冲输出流)
8.1.1 字节缓冲流
真正读写数据的还是原来的基本流
8.1.2 字符缓冲流
它也是底层自带了长度为8192的缓冲区提高性能,但是基本的字符流已经自带缓冲区了,所以这个字符缓冲流效率提升的不是很明显。
字符缓冲输入流特有方法
public String readLine() //读取一行数据,如果没有数据,返回null
字符缓冲输出流特有方法
public void newLine() //跨平台换行
特有方法,读取一行的使用示例
public static void main(String[] args) throws IOException {
//创建字符缓冲流对象
BufferedReader br = new BufferedReader(new FileReader("IO\\src\\main\\resources\\hello.txt"));
//读取数据
//readline方法在读取的时候,一次读一整行,遇到回车换行自动结束读取
//但是它不会把回车换行读到内存当中
String line;
while ((line = br.readLine()) != null){
System.out.println(line);
}
br.close();
}
特有方法,跨平台换行
//创建字符缓冲输出流对象,注意看续写true放的位置
BufferedWriter bw = new BufferedWriter(new FileWriter("IO\\src\\main\\resources\\hello.txt",true));
//写出数据
bw.write("你歪嘴的样子,龙王自愧不如");
//加一个所有系统通用的换行符
bw.newLine();
bw.write("如果我结婚了,你一定要来哦,没有新娘我会很尴尬");
bw.newLine();
//释放资源
bw.close();
细节1:字符缓冲流的缓冲区大小为16k
细节2:输出流关联文件时,文件存在,若没有添加续写开关,内容即清空
8.2 转换流
作用:可以避免乱码,字节流也可以使用字符流中的特定方法
读取GBK文件
public static void main(String[] args) throws IOException {
//1.创建转换流对象,并指定字符编码
InputStreamReader isr = new InputStreamReader(new FileInputStream("GBK格式文件路径"), "GBK");//字节流转字符流
//2.读取数据
int ch;
while ((ch = isr.read()) != -1){
System.out.println((char) ch);
}
isr.close();
}
字节流使用字符流的独有方法,比如readLine()
利用字节流读取文件中的数据,每次读一整行,而且不能出现乱码
1.字节流在读取中文的时候,是会出现乱码的,但是字符流可以搞定
2.字节流里面是没有读一整行的方法的,只有字符缓冲流才能搞定
8.3 序列化流
序列化流是高级流的一种,它也是字节流
序列化流/对象操作输出流
可以把Java中的对象写到本地文件中,正常方式打开显示的是乱码
作用:就是把对象存储本地文件,避免其他人篡改数据
序列化流也是高级流,所以也需要关联基本流,让基本流干活,它只是包装
//1.创建对象
//2.创建序列化对象
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("把对象写入哪个文件的路径"));
//3.写出数据,对象放进去
//oos.writeObject();
//4.释放资源
oos.close();
序列化流的小细节
使用对象输出流将对象保存到文件时会出现NotSerializableException异常
解决方案:需要让Javabean类实现Serializable接口
反序列化流/对象操作输入流,可以把序列化到本地文件中的对象,读取到程序中来
8.4 打印流
注意打印流不能读,只能写,所以只有字节,字符的输出流,为了方便记忆,就认为它是流中的唯一单身狗,其他都是成双成对。
使用范例
public static void main(String[] args) throws IOException {
//1.创建字节打印流对象
PrintStream ps = new PrintStream(new FileOutputStream("IO\\src\\main\\resources\\printlnTest.txt"),true,"UTF-8");
//2.写出数据——>三个功能:写出+自动刷新+换行
ps.println(97);
ps.println(true);
ps.printf("%s 爱上了 %s","阿珍","阿强");//%s是字符串占位符
ps.close();
}
8.5 解压缩流/压缩流
Java只能识别zip后缀的,解压本质就是把压缩包里面的每一个文件或者文件夹读取出来,按照层级拷贝到目的地
ZipEntry表示当前在压缩包里的每一个文件或文件夹
解压缩代码如下
public static void unzip(File src, File dest) throws IOException{
//解压本质就是把压缩包里面的每一个文件或者文件夹读取出来,按照层级拷贝到目的地
//创建一个解压缩流用来读取压缩包中的数据
ZipInputStream zip = new ZipInputStream(new FileInputStream(src));
//要先获取到压缩包里面的每一个zipentry对象
//表示当前在压缩包中获取的文件或文件夹
ZipEntry entry;
while ((entry = zip.getNextEntry()) != null){
System.out.println(entry);
if (entry.isDirectory()){
//文件夹,需要在目的地创建一个同样的文件夹
File file = new File(dest, entry.toString());
file.mkdirs();
}else {
//文件:需要读取到压缩包中的文件,并把他存放到目的地dest文件夹中(按照层级目录进行存放)
FileOutputStream fos = new FileOutputStream(new File(dest, entry.toString()));
int b;
while ((b = zip.read()) != -1){
//写到目的地
fos.write(b);
}
fos.close();
}
}
zip.close();
}
压缩代码
public static void toZip(File src, File dest) throws IOException{
//1.创建压缩流关联压缩包 或者文件名.zip
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File(dest, src.getName().split("\\.")[0] + ".zip")));
//2.创建ZipEntry对象,表示压缩包里面的每一个文件和文件夹
ZipEntry entry = new ZipEntry("要压缩的文件");
//3.把ZipEntry对象放到压缩包当中
zos.putNextEntry(entry);
//4.把src文件中的数据写到压缩包当中
FileInputStream fis = new FileInputStream(src);
int b;
while ((b = fis.read() )!= -1){
zos.write(b);
}
zos.closeEntry();
zos.close();
}
9,Commons-io工具包
简化代码编写,相关操作只需要直接调用工具包中的api接口。
10,Hutool工具包
国内大牛开发的,解释都是中文,简明易懂。其中也有关于io和file的工具包,导包后可以直接调用它对应的api,简化开发。
官网:https://hutool.cn/
API文档:https://apidoc.gitee.com/dromara/hutool/
中文使用文档:https://hutool.cn/docs/#/
11,其它——File的概述
File对象就表示一个路径,可以是文件的路径、也可以是文件夹的路径。
这个路径可以是存在的,也允许是不存在的。
1.File的创建
public File(String pathname) 根据文件路径创建文件对象
public File(String parent,String child) 根据父路径名字符串和子路径名字符串创建文件对象
public File(File parent,String child) 根据父路径对应文件对象和子路径名字符串创建文件
对象
2,File的常见成员方法
1:判断和获取
public boolean isDirectory() 判断此路径名表示的File是否是文件夹
public boolean isFile() 判断此路径名表示的File是否是文件
public boolean exists() 判断此路径名表示的File是否存在
public long length() 返回的是文件的大小(字节数量,/1024是KB,/1024/1024是MB以此类推)
public String getAbsolutePath() 返回文件的绝对路径
public String getPath() 返回定义文件时使用的路径
public String getName() 返回文件的名称,带后缀(没有后缀的文件也是存在的,叫纯文本文件)
public long lastModified() 返回文件的最后修改时间(时间毫秒值)
2:创建和删除
public boolean createNewFile() 创建一个新的空的文件
public boolean mkdir() 创建单级文件夹
public boolean mkdirs() 创建多级文件夹
public boolean delete() 删除文件、空文件夹
它们的返回值是boolean类型的,也就是是否创建成功
(1)createNewFile()
如果当前路径表示的文件是不存在的,则创建成功,返回true
如果当前路径表示的文件是存在的,则创建失败,方法返回false
如果父级路径不存在,那么方法会有异常IOException
createNewFile方法创建的一定是文件,如果路径中不包含后缀名,则创建一个没有后缀的文件(纯文本文件也是文件)
(2)mkdir()
windwos当中路径是唯一的,如果当前路径已经存在则创建失败,返回false,(文件夹不可和纯文本文件重名,其他文件可以因为是有后缀的)
mkdir只能创建单级文件夹,无法创建多级文件夹(也就是无法在不存在的文件夹下再继续创建文件夹)
创建一个文件夹
(3)delete()
如果删除的是文件则直接删除,不走回收站
如果删除的是空文件夹,则直接删除,不走回收站
如果删除的是有内容的文件夹,则删除失败
3:获取并遍历
public File[] listFiles() 获取当前路径下所有内容
public static File[] listRoots() 列出可用的文件系统根
public String[] list() 获取当前该路径下所有内容
public String[] list(FilenameFilter filter) 利用文件名过滤器获取当前该路径下所有内容
public File[] listFiles() 获取当前该路径下所有内容
public File[] listFiles(FileFilter filter) 利用文件名过滤器获取当前该路径下所有内容
public File[] listFiles(FilenameFilter filter) 利用文件名过滤器获取当前该路径下所有内容
class Base{
public static void main(String[] args) throws IOException {
File s=new File(“D:\MySQL”);
File[] ss=s.listFiles();
for(File f:ss) {
System.out.println(f);
}
}
}
3.使用File具体实现需求
(1)定义一个方法找某一个文件夹中,是否有以txt结尾的文件
class Base{
public static void main(String[] args) throws IOException {
File fi=new File("D:\\html\\ty");
File[] f=fi.listFiles();
for(File i:f) {
System.out.println(i);
}
System.out.println(findfile(fi));
}
public static boolean findfile(File file) {
//递归寻找
File[] fi=file.listFiles();
for(File f:fi) {
if(f.isFile()) {
if(f.getName().endsWith(".txt")) {
return true;
}
}
}
return false;
}
}
(2)找出电脑中所有以.avi结尾的文件,并输出
class Base{
public static void main(String[] args) throws IOException {
File file =new File("D:\\");
findfile(file);
}
public static void findfile() {
File[] s=File.listRoots();
for(File i:s) {
findfile(i);
}
}
public static void findfile(File file) {
File[] fi=file.listFiles();
if(fi!=null) {
for(File i:fi) {
if(i.isFile()) {
//是文件
if(i.getName().endsWith(".avi")) {
System.out.println(i.getName());
}
}
else {
//是文件夹,开始递归查找
findfile(i);
}
}
}
}
}
(3)删除一个多级文件夹(前面学到的delete还能删除文件和一个空的文件夹,无法删除有内容的文件夹)
class Base{
public static void main(String[] args) throws IOException {
File file =new File("D:\\html\\ty");
del(file);
}
public static void del(File file) {
File[] fi=file.listFiles();
for(File i:fi) {
if(i.isFile()) {
i.delete();
}
else {
del(i);
}
}
file.delete();
}
}
(4)统计一个文件夹的总大小
class Base{
public static void main(String[] args) throws IOException {
File file =new File("D:\\html");
System.out.println(len(file));
}
public static long len(File file) {
long sum=0;
File[] fi=file.listFiles();
for(File i:fi) {
if(i.isFile()) {
//是文件
sum+=i.length();
}
else {
//是文件夹
sum+=len(i);
}
}
return sum;
}
}
(5)统计一个文件夹中每种文件的个数并打印
如下:txt 3个
doc 4个
jpg 6个
class Base{
public static void main(String[] args){
HashMap<String,Integer> map=new HashMap<>();
File file=new File("D:\\ks");
map=findfile(file);
Set<Map.Entry<String, Integer>> s=map.entrySet();
for(Map.Entry<String, Integer> i:s) {
String key=i.getKey();
Integer val=i.getValue();
System.out.println("键="+key+" 值="+val);
}
}
public static HashMap<String,Integer> findfile(File file){
File[] fi=file.listFiles();
HashMap<String,Integer> ma=new HashMap<>();
for(File f:fi) {
if(f.isFile()) {
//是文件
String name=f.getName();
String[] s=name.split("\\.");
if(s.length>=2) {
String ss=s[s.length-1];
if(ma.containsKey(ss)) {
int count=ma.get(ss);
count++;
ma.put(ss, count);
}
else {
ma.put(ss,1);
}
}
}
else {
//是文件夹
//开始递归查找
Set<Map.Entry<String, Integer>> sonmap=findfile(f).entrySet();
//子文件夹中的各个文件开始一个一个查找
for(Map.Entry<String, Integer> i:sonmap) {
String key=i.getKey();
Integer val=i.getValue();
if(ma.containsKey(key)) {
val=val+ma.get(key);
ma.put(key, val);
}
else {
ma.put(key, val);
}
}
}
}
return ma;
}
}