java-IO流

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;
      }

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值