Java基础6-文件IO流

File类的使用

* File类的使用
*
* 1. File类的一个对象,代表一个文件或一个文件目录(俗称:文件夹)
* 2. File类声明在java.io包下
* 3. File类中涉及到关于文件或文件目录的创建、删除、重命名、修改时间、文件大小等方法,
*    并未涉及到写入或读取文件内容的操作。如果需要读取或写入文件内容,必须使用IO流来完成。
* 4. 后续File类的对象常会作为参数传递到流的构造器中,指明读取或写入的"终点".

File 类常用构造器

    /**
     * 1.如何创建File类的实例
     * File(String filePath)
     * File(String parentPath,String childPath)
     * File(File parentFile,String childPath)
     *
     * 2.路径
     * 相对路径:相较于某个路径下,指明的路径。
     * 绝对路径:包含盘符在内的文件或文件目录的路径
     *
     * 3.路径分隔符
     * windows:\\
     * unix:/
     */
    @Test
    public void test1(){
        //构造器1
        File file1 = new File("hello.txt");//相对于当前module
        File file2 =  new File("D:\\workspace_idea1\\JavaSenior\\day08\\he.txt");

        System.out.println(file1);
        System.out.println(file2);

        //构造器2:
        File file3 = new File("D:\\workspace_idea1","JavaSenior");
        System.out.println(file3);

        //构造器3:
        File file4 = new File(file3,"hi.txt");
        System.out.println(file4);
    }

File类常用方法

File类的获取功能

  • public String getAbsolutePath(): 获取绝对路径
  • public String getPath() : 获取路径
  • public String getName() : 获取名称
  • public String getParent(): 获取上层文件目录路径。 若无, 返回null
  • public long length() : 获取文件长度(即:字节数) 。 不能获取目录的长度。
  • public long lastModified() : 获取最后一次的修改时间, 毫秒值
  • 如下的两个方法适用于文件目录:
  • public String[] list() : 获取指定目录下的所有文件或者文件目录的名称数组
  • public File[] listFiles() : 获取指定目录下的所有文件或者文件目录的File数组
    @Test
    public void test2(){
        File file1 = new File("hello.txt");
        File file2 = new File("d:\\io\\hi.txt");

        System.out.println(file1.getAbsolutePath());
        System.out.println(file1.getPath());
        System.out.println(file1.getName());
        System.out.println(file1.getParent());
        System.out.println(file1.length());
        System.out.println(new Date(file1.lastModified()));

        System.out.println();

        System.out.println(file2.getAbsolutePath());
        System.out.println(file2.getPath());
        System.out.println(file2.getName());
        System.out.println(file2.getParent());
        System.out.println(file2.length());
        System.out.println(file2.lastModified());
    }
    @Test
    public void test3(){
        File file = new File("D:\\workspace_idea1\\JavaSenior");
        //获取指定目录下的所有文件或者文件目录的名称数组
        String[] list = file.list();
        for(String s : list){
            System.out.println(s);
        }
        System.out.println();
        //获取指定目录下的所有文件或者文件目录的File数组
        File[] files = file.listFiles();
        for(File f : files){
            System.out.println(f);
        }

    }

File类的重命名功能

  • public boolean renameTo(File dest):把文件重命名为指定的文件路径。
  • 比如:file1.renameTo(file2)为例。
  • 要想保证返回true,需要file1在硬盘中是存在的,且file2不能在硬盘中存在。
    @Test
    public void test4(){
        File file1 = new File("hello.txt");
        File file2 = new File("D:\\io\\hi.txt");

        boolean renameTo = file2.renameTo(file1)
        System.out.println(renameTo);

    }

file类的判断功能

  • public boolean isDirectory():判断是否是文件目录
  • public boolean isFile() :判断是否是文件
  • public boolean exists() :判断是否存在
  • public boolean canRead() :判断是否可读
  • public boolean canWrite() :判断是否可写
  • public boolean isHidden() :判断是否隐藏
    @Test
    public void test5(){
        File file1 = new File("hello.txt");
        file1 = new File("hello1.txt");

        System.out.println(file1.isDirectory());
        System.out.println(file1.isFile());
        System.out.println(file1.exists());
        System.out.println(file1.canRead());
        System.out.println(file1.canWrite());
        System.out.println(file1.isHidden());

        System.out.println();

        File file2 = new File("d:\\io");
        file2 = new File("d:\\io1");
        System.out.println(file2.isDirectory());
        System.out.println(file2.isFile());
        System.out.println(file2.exists());
        System.out.println(file2.canRead());
        System.out.println(file2.canWrite());
        System.out.println(file2.isHidden());

    }

file类的创建、删除功能

  • 创建硬盘中对应的文件或文件目录
  • public boolean createNewFile() :创建文件。若文件存在,则不创建,返回false
  • public boolean mkdir() :创建文件目录。如果此文件目录存在,就不创建了。如果此文件目录的上层目录不存在,也不创建。
  • public boolean mkdirs() :创建文件目录。如果此文件目录存在,就不创建了。如果上层文件目录不存在,一并创建
  • 删除磁盘中的文件或文件目录
  • public boolean delete():删除文件或者文件夹。删除注意事项:Java中的删除不走回收站。
 @Test
    public void test6() throws IOException {
        File file1 = new File("hi.txt");
        if(!file1.exists()){
            //文件的创建createNewFile
            file1.createNewFile();
            System.out.println("创建成功");
        }else{//文件存在
            file1.delete();
            System.out.println("删除成功");
        }
    }

    @Test
    public void test7(){
        //文件目录的创建mkdir
        File file1 = new File("d:\\io\\io1\\io3");
        boolean mkdir = file1.mkdir();
        if(mkdir){
            System.out.println("创建成功1");
        }

        //文件目录的创建mkdirs
        File file2 = new File("d:\\io\\io1\\io4");
        boolean mkdir1 = file2.mkdirs();
        if(mkdir1){
            System.out.println("创建成功2");
        }

        //要想删除成功,io4文件目录下不能有子目录或文件
        File file3 = new File("D:\\io\\io1\\io4");
        file3 = new File("D:\\io\\io1");
        System.out.println(file3.delete());
    }

File类使用案例

1、利用File构造器新建一个文件目录

/**
 * 利用File构造器新建一个文件目录
 */
public class FileDemo {

    @Test
    public void test1() throws IOException {
        File file = new File("D:\\io\\io1\\hello.txt");
        //创建一个与file同目录下的另外一个文件,文件名为:haha.txt
        File destFile = new File(file.getParent(),"haha.txt");
        boolean newFile = destFile.createNewFile();
        if(newFile){
            System.out.println("创建成功!");
        }
    }
}

2、判断指定目录下是否有后缀名为.jpg的文件,如果有,就输出该文件名称

/**
 * 练习2:判断指定目录下是否有后缀名为.jpg的文件,如果有,就输出该文件名称
 */
public class FindJPGFileTest {

	@Test
	public void test1(){
		File srcFile = new File("d:\\code");
		//获取指定目录下的所有文件或者文件目录的名称数组
		String[] fileNames = srcFile.list();
		for(String fileName : fileNames){
			//是否有后缀名为.jpg的文件
			if(fileName.endsWith(".jpg")){
				System.out.println(fileName);
			}
		}
	}
	@Test
	public void test2(){
		File srcFile = new File("d:\\code");
		//获取指定目录下的所有文件或者文件目录的File数组
		File[] listFiles = srcFile.listFiles();
		for(File file : listFiles){
			//是否有后缀名为.jpg的文件
			if(file.getName().endsWith(".jpg")){
				System.out.println(file.getAbsolutePath());
			}
		}
	}

	/**
	 * File类提供了两个文件过滤器方法
	 * public String[] list(FilenameFilter filter)
	 * public File[] listFiles(FileFilter filter)
	 */

	@Test
	public void test3(){
		File srcFile = new File("d:\\code");
		//使用过滤器FilenameFilter筛选出后缀名为.jpg的文件
		File[] subFiles = srcFile.listFiles(new FilenameFilter() {
			@Override
			public boolean accept(File dir, String name) {
				return name.endsWith(".jpg");
			}
		});
		//遍历文件输出
		for(File file : subFiles){
			System.out.println(file.getAbsolutePath());
		}
	}
	
}

3、遍历指定目录所有文件名称,包括子文件目录中的文件。

/**
 * 遍历指定目录所有文件名称,包括子文件目录中的文件。
 * 拓展1:并计算指定目录占用空间的大小
 * 拓展2:删除指定文件目录及其下的所有文件
 */
public class ListFilesTest {

	public static void main(String[] args) {
		// 递归:文件目录
		/** 打印出指定目录所有文件名称,包括子文件目录中的文件 */
		// 1.创建目录对象
		File dir = new File("G:\\IDEA2022\\workmenu\\JavaSenior\\day08\\src\\3_软件");
		// 2.打印目录的子文件
		printSubFile(dir);
	}

	/**
	 * 方式一 递归
	 * 递归打印目录的子文件绝对路径
	 * @param dir
	 */
	public static void printSubFile(File dir) {
		//获取此文件下的所有下级文件/目录
		File[] subfiles = dir.listFiles();

		for (File f : subfiles) {
			if (f.isDirectory()) {
				//是文件目录,递归获取下级
				printSubFile(f);
			} else {
				//文件绝对路径
				System.out.println(f.getAbsolutePath());
			}

		}
	}

	/**
	 * 	方式二:循环实现
	 * 	列出file目录的下级内容,仅列出一级的话
	 * 	使用File类的String[] list()比较简单
	 * @param file
	 */
	public static void listSubFiles(File file) {
		if (file.isDirectory()) {
			//获取指定目录下的所有文件或者文件目录的名称数组
			String[] all = file.list();
			for (String s : all) {
				System.out.println(s);
			}
		} else {
			System.out.println(file + "是文件!");
		}
	}

	// 列出file目录的下级,如果它的下级还是目录,接着列出下级的下级,依次类推
	// 建议使用File类的File[] listFiles()
	public void listAllSubFiles(File file) {
		if (file.isFile()) {
			System.out.println(file);
		} else {
			File[] all = file.listFiles();
			// 如果all[i]是文件,直接打印
			// 如果all[i]是目录,接着再获取它的下一级
			for (File f : all) {
				// 递归调用:自己调用自己就叫递归
				listAllSubFiles(f);
			}
		}
	}

	/**
	 * 拓展1:求指定目录所在空间的大小
	 * 求任意一个目录的总大小
	 * @param file
	 * @return
	 */
	public long getDirectorySize(File file) {
		// file是文件,那么直接返回file.length()
		// file是目录,把它的下一级的所有大小加起来就是它的总大小
		long size = 0;
		// 判断file是否为文件
		if (file.isFile()) {
			//length表示的文件的长度以字节为单位
			size += file.length();
		} else {
			// 获取file的下一级
			File[] all = file.listFiles();
			// 累加all[i]的大小
			for (File f : all) {
				// f的大小
				size += getDirectorySize(f);
			}
		}
		return size;
	}

	/**
	 * 拓展2:删除指定的目录
	 * @param file
	 */
	public void deleteDirectory(File file) {
		// 如果file是文件,直接delete
		// 如果file是目录,先把它的下一级干掉,然后删除自己
		if (file.isDirectory()) {
			File[] all = file.listFiles();
			// 循环删除的是file的下一级
			for (File f : all) {
				//递归删除,f代表file的每一个下级
				deleteDirectory(f);
			}
		}
		// 删除自己
		file.delete();
	}
}

IO流原理及流的分类

IO流原理

  • I/O是Input/Output的缩写, I/O技术是非常实用的技术, 用于处理设备之间的数据传输。如读/写文件,网络通讯等。
  • Java程序中,对于数据的输入/输出操作以“流(stream)” 的方式进行。
  • java.io包下提供了各种“流”类和接口,用以获取不同种类的数据,并通过标准的方法输入或输出数据。
  • 输入输出分别是相对的,一般以流向为分别根据。

image.png

流的分类

image.png

image.png

IO流体系结构

image.png

/**
 * 一、流的分类:
 * 1.操作数据单位:字节流、字符流
 * 2.数据的流向:输入流、输出流
 * 3.流的角色:节点流、处理流
 * <p>
 * 二、流的体系结构
 * 抽象基类         节点流(或文件流)                               缓冲流(处理流的一种)
 * InputStream     FileInputStream   (read(byte[] buffer))        BufferedInputStream (read(byte[] buffer))
 * OutputStream    FileOutputStream  (write(byte[] buffer,0,len)  BufferedOutputStream (write(byte[] buffer,0,len) / flush()
 * Reader          FileReader (read(char[] cbuf))                 BufferedReader (read(char[] cbuf) / readLine())
 * Writer          FileWriter (write(char[] cbuf,0,len)           BufferedWriter (write(char[] cbuf,0,len) / flush()
 * <p>
 * 三、文件类型
 * 1. 对于文本文件(.txt,.java,.c,.cpp),使用字符流处理
 * 2. 对于非文本文件(.jpg,.mp3,.mp4,.avi,.doc,.ppt,...),使用字节流处理
 */

IO基类(节点流)

IO基类-字符输入流-Reader

image.png

/**
     * 文件相对路径在main方法和@Test下的不同
     * @param args
     */
    public static void main(String[] args) {
        //在类方法下,相较于当前工程
        File file = new File("hello.txt");
        System.out.println(file.getAbsolutePath());
        //在类方法下,想相较于当前module模块
        File file1 = new File("day09\\hello.txt");
        System.out.println(file1.getAbsolutePath());
    }

    /**
     * FileReader、Reader使用
     * 将day09下的hello.txt文件内容读入程序中,并输出到控制台
     * 说明点:
     * 1. read()的理解:返回读入的一个字符。如果达到文件末尾,返回-1
     * 2. 异常的处理:为了保证流资源一定可以执行关闭操作。需要使用try-catch-finally处理
     * 3. 读入的文件一定要存在,否则就会报FileNotFoundException。
     */

    //使用FileReader继承InputStreamReader的read方法
    @Test
    public void testFileReader() {
        //方法名后throws IOException抛异常,会出现不执行close,内存泄漏
        FileReader fr = null;
        try {
            //1.实例化File类的对象,指明要操作的文件
            //在测试方法下,相较于当前Module
            File file = new File("hello.txt");
            //2.提供具体的流
            fr = new FileReader(file);

            //3.数据的读入
            //方式一:read():返回读入的一个字符。如果达到文件末尾,返回-1
            //int data = fr.read();
            //while(data != -1){
            //    System.out.print((char)data);
            //    data = fr.read();
            //}
            //方式二:语法上针对于方式一的修改
            int data;
            while ((data = fr.read()) != -1) {
                System.out.print((char) data);
            }
        } catch (IOException e) {
            //抛异常
            e.printStackTrace();
        } finally {
            //4.流的关闭操作
            //try {
            //    if(fr != null)
            //        fr.close();
            //} catch (IOException e) {
            //    e.printStackTrace();
            //}

            //4. 流的关闭操作
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //使用FileReader继承InputStreamReader继承Reader的重载read方法
    @Test
    public void testFileReader1() {
        FileReader fr = null;
        try {
            //1.File类的实例化
            File file = new File("hello.txt");

            //2.FileReader流的实例化
            fr = new FileReader(file);

            //3.读入的操作
            //read(char[] cbuf):返回每次读入cbuf数组中的字符的个数。如果达到文件末尾,返回-1
            char[] cbuf = new char[5];
            int len;
            //一块一块地读
            while ((len = fr.read(cbuf)) != -1) {
                //方式一:
                //错误的写法,难点遍历数组覆盖,数组全输出
                //for(int i = 0;i < cbuf.length;i++){
                //    System.out.print(cbuf[i]);
                //}
                //正确的写法
                //for(int i = 0;i < len;i++){
                //    System.out.print(cbuf[i]);
                //}

                //方式二:
                //错误的写法,难点遍历数组覆盖,数组全输出
                //String str = new String(cbuf);
                //System.out.print(str);
                //正确的写法
                String str = new String(cbuf, 0, len);
                System.out.print(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fr != null) {
                //4.资源的关闭
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

IO基类-字符输出流-Writer

image.png

    /**
     * FileWriter、Writer使用。从内存中写出数据到硬盘的文件里。
     * 说明:
     * 1.输出操作,对应的File可以不存在的。并不会报异常
     * 2.File对应的硬盘中的文件如果不存在,在输出的过程中,会自动创建此文件。
     *
     * File对应的硬盘中的文件如果存在:
     * 如果流使用的构造器是:FileWriter(file,false) / FileWriter(file):默认对原有文件的覆盖
     * 如果流使用的构造器是:FileWriter(file,true):不会对原有文件覆盖,而是在原有文件基础上追加内容
     */

    @Test
    public void testFileWriter() {
        FileWriter fw = null;
        try {
            //1.提供File类的对象,指明写出到的文件
            File file = new File("hello1.txt");

            //2.提供FileWriter的对象,用于数据的写出
            //append参数:true文件追加、false文件覆盖
            fw = new FileWriter(file, false);

            //3.写出的操作
            fw.write("I have a dream!\n");
            fw.write("you need to have a dream!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.流资源的关闭
            if (fw != null) {
                try {
                    fw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

字符流用于文本文件复制

    /**
     * FileReader、FileWriter读入写出
     * 文件复制:将txt文件内容读入程序中,并输出到控制台。从内存中写出数据到硬盘的文件里。
     */
    @Test
    public void testFileReaderFileWriter() {
        FileReader fr = null;
        FileWriter fw = null;
        try {
            //1.创建File类的对象,指明读入和写出的文件
            File srcFile = new File("hello.txt");
            File destFile = new File("hello2.txt");

            //不能使用字符流来处理图片等字节数据
            //File srcFile = new File("爱情与友情.jpg");
            //File destFile = new File("爱情与友情1.jpg");

            //2.创建输入流和输出流的对象
            fr = new FileReader(srcFile);
            fw = new FileWriter(destFile);

            //3.数据的读入和写出操作
            char[] cbuf = new char[5];
            //记录每次读入到cbuf数组中的字符的个数
            int len;
            while ((len = fr.read(cbuf)) != -1) {
                //每次写出len个字符
                fw.write(cbuf, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.关闭流资源,推荐倒着关闭。
            //方式一:
            //try {
            //    if(fw != null)
            //        fw.close();
            //} catch (IOException e) {
            //    e.printStackTrace();
            //}finally{
            //    try {
            //        if(fr != null)
            //            fr.close();
            //    } catch (IOException e) {
            //        e.printStackTrace();
            //    }
            //}

            //方式二:并列trycatch
            try {
                if (fw != null)
                    fw.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                if (fr != null)
                    fr.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

IO基类-字节输入流-InputStream

image.png

   /**
     * 将非文本文件内容读入程序中,并输出到控制台
     * 使用FileInputStream继承InputStream的重载read方法
     * 注意:使用字节流FileInputStream处理文本文件,可能出现乱码
     */
    @Test
    public void testFileInputStream() {
        FileInputStream fis = null;
        try {
            //1.造文件
            File file = new File("hello.txt");
            //2.造流
            fis = new FileInputStream(file);
            //3.读数据
            byte[] buffer = new byte[5];
            int len;//记录每次读取的字节的个数
            while((len = fis.read(buffer)) != -1){
                //放入String
                String str = new String(buffer,0,len);
                System.out.print(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fis != null){
                //4.关闭资源
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

IO基类-字节输出流-OutputStream

/**
     * 实现对图片的复制操作
     * 使用FileOutputStream继承OutputStream的重载write方法
     * 将非文本文件,一块一块地输出到目标文件。
     */
    @Test
    public void testFileInputOutputStream()  {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            //定义输入file、输出file
            File srcFile = new File("爱情与友情.jpg");
            File destFile = new File("爱情与友情2.jpg");
            //定义输入、输出
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);
            //复制的过程
            byte[] buffer = new byte[5];
            int len;
            while((len = fis.read(buffer)) != -1){
                fos.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

字节流用于(非文本/文本)文件复制

    /**
     * 指定路径下文件的复制
     * @param srcPath
     * @param destPath
     */
    public void copyFile(String srcPath,String destPath){
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            File srcFile = new File(srcPath);
            File destFile = new File(destPath);
            fis = new FileInputStream(srcFile);
            fos = new FileOutputStream(destFile);
            //复制的过程 字节大小不影响时间,只影响消耗内存
            byte[] buffer = new byte[1024];
            int len;
            while((len = fis.read(buffer)) != -1){
                fos.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fos != null){
                //
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 注意:字节流可用于文本文件复制,不在内存看。字符流不可用于非文本文件复制。
     */
    @Test
    public void testCopyFile(){
        //开始时间
        long start = System.currentTimeMillis();

        //绝对文件路径
        //String srcPath = "C:\\Users\\Administrator\\Desktop\\01-视频.avi";
        //String destPath = "C:\\Users\\Administrator\\Desktop\\02-视频.avi";
        //在类方法下,相较于当前工程。
        String srcPath = "hello.txt";
        String destPath = "hello3.txt";
        //文件复制
        copyFile(srcPath,destPath);

        //结束时间
        long end = System.currentTimeMillis();
        System.out.println("复制操作花费的时间为:" + (end - start));
    }

IO处理流

IO处理流-缓冲流

IO缓冲流定义

image.png

image.png

image.png

IO缓冲流的使用

/**
 * 处理流之一:缓冲流的使用
 * <p>
 * 1.缓冲流:
 * BufferedInputStream
 * BufferedOutputStream
 * BufferedReader
 * BufferedWriter
 * <p>
 * 2.作用:提供流的读取、写入的速度。
 * 提高读写速度的原因:内部提供了一个缓冲区,用空间换时间。
 * <p>
 * 3.处理流,就是“套接”在已有的流的基础上。
 */
public class BufferedTest {

    /**
     * 使用缓冲流实现非文本文件的复制
     */
    @Test
    public void BufferedStreamTest() {
        //实例化缓冲流对象
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            //1.造文件
            File srcFile = new File("爱情与友情.jpg");
            File destFile = new File("爱情与友情3.jpg");
            //2.造流
            //2.1 造节点流
            FileInputStream fis = new FileInputStream((srcFile));
            FileOutputStream fos = new FileOutputStream(destFile);
            //2.2 造缓冲流
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);

            //3.复制的细节:读取、写入
            byte[] buffer = new byte[10];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
                //bos.flush();//刷新缓冲区
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.资源关闭,要求:先关闭外层的流,再关闭内层的流
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,可以省略.
            //fos.close();
            //fis.close();
        }
    }

    /**
     * 使用缓冲流实现(非文本/文本)文件复制的方法
     *
     * @param srcPath
     * @param destPath
     */
    public void copyFileWithBuffered(String srcPath, String destPath) {
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
            //1.造文件
            File srcFile = new File(srcPath);
            File destFile = new File(destPath);
            //2.造流
            //2.1 造节点流
            FileInputStream fis = new FileInputStream((srcFile));
            FileOutputStream fos = new FileOutputStream(destFile);
            //2.2 造缓冲流
            bis = new BufferedInputStream(fis);
            bos = new BufferedOutputStream(fos);

            //3.复制的细节:读取、写入
            byte[] buffer = new byte[1024];
            int len;
            while ((len = bis.read(buffer)) != -1) {
                bos.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.资源关闭
            //要求:先关闭外层的流,再关闭内层的流
            if (bos != null) {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            //说明:关闭外层流的同时,内层流也会自动的进行关闭。关于内层流的关闭,我们可以省略.
            //fos.close();
            //fis.close();
        }
    }
    
    /**
     * 使用缓冲流实现视频文件复制的方法
     */
    @Test
    public void testCopyFileWithBuffered() {
        long start = System.currentTimeMillis();

        String srcPath = "C:\\Users\\Administrator\\Desktop\\01-视频.avi";
        String destPath = "C:\\Users\\Administrator\\Desktop\\03-视频.avi";

        copyFileWithBuffered(srcPath, destPath);

        long end = System.currentTimeMillis();
        System.out.println("复制操作花费的时间为:" + (end - start));//618 - 176
    }


    /**
     * 使用BufferedReader和BufferedWriter实现文本文件的复制
     */
    @Test
    public void testBufferedReaderBufferedWriter() {
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
            //创建文件和相应的流,相对路径在@Test下,相较于当前Module
            br = new BufferedReader(new FileReader(new File("dbcp.txt")));
            bw = new BufferedWriter(new FileWriter(new File("dbcp1.txt")));

            //读写操作
            //方式一:使用char[]数组
            //char[] cbuf = new char[1024];
            //int len;
            //while((len = br.read(cbuf)) != -1){
            //    bw.write(cbuf,0,len);
            //    bw.flush();
            //}

            //方式二:使用String字符串
            String data;
            while ((data = br.readLine()) != null) {
                //方法一:
                //bw.write(data + "\n"); //data中不包含换行符
                //方法二:
                bw.write(data); //data中不包含换行符
                bw.newLine(); //提供换行的操作
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

IO缓冲流练习

IO流练习1:图片的加密解密

/**
 * IO流练习1:图片的加密解密
 * @author cmy
 * @create 2019 下午 4:08
 */
public class PicTest {

    /**
     * 图片的加密
     */
    @Test
    public void test1() {

        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("爱情与友情.jpg");
            fos = new FileOutputStream("爱情与友情secret.jpg");

            byte[] buffer = new byte[20];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                //对字节数组进行修改
                //错误的
                //for(byte b : buffer){
                //    b = (byte) (b ^ 5);
                //}
                //正确的
                for (int i = 0; i < len; i++) {
                    buffer[i] = (byte) (buffer[i] ^ 5);
                }
                //输出加密后的字节数组
                fos.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    /**
     * 图片的解密
     */
    @Test
    public void test2() {

        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
            fis = new FileInputStream("爱情与友情secret.jpg");
            fos = new FileOutputStream("爱情与友情4.jpg");

            byte[] buffer = new byte[20];
            int len;
            while ((len = fis.read(buffer)) != -1) {
                //字节数组进行修改
                //错误的
                //for(byte b : buffer){
                //    b = (byte) (b ^ 5);
                //}
                //正确的
                for (int i = 0; i < len; i++) {
                    //异或:将两个数转为二进制逐位比较,同为0,异为1
                    //解密:m^n^n=m
                    buffer[i] = (byte) (buffer[i] ^ 5);
                }
                fos.write(buffer, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (fos != null) {
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

** IO流练习3:词频统计**

/**
 * IO流练习3:词频统计,获取文本上字符出现的次数,把数据写入文件
 *
 * 实现思路:
 * 1.遍历文本每一个字符
 * 2.字符出现的次数存在Map中
 * Map<Character,Integer> map = new HashMap<Character,Integer>();
 * map.put('a',18);
 * map.put('你',2);
 * 3.把map中的数据写入文件
 */
public class WordCount {

    /**
     * 说明:如果使用单元测试@Test,文件相对路径为当前module
     *      如果使用main()测试,文件相对路径为当前工程
     */
    @Test
    public void testWordCount() {
        FileReader fr = null;
        BufferedWriter bw = null;
        try {
            //1.创建Map集合
            Map<Character, Integer> map = new HashMap<Character, Integer>();

            //2.遍历每一个字符,每一个字符出现的次数放到map中
            fr = new FileReader("dbcp.txt");
            int c = 0;
            while ((c = fr.read()) != -1) {
                //int 还原 char
                char ch = (char) c;
                // 判断char是否在map中第一次出现
                if (map.get(ch) == null) {
                    map.put(ch, 1);
                } else {
                    map.put(ch, map.get(ch) + 1);
                }
            }

            //3.把map中数据存在文件count.txt
            //3.1 创建Writer
            bw = new BufferedWriter(new FileWriter("wordcount.txt"));

            //3.2 遍历map,再写入数据
            List<Map.Entry<Character, Integer>> entryList = new ArrayList<>(map.entrySet());

            //map按value排序,降序
            Collections.sort(entryList,new Comparator<Map.Entry<Character, Integer>>() {
                @Override
                public int compare(Map.Entry<Character, Integer> o1, Map.Entry<Character, Integer> o2) {
                    //compare():o1-o2返回值小于0表示升序,大于0表示降序
                    return -(o1.getValue()-o2.getValue());
                }
            });

            //遍历entrySet集合,得到Map.Entry键值对
            for (Map.Entry<Character, Integer> entry : entryList) {
                //判断key
                switch (entry.getKey()) {
                    case ' ':
                        bw.write("空格=" + entry.getValue());
                        break;
                    case '\t'://\t表示tab 键字符
                        bw.write("tab键=" + entry.getValue());
                        break;
                    case '\r'://
                        bw.write("回车=" + entry.getValue());
                        break;
                    case '\n'://
                        bw.write("换行=" + entry.getValue());
                        break;
                    default:
                        bw.write(entry.getKey() + "=" + entry.getValue());
                        break;
                }
                //创建换行
                bw.newLine();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //4.关流
            if (fr != null) {
                try {
                    fr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


IO处理流-转换流

IO转换流定义

image.png

image.png

IO转换流的使用

image.png

/**
 * 处理流之二:转换流的使用
 * 1.转换流:属于字符流
 *   InputStreamReader:将一个字节的输入流转换为字符的输入流
 *   OutputStreamWriter:将一个字符的输出流转换为字节的输出流
 *
 * 2.作用:提供字节流与字符流之间的转换
 *
 * 3. 解码:字节、字节数组  --->字符数组、字符串
 *    编码:字符数组、字符串 ---> 字节、字节数组
 *
 * 4.字符集
 * ASCII:美国标准信息交换码。用一个字节的7位可以表示。
 * ISO8859-1:拉丁码表。欧洲码表用一个字节的8位表示。
 * GB2312:中国的中文编码表。最多两个字节编码所有字符。
 * GBK:中国的中文编码表升级,融合了更多的中文文字符号。以开头0/1区分存储1/2个字节,最多两个字节编码。
 * Unicode:国际标准码,融合了目前人类使用的所有字符。为每个字符分配唯一的字符码。所有的文字都用两个字节来表示。
 * UTF-8:变长的编码方式,可用1-4个字节来表示一个字符。
 */
public class InputStreamReaderTest {

    /**
     * 文件解码:加入内存看
     * InputStreamReader的使用,实现字节的输入流到字符的输入流的转换
     * 此时处理异常的话,仍然应该使用try-catch-finally
     */
    @Test
    public void test1(){
        InputStreamReader isr = null;
        try {
            //实例化字节输入流
            FileInputStream fis = new FileInputStream("dbcp.txt");
            //使用系统默认的字符集
            //InputStreamReader isr = new InputStreamReader(fis);
            //参数2指明了字符集,具体使用哪个字符集,取决于文件dbcp.txt保存时使用的字符集
            isr = new InputStreamReader(fis,"UTF-8");

            char[] cbuf = new char[20];
            int len;
            //循环遍历读取到cbuf中
            while((len = isr.read(cbuf)) != -1){
                //将读取结果放入String
                String str = new String(cbuf,0,len);
                System.out.print(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (isr!=null){
                try {
                    isr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    /**
     * 文件编码,将utf-8转换成gbk,加入磁盘存
     * 综合使用InputStreamReader和OutputStreamWriter
     * InputStreamReader实现将字节的输入流按指定字符集转换为字符的输入流
     * OutputStreamWriter实现将字符的输出流按指定字符集转换为字节的输出流
     */
    @Test
    public void test2(){
        InputStreamReader isr = null;
        OutputStreamWriter osw = null;
        try {
            //1.造文件、造流
            File file1 = new File("dbcp.txt");
            File file2 = new File("dbcp_gbk.txt");

            //定义字节输入流、输出流
            FileInputStream fis = new FileInputStream(file1);
            FileOutputStream fos = new FileOutputStream(file2);
            //InputStreamReader转换流
            isr = new InputStreamReader(fis,"utf-8");
            //OutputStreamWriter转换流
            osw = new OutputStreamWriter(fos,"gbk");

            //2.读写过程
            char[] cbuf = new char[20];
            int len;
            //输入转换流循环遍历读
            while((len = isr.read(cbuf)) != -1){
                //输出转换流循环白遍历写
                osw.write(cbuf,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //3.关闭资源
            if (isr!=null){
                try {
                    isr.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (osw!=null){
                try {
                    osw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

补充:字符编码

字符编码定义

image.png

Unicode编码问题

解决:Unicode字符集只是定义了字符的集合和唯一编号,但具体存储和读取按UTF-8\UTF-16\UTF-32等编码方案。

image.png

UTF-8编码实现过程

如存储一个汉字"尚",Unicode十六进制—>UTF-8二进制。
image.png

IO处理流-标准输入输出流

标准输入输出流定义

image.png

标准输入输出流使用

   /**
     * 1.标准的输入、输出流
     * 1.1
     * System.in:标准的输入流(字节),默认从键盘输入
     * System.out:标准的输出流(字节),默认从控制台输出
     * 1.2
     * System类的setIn(InputStream is) / setOut(PrintStream ps)方式重新指定输入和输出的流。
     *
     * 1.3练习:
     * 从键盘输入字符串,要求将读取到的整行字符串转成大写输出。然后继续进行输入操作,
     * 直至当输入“e”或者“exit”时,退出程序。
     * 方法一:使用Scanner实现,调用next()返回一个字符串
     * 方法二:使用System.in实现。System.in字节流  --->  转换流 ---> BufferedReader缓冲流的readLine()
     */
    public static void main(String[] args) {
        //定义缓冲字符输入流
        BufferedReader br = null;
        try {
            //定义转换流,将字节输入流转成字符输入流,System.in键盘输入
            InputStreamReader isr = new InputStreamReader(System.in);
            //在缓冲流加入转换流
            br = new BufferedReader(isr);
            //循环遍历读取
            while (true) {
                //idea单元测试不支持System.in键盘输入
                System.out.println("请输入字符串:");
                //读取一行
                String data = br.readLine();
                //判断结束符
                if ("e".equalsIgnoreCase(data) || "exit".equalsIgnoreCase(data)) {
                    System.out.println("程序结束");
                    break;
                }
                //转成大写输出
                String upperCase = data.toUpperCase();
                System.out.println(upperCase);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

自定义编写Scanner的读取输入类

/**
 * 自定义编写读取类似Scanner的标准输入工具类
 */
public class MyInput {
    // Read a string from the keyboard
    public static String readString() {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        // Declare and initialize the string
        String string = "";
        // Get the string from the keyboard
        try {
            string = br.readLine();

        } catch (IOException ex) {
            System.out.println(ex);
        }
        // Return the string obtained from the keyboard
        return string;
    }

    // Read an int value from the keyboard
    public static int readInt() {
        return Integer.parseInt(readString());
    }

    // Read a double value from the keyboard
    public static double readDouble() {
        return Double.parseDouble(readString());
    }

    // Read a byte value from the keyboard
    public static double readByte() {
        return Byte.parseByte(readString());
    }

    // Read a short value from the keyboard
    public static double readShort() {
        return Short.parseShort(readString());
    }

    // Read a long value from the keyboard
    public static double readLong() {
        return Long.parseLong(readString());
    }

    // Read a float value from the keyboard
    public static double readFloat() {
        return Float.parseFloat(readString());
    }
}

IO处理流-打印流

打印流定义

image.png

打印流使用

/**
     * 2. 打印流:PrintStream和PrintWriter
     * 2.1 提供了一系列重载的print() 和 println()
     * 2.2 打印流练习:将输出ASCII字符打印到文件,不打印到控制台
     */
    @Test
    public void test2() {
        PrintStream ps = null;
        try {
            //文件字节输出流
            FileOutputStream fos = new FileOutputStream(new File("PrintStream.txt"));
            // 创建打印输出流,设置为自动刷新模式(写入换行符或字节 '\n' 时都会刷新输出缓冲区)
            ps = new PrintStream(fos, true);
            if (ps != null) {
                // 自定义把标准输出流(控制台输出)改成文件
                System.setOut(ps);
            }
            for (int i = 0; i <= 255; i++) {
                // 输出ASCII字符,System.out输出到文件
                System.out.print((char) i);
                if (i % 50 == 0) {
                    // 每50个数据一行 // 换行
                    System.out.println();
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (ps != null) {
                ps.close();
            }
        }
    }

IO处理流-数据流

数据流定义

image.png

数据流使用

   /**
     * 3. 数据流
     * 3.1 DataInputStream 和 DataOutputStream
     * 3.2 作用:用于读取或写出基本数据类型的变量或字符串
     * 练习:DataOutputStream将内存中的字符串、基本数据类型的变量写出到文件中。
     * 注意:处理异常的话,仍然应该使用try-catch-finally.
     */
    @Test
    public void test3() throws IOException {
        //1.实例化File类、提供具体的流
        DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.txt"));
        //2.DataOutputStream写入内容
        dos.writeUTF("刘建辰");
        //刷新操作,将内存中的数据写入文件
        dos.flush();
        dos.writeInt(23);
        dos.flush();
        dos.writeBoolean(true);
        dos.flush();
        //3.流的关闭操作
        dos.close();
    }

    /**
     * DataInputStream将文件中存储的基本数据类型变量和字符串读取到内存中,保存在变量中。
     * 注意:读取不同类型的数据的顺序要与当初写入文件时,保存的数据的顺序一致!
     */
    @Test
    public void test4() throws IOException {
        //1.实例化File类、提供具体的流
        DataInputStream dis = new DataInputStream(new FileInputStream("data.txt"));
        //2.DataInputStream读取内容
        String name = dis.readUTF();
        int age = dis.readInt();
        boolean isMale = dis.readBoolean();
        //输出
        System.out.println("name = " + name);
        System.out.println("age = " + age);
        System.out.println("isMale = " + isMale);

        //3.流的关闭操作
        dis.close();
    }

IO处理流-对象流

对象流定义

image.png

对象序列化

对象序列化机制定义

image.png

serialVersionUID定义

image.png

Serializable接口理解

image.png

对象流使用

image.png>定义Person、Account对象

/**
 * Person需要满足如下的要求,方可序列化
 * 1.需要实现接口:Serializable标识接口
 * 2.当前类提供一个全局常量:serialVersionUID
 * 3.除了当前Person类需要实现Serializable接口之外,
 * 还必须保证其内部所有属性是可序列化的。(默认情况下,基本数据类型可序列化)
 *
 * 补充:ObjectOutputStream和ObjectInputStream不能序列化static和transient修饰的成员变量
 */
public class Person implements Serializable {
    //序列化唯一标识,控制类序列化版本还原
    public static final long serialVersionUID = 475463534532L;

    private String name;
    //static不归对象所有,归类所有
    private static int age;
    //transient不让序列化对象属性
    private transient int id;
    private Account acct;

    public Person(String name, int age, int id) {
        this.name = name;
        this.age = age;
        this.id = id;
    }

    public Person(String name, int age, int id, Account acct) {
        this.name = name;
        this.age = age;
        this.id = id;
        this.acct = acct;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", id=" + id +
                ", acct=" + acct +
                '}';
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Person(String name, int age) {

        this.name = name;
        this.age = age;
    }

    public Person() {

    }
}

class Account implements Serializable {
    public static final long serialVersionUID = 4754534532L;
    private double balance;

    @Override
    public String toString() {
        return "Account{" +
                "balance=" + balance +
                '}';
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    public Account(double balance) {

        this.balance = balance;
    }
}

对象序列化存储、对象反序列化输出

/**
 * 对象流的使用
 * 1.ObjectInputStream 和 ObjectOutputStream
 * 2.作用:用于存储和读取基本数据类型数据或对象的处理流。它的强大之处就是可以把Java中的对象写入到数据源中,也能把对象从数据源中还原回来。
 *
 * 3.要想一个java对象是可序列化的,需要满足相应的要求。见Person.java
 *
 * 4.序列化机制:
 * 对象序列化机制允许把内存中的Java对象转换成平台无关的二进制流,从而允许把这种
 * 二进制流持久地保存在磁盘上,或通过网络将这种二进制流传输到另一个网络节点。
 * 当其它程序获取了这种二进制流,就可以恢复成原来的Java对象。
 */
public class ObjectInputOutputStreamTest {

    /**
     * 序列化过程:将内存中的java对象保存到磁盘中或通过网络传输出去
     * 使用ObjectOutputStream实现
     */
    @Test
    public void testObjectOutputStream(){
        ObjectOutputStream oos = null;

        try {
            //1.实例化file、FileOutputStream、ObjectOutputStream
            oos = new ObjectOutputStream(new FileOutputStream("object.dat"));
            //2.写入String对象
            oos.writeObject(new String("我爱北京天安门"));
            //刷新操作
            oos.flush();
            //写入Person对象
            oos.writeObject(new Person("王铭",23));
            oos.flush();
            //写入Person对象、嵌套Account对象
            oos.writeObject(new Person("张学良",23,1001,new Account(5000)));
            oos.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(oos != null){
                //3.关闭资源
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 反序列化:将磁盘文件中的对象还原为内存中的一个java对象
     * 使用ObjectInputStream来实现
     */
    @Test
    public void testObjectInputStream(){
        ObjectInputStream ois = null;
        try {
            //实例化file、FileInputStream、ObjectInputStream
            ois = new ObjectInputStream(new FileInputStream("object.dat"));
            //获取对象
            Object obj = ois.readObject();
            //强转
            String str = (String) obj;
            Person p = (Person) ois.readObject();
            Person p1 = (Person) ois.readObject();
            //输出
            System.out.println(str);
            System.out.println(p);
            System.out.println(p1);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if(ois != null){
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

IO处理流-随机存取文件流

随机存取文件流定义

image.png

随机存取文件流使用

/**
 * RandomAccessFile的使用
 * 1.RandomAccessFile直接继承于java.lang.Object类,实现了DataInput和DataOutput接口
 * 2.RandomAccessFile既可以作为一个输入流,又可以作为一个输出流
 *
 * 3.如果RandomAccessFile作为输出流时,写出到的文件如果不存在,则在执行过程中自动创建。
 *   如果写出到的文件存在,则会对原有文件内容进行覆盖。(默认情况下,从头覆盖)
 *
 * 4.可以通过相关的操作,移动操作开始位置指针,实现RandomAccessFile“插入”数据的效果
 *   通过seek()方法将操作指针移动到指定位置,实现RandomAccessFile可应用于文件断点下载
 */
public class RandomAccessFileTest {

    /**
     * RandomAccessFile输入输出流复制图片
     */
    @Test
    public void test1() {
        //1.实例化RandomAccessFile、File
        RandomAccessFile raf1 = null;
        RandomAccessFile raf2 = null;
        try {
            //输入流,mode方式为r只读
            raf1 = new RandomAccessFile(new File("爱情与友情.jpg"),"r");
            //输出流,mode方式为rw可读可写
            raf2 = new RandomAccessFile(new File("爱情与友情1.jpg"),"rw");
            //2.读取写入字节数组
            byte[] buffer = new byte[1024];
            int len;
            //循环遍历读取
            while((len = raf1.read(buffer)) != -1){
                //写入数据
                raf2.write(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            //3.关闭流
            if(raf1 != null){
                try {
                    raf1.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(raf2 != null){
                try {
                    raf2.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * RandomAccessFile输入输出流向文本写入数据
     */
    @Test
    public void test2() throws IOException {
        //定义RandomAccessFile输入输出流rw
        RandomAccessFile raf1 = new RandomAccessFile("hello.txt","rw");
        //将指针调到角标为3的位置,避免覆盖
        raf1.seek(3);
        //默认会从开头覆盖内容
        raf1.write("xyz".getBytes());
        //关闭流
        raf1.close();

    }

    /**
     * RandomAccessFile输入输出流实现数据的插入效果
     */
    @Test
    public void test3() throws IOException {
        //定义RandomAccessFile输入输出流
        RandomAccessFile raf1 = new RandomAccessFile("hello.txt","rw");
        //将指针调到角标为3的位置
        raf1.seek(3);

        //保存指针3后面的所有数据到StringBuilder中
        //StringBuilder builder = new StringBuilder((int) new File("hello.txt").length());
        //byte[] buffer = new byte[20];
        //int len;
        //while((len = raf1.read(buffer)) != -1){
        //    builder.append(new String(buffer,0,len)) ;
        //}

        //思考:将StringBuilder替换为ByteArrayOutputStream字节数组输出流
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[20];
        int len;
        while((len = raf1.read(buffer)) != -1){
            baos.write(buffer,0,len);
        }

        //调回指针,写入“xyz”
        raf1.seek(3);
        //写入xyz到RandomAccessFile输入输出流
        raf1.write("xyz".getBytes());
        //当前指针为4,将StringBuilder中的数据写入到文件中,直接覆盖写入
        //raf1.write(builder.toString().getBytes());
        raf1.write(baos.toByteArray());
        //关闭流
        baos.close();
        raf1.close();
    }
}

应用-文件断点下载

image.png

IO工具类FileUtils

导入apache.commons.io的jar包,它封装了IO的相关用法,平时实践开发可以直接使用。

/**
 * IO工具类FileUtils的使用
 */
public class FileUtilsTest {

    public static void main(String[] args) {
        File srcFile = new File("day10\\爱情与友情.jpg");
        File destFile = new File("day10\\爱情与友情2.jpg");

        try {
            //copyFile底层使用了文件字节输入输出流FileInputStream和FileOutputStream、
            //还使用了NIO文件输入输出管道FileChannel。
            FileUtils.copyFile(srcFile,destFile);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

同步\异步、阻塞\非阻塞定义

同步与异步描述的是被调用者的。 如 A 调用 B:

  • 如果是同步,B 在接到 A 的调用后, 会立即执行要做的事。 A 的本次调用可以得到结果。
  • 如果是异步, B 在接到 A 的调用后, 不保证会立即执行要做的事, 但保证会去做, B做好了之后会通知 A。A 的本次调用得不到结果, 但是 B 执行完之后会通知 A。

阻塞与非阻塞描述的是调用者的。 如 A 调用 B:

  • 如果是阻塞, A 在发出调用后, 要一直等待, 等着 B 返回结果。
  • 如果是非阻塞, A 在发出调用后, 不需要等待, 可以去做自己的事情。

同步/异步、阻塞/非阻塞区别

同步与异步描述的是被调用者的。阻塞与非阻塞描述的是调用者的。
同步不一定阻塞,异步也不一定非阻塞,没有必然关系。
举个简单的例子,老张烧水。
1. 老张把水壶放到火上,一直在水壶旁等着水开。同步阻塞
2. 老张把水壶放到火上,去客厅看电视, 时不时去厨房看看水开没有。同步非阻塞
3. 老张把响水壶放到火上,一直在水壶旁等着水开。 异步阻塞
4. 老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。异步非阻塞

Linux 5 种 IO 模型

阻塞式 IO 模型

最传统的一种 IO 模型, 即在读写数据过程中会发生阻塞现象。
当用户线程发出 IO 请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处
于阻塞状态,用户线程交出 CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用
户线程才解除 block 状态。

非阻塞 IO 模型

当用户线程发起一个 read 操作后, 并不需要等待, 而是马上就得到了一个结果。 如果结果是一个 error 时, 它就知道数据还没有准备好, 于是它可以再次发送 read 操作。 一旦内核中的数据准备好了, 并且又再次收到了用户线程的请求, 那么它马上就将数据拷贝到了用户线程, 然后返回。
所以事实上, 在非阻塞 IO 模型中, 用户线程需要不断地询问内核数据是否就绪, 也就说非阻塞 IO 不会交出CPU, 而会一直占用 CPU。

IO 复用模型

多路复用 IO 模型是目前使用得比较多的模型。 Java NIO 实际上就是多路复用 IO。
在多路复用 IO 模型中, 会有一个线程不断去轮询多个 socket 的状态, 只有当socket 真正有读写事件时, 才真正调用实际的 IO 读写操作。 因为在多路复用 IO 模型中,只需要使用一个线程就可以管理多个 socket, 系统不需要建立新的进程或者线程, 也不必维护这些线程和进程, 并且只有在真正有 socket 读写事件进行时, 才会使用 IO 资源, 所以它大大减少了资源占用。

信号驱动 IO 模型

在信号驱动 IO 模型中, 当用户线程发起一个 IO 请求操作, 会给对应的 socket 注册一个信号函数, 然后用户线程会继续执行, 当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后, 便在信号函数中调用 IO 读写操作来进行实际的 IO 请求操作。

异步 IO 模型

异步 IO 模型是比较理想的 IO 模型, 在异步 IO 模型中, 当用户线程发起 read 操作之后, 立刻 就可以开始去做 其它的事。 而另 一方面, 从内核 的角度, 当它受 到一个asynchronous read 之后, 它会立刻返回, 说明 read请求已经成功发起了, 因此不会对用户线程产生任何 block。 然后, 内核会等待数据准备完成, 然后将数据拷贝到用户线程,当这一切都完成之后, 内核会给用户线程发送一个信号, 告诉它 read 操作完成了。
注意, 异步 IO 是需要操作系统的底层支持, 在 Java 7 中, 提供了 Asynchronous IO。

Java BIO

Java BIO 即 Block I/O,同步并阻塞的 IO。BIO 就是传统的 java.io 包下面的代码实现。

Java NIO

Java NIO定义

一个 面向块 的 I/O 系统以块的形式处理数据。 每一个操作都在一步中产生或者消费一个数据块。 按块处理数据比按(流式的)字节处理数据要快得多。 但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。
image.png

Java NIO核心API

NIO核心API由来

image.png

Paths 类:获取Path对象

image.png

Path接口:File类操作升级版

image.png

/**
 * 1. jdk 7.0 时,引入了 Path、Paths、Files三个类。
 * 2.此三个类声明在:java.nio.file包下。
 * 3.Path可以看做是java.io.File类的升级版本。也可以表示文件或文件目录,与平台无关
 * <p>
 * 4.如何实例化Path:使用Paths.
 * static Path get(String first, String … more) : 用于将多个字符串串连成路径
 * static Path get(URI uri): 返回指定uri对应的Path路径
 */
public class PathTest {

    //如何使用Paths实例化Path
    @Test
    public void test1() {
        //new File(String filepath)
        Path path1 = Paths.get("d:\\nio\\hello.txt");
        //new File(String parent,String filename);
        Path path2 = Paths.get("d:\\", "nio\\hello.txt");

        System.out.println(path1);
        System.out.println(path2);

        Path path3 = Paths.get("d:\\", "nio");
        System.out.println(path3);
    }

    //Path中的常用方法
    @Test
    public void test2() {
        Path path1 = Paths.get("d:\\", "nio\\nio1\\nio2\\hello.txt");
        Path path2 = Paths.get("hello.txt");

        //String toString() : 返回调用 Path 对象的字符串表示形式
        System.out.println(path1);

		//boolean startsWith(String path) : 判断是否以 path 路径开始
        System.out.println(path1.startsWith("d:\\nio"));
		//boolean endsWith(String path) : 判断是否以 path 路径结束
        System.out.println(path1.endsWith("hello.txt"));
		//boolean isAbsolute() : 判断是否是绝对路径
        System.out.println(path1.isAbsolute() + "~");
        System.out.println(path2.isAbsolute() + "~");
		//Path getParent() :返回Path对象包含整个路径,不包含 Path 对象指定的文件路径
        System.out.println(path1.getParent());
        System.out.println(path2.getParent());
		//Path getRoot() :返回调用 Path 对象的根路径
        System.out.println(path1.getRoot());
        System.out.println(path2.getRoot());
		//Path getFileName() : 返回与调用 Path 对象关联的文件名
        System.out.println(path1.getFileName() + "~");
        System.out.println(path2.getFileName() + "~");
		//int getNameCount() : 返回Path 根目录后面元素的数量
		//Path getName(int idx) : 返回指定索引位置 idx 的路径名称
        for (int i = 0; i < path1.getNameCount(); i++) {
            System.out.println(path1.getName(i) + "*****");
        }

		//Path toAbsolutePath() : 作为绝对路径返回调用 Path 对象
        System.out.println(path1.toAbsolutePath());
        System.out.println(path2.toAbsolutePath());
		//Path resolve(Path p) :合并两个路径,返回合并后的路径对应的Path对象
        Path path3 = Paths.get("d:\\", "nio");
        Path path4 = Paths.get("nioo\\hi.txt");
        path3 = path3.resolve(path4);
        System.out.println(path3);

		//File toFile(): 将Path转化为File类的对象
        //Path--->File的转换
        File file = path1.toFile();
        //File--->Path的转换
        Path newPath = file.toPath();
    }
}

Files 类:操作文件或目录

image.png

/**
 * Files工具类的使用:操作文件或目录的工具类
 */
public class FilesTest {

    @Test
    public void test1() throws IOException {
        /**
         * 获取NIOFile块式IO文件路径
         */
        Path path1 = Paths.get("G:\\IDEA2022\\workmenu\\JavaSenior\\day10", "hello.txt");
        Path path2 = Paths.get("baidu.txt");

        /**
         * Path copy(Path src, Path dest, CopyOption … how) : 文件的复制
         * 要想复制成功,要求path1对应的物理上的文件存在。path1对应的文件没有要求
         */
        Files.copy(path1, path2, StandardCopyOption.REPLACE_EXISTING);

        /**
         * Path createDirectory(Path path, FileAttribute<?> … attr) : 创建一个目录
         * 要想执行成功,要求path对应的物理上的文件目录不存在。一旦存在,抛出异常。
         */
        Path path3 = Paths.get("G:\\IDEA2022\\workmenu\\JavaSenior\\day10\\nio\\nio1");
        Files.createDirectory(path3);

        //Path createFile(Path path, FileAttribute<?> … arr) : 创建一个文件
        //要想执行成功,要求path对应的物理上的文件不存在。一旦存在,抛出异常。
        Path path4 = Paths.get("d:\\nio\\hi.txt");
        //Files.createFile(path4);

        //void delete(Path path) : 删除一个文件/目录,如果不存在,执行报错
        //Files.delete(path4);

        //void deleteIfExists(Path path) : Path对应的文件/目录如果存在,执行删除.如果不存在,正常执行结束
        Files.deleteIfExists(path3);

        //Path move(Path src, Path dest, CopyOption…how) : 将 src 移动到 dest 位置
        //要想执行成功,src对应的物理上的文件需要存在,dest对应的文件没有要求。
        //Files.move(path1, path2, StandardCopyOption.ATOMIC_MOVE);

        //long size(Path path) : 返回 path 指定文件的大小
        long size = Files.size(path2);
        System.out.println(size);

    }

    @Test
    public void test2() throws IOException {
        Path path1 = Paths.get("d:\\nio", "hello.txt");
        Path path2 = Paths.get("baidu.txt");
        //boolean exists(Path path, LinkOption … opts) : 判断文件是否存在
        System.out.println(Files.exists(path2, LinkOption.NOFOLLOW_LINKS));

        //boolean isDirectory(Path path, LinkOption … opts) : 判断是否是目录
        //不要求此path对应的物理文件存在。
        System.out.println(Files.isDirectory(path1, LinkOption.NOFOLLOW_LINKS));

        //boolean isRegularFile(Path path, LinkOption … opts) : 判断是否是文件

        //boolean isHidden(Path path) : 判断是否是隐藏文件
        //要求此path对应的物理上的文件需要存在。才可判断是否隐藏。否则,抛异常。
        //System.out.println(Files.isHidden(path1));

        //boolean isReadable(Path path) : 判断文件是否可读
        System.out.println(Files.isReadable(path1));
        //boolean isWritable(Path path) : 判断文件是否可写
        System.out.println(Files.isWritable(path1));
        //boolean notExists(Path path, LinkOption … opts) : 判断文件是否不存在
        System.out.println(Files.notExists(path1, LinkOption.NOFOLLOW_LINKS));
    }

    /**
     * StandardOpenOption.READ:表示对应的Channel是可读的。
     * StandardOpenOption.WRITE:表示对应的Channel是可写的。
     * StandardOpenOption.CREATE:如果要写出的文件不存在,则创建。如果存在,忽略
     * StandardOpenOption.CREATE_NEW:如果要写出的文件不存在,则创建。如果存在,抛异常
     */
    @Test
    public void test3() throws IOException {
        Path path1 = Paths.get("d:\\nio", "hello.txt");

        //InputStream newInputStream(Path path, OpenOption…how):获取 InputStream 对象
        InputStream inputStream = Files.newInputStream(path1, StandardOpenOption.READ);

        //OutputStream newOutputStream(Path path, OpenOption…how) : 获取 OutputStream 对象
        OutputStream outputStream = Files.newOutputStream(path1, StandardOpenOption.WRITE, StandardOpenOption.CREATE);

        //SeekableByteChannel newByteChannel(Path path, OpenOption…how) : 获取与指定文件的连接,how 指定打开方式。
        SeekableByteChannel channel = Files.newByteChannel(path1, StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);

        //DirectoryStream<Path>  newDirectoryStream(Path path) : 打开 path 指定的目录
        Path path2 = Paths.get("e:\\teach");
        DirectoryStream<Path> directoryStream = Files.newDirectoryStream(path2);
        Iterator<Path> iterator = directoryStream.iterator();
        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }
}

使用BIO-FileChannel实现文件的读取和写入

package com.timothy.socket.nio.file;
 
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
 
public class NioFile {
 
    public static void copyFile(String sourcePath, String destPath) {
        FileInputStream is = null;
        FileChannel rChannel = null;
        FileOutputStream os = null;
        FileChannel wChannel = null;
 
        try {
            is = new FileInputStream(sourcePath);
            rChannel = is.getChannel();
            ByteBuffer buffer = ByteBuffer.allocate(1024);
 
            os = new FileOutputStream(destPath);
            wChannel = os.getChannel();
 
            int readBytes = 0;
            while ((readBytes = rChannel.read(buffer)) > 0) {
                // 写模式切换为读模式
                buffer.flip();  // limit = position, position = 0
                while (buffer.hasRemaining()) {
                    wChannel.write(buffer);
                }
                buffer.clear(); // position = 0, limit = capacity, mark=-1
            }
 
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                rChannel.close();
                wChannel.close();
                is.close();
                os.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
 
    public static void main(String[] args) {
        String sourcePath = "D:\\test\\test1.txt";
        String destPath = "D:\\test\\test2.txt";
        NioFile.copyFile(sourcePath, destPath);
    }
}

Java AIO

Java AIO 即 Async 非阻塞, 是异步非阻塞的 IO。

使用AIO-AsynchronousFileChannel实现文件的读取和写入

public class AIOReadFile {
    public static void main(String[] args) throws IOException {
        // aioReadFile();
        // aioWriteFile();
        aioWriteFile_2();
    }
 
    public static void aioReadFile() throws IOException {
        Path path = Paths.get("D:\\out.txt");
        try (AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);) {
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            Future<Integer> read = fileChannel.read(byteBuffer, 0);
            while (!read.isDone()) {
                System.out.println("reading");
            }
            System.out.println(read.isDone());
            System.out.println(byteBuffer);
            byte[] array = byteBuffer.array();
            System.out.println(array.length);
 
            System.out.println(new String(array, "UTF-8"));
        }
    }
 
    public static void aioWriteFile() throws IOException {
        Path path = Paths.get("D:\\outaio.txt");
        try (AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE);) {
            ByteBuffer byteBuffer = ByteBuffer.wrap("我就想写点东西".getBytes("UTF-8"));
            /**
             * 设置capacity position limit 在读写模式下 capacity相同 都是设置的缓冲区大小 limit读是缓存中实际数据多少 写模式下=capacity
             * 因此此处就position重要 表示设置bytebuffer从头开始写
             * byteBuffer.flip();
             */
            Future<Integer> write = fileChannel.write(byteBuffer, 0);
            while (!write.isDone()) {
                System.out.println("writing");
            }
            System.out.println(write.isDone());
            System.out.println(new String(byteBuffer.array(), "UTf-8"));
        }
    }
 
    public static void aioWriteFile_2() throws IOException {
        Path path = Paths.get("D:\\outaio2.txt");
        try (AsynchronousFileChannel fileChannel = AsynchronousFileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE);) {
            ByteBuffer byteBuffer = ByteBuffer.wrap("我就想写点东西".getBytes("UTF-8"));
            /**
             * 设置capacity position limit 在读写模式下 capacity相同 都是设置的缓冲区大小 limit读是缓存中实际数据多少 写模式下=capacity
             * 因此此处就position重要 表示设置bytebuffer从头开始写
             *
             * byteBuffer.flip();
             */
            fileChannel.write(byteBuffer, 0, "anything is ok here", new CompletionHandler<Integer, String>() {
                @Override
                public void completed(Integer result, String attachment) {
                    System.out.println(result + "/" + attachment);
                    System.out.println(byteBuffer);
                }
 
                @Override
                public void failed(Throwable exc, String attachment) {
                    System.out.println(byteBuffer);
                }
            });
            //这里没法获取write对象 所以不能像上面一样判断是否结束 所以让线程睡一会
            Thread.sleep(5050);
            System.out.println(new String(byteBuffer.array(), "UTf-8"));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

BIO、NIO、AIO区别及应用

BIO、NIO、AIO区别

image.png

BIO、NIO、AIO应用

image.png

参考链接

用 BIO NIO AIO 实现文件读写
https://blog.csdn.net/qq_27870421/article/details/90143979
https://blog.csdn.net/qq_34557770/article/details/96873130



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值