JAVA IO流

12. IO流

文件操作的简介

操作磁盘上的某⼀个⽂件或者⽂件夹。可以对他们进⾏创建、删除、移动、属性获取、属性设置等操

作。但是,并不包含读取⽂件的内容,拷⻉⽂件。

在Java中,使⽤ java.io.File 类描述⽂件(夹)。

绝对路径和相对路径

路径:⽤来描述⼀个⽂件所存在的地址,可以分为 绝对路径 和 相对路径。

绝对路径:从磁盘的根⽬录开始,⼀层层的向内查找,直到找到这个⽂件。

相对路径:某⼀个⽂件,相对于指定⽂件的路径(),在项目中,所有的相对路径都是针对项目的根目录的,例:File file = new File(“src\out\day23.java”)。

路径表示优点缺点
绝对路径⽤来表示⼀个路径,只要还在这个磁盘上,肯定可以找到指定的⽂件的。⼀旦换⼀个⽂件系统,此时这个路径表示的⽂件将⽆法找到。
相对路径只要两者的相对位置不变,⽆论在哪⼀个⽂件系统中,都可以找到这个⽂件。⽤来表示路径,只要两者的相对位置发⽣了改变,这个⽂件将⽆法找到。
12.1 IO流的简介

流:在文件和程序之间建立的让数据在其中流通(单向流动)的管道,就是一个流对象。

IO流:Input、Output,输入输出。

使用IO流,实现对磁盘上的某个文件进行读写的操作。

IO流依据不同的分类方式可分为不同类型。

按方向划分按流中的数据单位划分
输入流字节流
输出流字符流

必须掌握的流:父类流,Java中的流非常多,但均继承自以下四个父类之一。

父类流含义
InputStream字节输入流
OutputStream字节输出流
Reader字符输入流
Writer字符输出流

注意事项:

  1. 上述四个⽗类流,他们都是抽象类,不能直接实例化对象,需要借助⼦类对象。
  2. 流对象⼀旦实例化完成,此时这个流是会持有这个⽂件的。此时将不能对这个⽂件进⾏某些操作。
  3. ⼀个流在使⽤结束后,⼀定要释放流资源。
12.2 File类

File:对磁盘上某一文件(目录)的描述,因为IO流需要对文件进行操作,因此在使用IO流的时候,一定会使用到File类。File类还包含若干对文件的操作方法。

File类在Java.io包里

关于⽬录分隔符,在不同的操作系统中,不⼀样。在windows中,使⽤ \ 作为⽬录分隔符,但是,在⾮windows的操作系统中,例如:Linux、Unix,使⽤ / 作为⽬录分隔符。

关于路径分隔符,在不同的操作系统中,不⼀样。在windows中,使⽤ ; 作为路径分隔符,但是,在⾮windows的操作系统中,例如:Linux、Unix,使⽤ : 作为路径分隔符。

静态属性描述
separator根据不同的操作系统,返回不同的目录分隔符(字符串)
pathSeparatorChar根据不同的操作系统,返回不同的目录分隔符(字符)
pathSeparator根据不同的操作系统,返回不同的路径分隔符(字符串)
pathSeparatorChar根据不同的操作系统,返回不同的路径分隔符(字符)
Windows操作系统非Windows操作系统
目录分隔符\/
路径分隔符;:
构造方法描述
File(String pathname)通过文件的路径来实例化一个File对象
File(String parent,String child)拼接两个路径得到一个完整的路径
File(File parent,String child)根据一个父级文件和子路径得到一个新的File对象

File文件相关操作:

public class Program1 {
	public static void main(String[] args) {
		// 1.关于文件的路径
		// 不同操作系统内使用的目录分隔符是不同的,Windows中是符号:\ ,路径分隔符也不同,Windows中的是符号:;
		String a = File.separator;
		char b = File.pathSeparatorChar;
		String c = File.pathSeparator;
		char d = File.pathSeparatorChar;
		
		File file = updataInfo();
		getInfo(file);
		String path = "E:\\软件目录\\Eclipse\\JAVA";
		show(path);
		
	}
	//关于File对象的操作
	private static File updataInfo() {
		//创建文件
		// 构造方法1
		File file1 = new File("E:\\软件目录\\Eclipse\\JAVA\\新建文件1");
		// 构造方法2
		File file2 = new File("E:\\软件目录\\Eclipse","JAVA\\新建文件2"); // 不一定从磁盘开始
		try {
			//返回尝试创建的结果
			boolean result1 = file1.createNewFile();
			if (result1) {
				System.out.println("文件创建成功");
			} else {
				System.out.println("文件创建失败");
			}
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		//2.1创建文件夹(只能创建一级目录)并返回创建结果,如果创建成功,会返回true
		// 创建失败原因:
		// 指定路径下,已经有这个⽂件了。
		// 没有⽗级⽬录的写权限
		// ⽗级路径不存在
		//返回尝试创建的结果
		boolean result2_1 = file2.mkdir();
		//2.2创建文件夹(可以创建多级目录)
		//返回尝试创建的结果
		boolean result2_2 = new File("E:\\软件目录\\Eclipse","JAVA\\新建文件夹2\\新建文件夹2_2").mkdirs();
		//3.删除一个文件或一个空的文件夹(非空文件夹删不掉)
		//该删除操作永久删除,不会进入回收站
//		new File("E:\\软件目录\\Eclipse\\JAVA\\新建文件1").delete();
		//4.重命名一个文件(同时移动一个文件的位置)
        // 失败的情况:重命名的⽂件已存在
		// 源⽂件不存在
		System.out.println(file2.renameTo(new File("E:\\软件目录\\Eclipse","JAVA\\新建文件夹2_2")));
		return file1;
	}
	
	// 常用关于File对象若干属性的获取方法
	private static void getInfo(File file1) {
		// 1.判断文件是否存在
		boolean boolean1 = file1.exists();
		// 2.判断一个路径指向的空间是否是一个文件
		boolean boolean2 = file1.isFile();
		// 3.判断一个路径指向的空间是否是一个文件夹
		boolean boolean3 = file1.isDirectory();
		// 4.文件权限判断
		boolean boolean4_1 = file1.canRead(); // 判断文件是否可读
		boolean boolean4_2 = file1.canWrite(); // 判断文件是否可写
		boolean boolean4_3 = file1.canExecute(); // 判断文件是否可执行
		// 5.判断文件是否是隐藏文件(隐藏文件以 . 开头)
		boolean boolean5 = file1.isHidden();
		// 6.获取文件的大小(字节)
		Long long6 = file1.length();
		// 7.获取文件的名字
		file1.getName();
		// 8.获取文件的路径
		String string8_1 = file1.getPath(); // 相对路径:相对于某文件夹至该文件的路径
		// 注:在Eclipse中,相对路径默认是相对于当前项目而言的
		String string8_2 = file1.getAbsolutePath(); // 绝对路径:从磁盘的根目录至该文件
		// 9.获取父级路径(路径字符串)
		String string9 = file1.getParent();
		// 10.获取描述父级文件的FIle类
		File file10 = file1.getParentFile();
		// 11.获取上次修改的时间
		Date date11 = new Date(file1.lastModified());
		System.out.println(date11);
	}
	//查询某一文件夹下所有的子文件夹
		private static void show(String path) {
    	//指定的路径做出File对象
    	File file = new File(path);
    	//1.列举一个目录下所有文件的名字
    	String[] files1 = file.list();
    	//2.listFiles(FileFilter filter)
        //将file文件夹下的所有文件都带入到方法中,返回 返回值为true的文件构成的字符串数组
    	//File file:文件夹名字
    	//String name:子文件夹的名字
		// 获取⼀个路径下满⾜条件的⽂件
		File[] files = file.listFiles(f -> !f.isHidden() && f.length() > 100 * 1024 *1024);
        //例:列举出指定目录下所有的chw格式文件
            //f:父类目录
            //s:文件名
        String[] files2 = file.list((f,s)->s.endsWith(".chw"));
        //3.获取一个文件夹中所有的文件(返回所有子文件构成的File数组)
        File[] file3 = file.listFiles();
        //将file文件夹下的所有文件都带入到方法中,返回 返回值为true的文件构成的File数组
        File[] file4 = file.listFiles(f->f.getName().endsWith(".mp4"));
        //3.list()获取到⼀个路径下所有的⼦⽂件,以 String[]的形式返回,数组中存储的是⽂件的名字
		String[] files = file.list();
		Arrays.stream(files).forEach(System.out::println);
    }
}

URI:

协议头,例:http:// ftp:// smb://

主机,例:www.baidu.com

端口,例: :8080/

访问文件的路径,例:root/second/third/…/a.java

分隔文件和参数列表:?

参数列表,例:username=xxx&passwd=123

12.3 InputStream、OutputSteam

重点:(容易出现乱码)

如何使用InputStream进行文件的读取

如何使用OutputStream进行文件的写操作

声明一个字节流对象:

public class Program1 {
	public static void main(String[] args) {
		//字节输入流:读取某个文件中的数据
//		InputStream is = new FileInputStream("file\\source");
//		InputStream is = new FileInputStream(new File("file\\source"));
		//实际使用中,先声明一个InputStream
		InputStream is = null;
		try {
			//实例化一个输入流
			is = new FileInputStream("file\\source");
//			E:\软件目录\Eclipse\JAVA\TestProgram\file\source
//			E:\软件目录\Eclipse\JAVA\TestProgram\src\bbinary\Program1.java
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		finally {
			//一个流若不再使用,一定要关闭它,否则不允许对文件进行其他操作,例:删除
			try {
				is.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		System.out.println(new File("file\\source").delete());
	}
}

注:所有的流在使用结束后一定要立即关闭

方案一:try代码段结束前加 对象.clear()

方案二:InputStream已经继承了Autocloseable接口可以在try后,大括号前添加小括号,将流的声明放在小括号内

注意事项:过了try代码段,流就关闭了,此时一切对流的操作都无效

语法进阶:

public class Program2 {
	public static void main(String[] args) {
		//实例化 流对象的进阶语法(不需要手动关闭,因为try后小括号的代码段伴随try的开关):
		try(InputStream is = new FileInputStream("file\\source")) {
			
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}
}

通过流对象读取某文件的内容:

public class Program3 {
	public static void main(String[] args) {
		//读取流中的数据:
		//int read():读取流中的数据,每次调用读取一个字节,返回值代表本次读取到的内容
		//int read(byte[] array):读取流中的数据,将读取到的数据存入一个数组,返回值代表本次读取到多少个字节数据
		//1.实例化了一个流的对象,在文件和程序之间建立了一个管道
		try (InputStream is = new FileInputStream("file\\source")){
			//2.实例化一个字节数组,循环读取一个文件中的数据
			byte[] array = new byte[10];
			//3.为防止最后一组array填不满,而多出乱码等情况
			//需声明一个变量,用来记录每次读到了多少数据
			int length = 0;
			//4.循环读取流中的数据
			while ((length = is.read(array))!=-1) {		//is.read(array)返回该组array读取的字节数(无数据则返回-1),无参read函数代表每次读取一个元素
				//将字节数组中的数据进行处理
				String string = new String(array,0,length);//为防止最后一组array填不满,而多出乱码
				System.out.print(string);
			}
			
		} catch (Exception e) {
			e.printStackTrace();;
		}
	}
}

通过流对象某文件进行写操作

public class Program4 {
	public static void main(String[] args) {
		//实例化一个流对象,连接程序和指定的文件
		//在写程序的操作中,若目标文件不存在,则会自动创建一个文件
		//FileOutputStream实例化的时候,会有一个boolean append参数来控制新的写操作是否会覆盖原有内容
		//append:true->在原有内容的后面追加新的数据;
		//append:false->删除原有数据并写入新数据,不写则默认值为false;
		try (OutputStream os = new FileOutputStream("file\\target",true)){
			//如何进行写操作
			//注意:写操作并不是将数据写入到文件中,而是写入到输出流,再由输出流将数据流动到文件中
			os.write("你好,世界 hello,world".getBytes());
			//注意:每次写操作后,都需要添加一个flush操作
			//目的:冲刷数据流,加速流中的数据流动到文件中
			//在关闭流的时候,系统会自动调用一次flush操作以确保流中的数据都已经流动到文件中
			os.flush();
			System.out.println("写数据完成!");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		catch (IOException e) {
			e.printStackTrace();
		}
	}
}

OutputStream注意事项:

  1. 使⽤⼀个输出流,建⽴程序与⽂件的连接。**如果这个⽂件不存在,会自动的创建。**但是在创建的时候,要求⽗级⽂件夹是存在的。如果父级文件夹不存在,则这个⽂件也会创建失败,会出现FileNotFoundException 异常。
  2. 使⽤流建议⽂件与程序的连接,当这个流第⼀次向⽂件中写数据的时候,会将⽂件中原来的数据全部清除,再写⼊新的数据。从第⼆次写开始,将会在原来的基础上向后追加。

拷贝文件

public class Program5 {
	public static void main(String[] args) {
		copy("E:\\文件目录\\source.xlsx","C:\\Users\\陈永豪\\Desktop\\garget.xlsx");
	}
	//从文件名source拷贝至文件名target的文件中
	private static void copy(String source,String target) {
		//循环读取文件中的数据,将读取到的数据写入到目标文件
		try (InputStream is = new FileInputStream(source);OutputStream os = new FileOutputStream(target)){
			//1.实例化一个字节数组,用来存储每次读到的数据
			byte[] array = new byte[1024];
			//2.声明一个变量,存储每次读取到的字节数量
			int length = 0;
			//3.循环读取
			while ((length = is.read(array))!=-1) {
				//4.将读取到的数据写入到输出流中
				os.write(array,0,length);
				os.flush();
			}
			System.out.println("文件拷贝完成");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}catch (IOException e) {
			e.printStackTrace();
		}
	}
}

加密操作:

public class Program6 {
	public static void main(String[] args) {
		String file = "E:\\文件目录\\source.xlsx";
		lock(file);
	}
	//加密代码
	private static void lock(String file) {
		//思路:在当前路径下,创建一个新的文件,命名为: 原文件的命名.lock
		//例:source.mp4->source.mp4.lock
		String lockFilePath = file +".lock";
				try (InputStream is = new FileInputStream(file);OutputStream os = new FileOutputStream(lockFilePath)){
					byte[] array = new byte[1024];
					int length = 0;
					while ((length = is.read(array))!=-1) {
						array = lock(array);
						os.write(array,0,length);
						os.flush();
					}
					System.out.println("文件拷贝完成");
				} catch (FileNotFoundException e) {
					e.printStackTrace();
				}catch (IOException e) {
					e.printStackTrace();
				}
				new File(file).delete();
		}
		private static byte[] lock(byte[] array) {
		int secury = 1234;
		for (int i = 0; i < array.length; i++) {
			array[i] ^=secury;
		}
		return array;
	}
}

解密操作:

public class Program7 {
	public static void main(String[] args) {
		String file = "E:\\文件目录\\source.xlsx.lock";
		copyAndSecure(file);
		
	}
	private static void copyAndSecure(String file) {
		String lockFilePath = file.substring(0,file.length()-5);
		try (InputStream is = new FileInputStream(file);OutputStream os = new FileOutputStream(lockFilePath)){
			byte[] array = new byte[1024];
			int length = 0;
			while ((length = is.read(array))!=-1) {
				array = lock(array);
				os.write(array,0,length);
				os.flush();
			}
			System.out.println("文件拷贝完成");
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}catch (IOException e) {
			e.printStackTrace();
		}
		new File(file).delete();
	}
	private static byte[] lock(byte[] array) {
		int secury = 1234;
		for (int i = 0; i < array.length; i++) {
			array[i] ^=secury;			//位异或(两次异或得原码,即第二次解密)
		}
		return array;
	}
}
12.4 Reader、Writer

Reader、Writer是所有的字符流的父类,一般情况下字符流是用来操作文本的

Reader:是一个输入流。

Writer:是一个输出流。

字符流,流中流动的数据的单位是–字符,一般是对文本进行操作。

使用字符流在读取数据的时候,过程与字节流读取基本相同,不同点在于:使用字节流读取数据,需要用到一个字节数组,将读取到的数据存入到一个字节数组中;使用字节流读取数据,需要用到一个字符数组,将读取到的数据存入到一个字符数组中。

将数据读取到字符输入流

public class Program1 {
	public static void main(String[] args) {
		//1.实例化一个FileReader对象并向上转型为Reader类型
		try (Reader reader = new FileReader("file\\source")) {
			//2./实例化一个字符数组,用来存储每次从流中读取的数据
			char[] array = new char[10];
			//3.声明一个变量,用来存储每次读取到多少个数据(字符)
			int length = 0;
			//4.循环读取
			while ((length=reader.read(array))!=-1) {
				//5.将字符数组中的元素拼接成字符串
				String string = new String(array,0,length);
				System.out.println(string);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}catch (IOException e) {
			e.printStackTrace();
		}
	}
}

将数据写入到字符输出流

public class Program2 {
	public static void main(String[] args) {
		try (Writer writer = new FileWriter("file\\target",true)) {
			writer.write("哈哈哈哈哈");
			writer.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

字节流与字符流的区别:

  1. 字节流可以用于任何类型数据的输入输出,字符流只能用于文本数据的输入输出
  2. 对于文本数据字节流和字符流都能用,只不过用字符流更方便,因为字符流处理了编码
12.5 缓冲流

对父类流进行了一层包装,添加了一个缓冲区(Buffer),目的是提高流操作的效率。

BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter

在所有的缓冲流中,都有一个属性用来记录所包装的流对象,在缓冲流的close中,会对包装的流对象进行自动的关闭操作(使用完缓冲流的时候,通过关闭缓冲流即可

缓冲字节流的读操作:

public class Program1 {
    public static void main(String[] args) {
        //实例化
        //通过一个InputStream实例化一个BufferedInputStream
        try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream("file\\source"))) {
            //1.BufferedInputStream继承自InputStream,所以本质上还是字节流,因此读取数组还需字符数组
            byte[] array = new  byte[1024];
            //2.声明一个变量,用来存储每次读取到多少个字节的数据
            int length = 0;
            //3.循环读取
            while ((length=bis.read(array))!=-1) {
                //4.将读取到的数据进行处理
                String string = new String(array,0,length);
                System.out.print(string);
            }
        }catch (FileNotFoundException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

缓冲字节流的写操作:

// 1、通过⼀个 OutputStream,实例化⼀个 BufferedOutputStream
// 此时,BufferedOutputStream在进⾏关闭的时候,会对内部的 字节输出流 进⾏
try (BufferedOutputStream bos = new BufferedOutputStream(new
	FileOutputStream("files\\dst"))) {
 	// 将数据写⼊到输出流中
 	bos.write("hello".getBytes());
    bos.flush();
} catch (IOException e) {
 	e.printStackTrace();
}

缓冲字符流的读操作:

public class Program2 {
    public static void main(String[] args) {
        //实例化
        //通过一个Reader实例化一个BufferedReader
 		//在使⽤完 BufferedInputStream 之后,直接关闭 bis 即可,不需要⼿动关闭 InputStream字节流
        try(BufferedReader br = new BufferedReader(new FileReader("file\\source"))) {
            //1.声明一个String类型的变量,用来存储每次读取一行的数据
            String line = null;
            //2.循环读取
            //readLine():读取流中的一行数据,行的区分,是按照\n计算的,但读取的内容不包含换行符\n(读取的内容在一行中)
            while ((line=br.readLine())!=null) {
                //3.将读取到的数据进行处理
                System.out.println(line);
            }
        }catch (FileNotFoundException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

缓冲字符流的写操作:

public class Program3 {
    public static void main(String[] args) throws IOException {
        try (BufferedWriter bw = new BufferedWriter(new FileWriter("file\\target",true))){
            bw.write("后宫佳丽三千人,三千宠爱在一身");
            //在BufferedWriter:增加了一个方法:newLine();
            //在流中写一个换行符,等价于write方法后加一个 \n
            bw.newLine();
            bw.write("缓歌漫舞凝丝竹,尽日君王看不足");
            bw.flush();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
12.6 Scanner类

扫描器类,从一个流或者从一个文件中扫描数据(不一定是控制台)。

12.6.1 标准输入、输出流

System.in:系统标准字节输入流,本质来讲是一个InputSteam,但是这个对象是系统自己实例化的。连接了程序和控制台,在内部封装了一些逻辑,可以阻塞线程,等待用户的输入,直到用户输入完成,可以继续进行其他的操作。

System.out:系统标准输出流,本质来讲是一个打印流(printSteam),可以将程序中的数据输出到控制台,也可以从控制台向指定文件输入内容,其实连接控制台的操作是由打印流完成的。

重定向标准输入流:

// 在重定向输⼊流之前,先备份原来的输⼊流,以便使⽤结束之后,恢复
InputStream original = System.in;
// 实例化⼀个 InputStream,读取⼀个⽂件中的数据
try (BufferedInputStream inputStream = new BufferedInputStream(new
FileInputStream("files\\src"))) {
 	// 重定向标准输⼊流
 	System.setIn(inputStream);
 	// 此时,再使⽤到 System.in 的时候,其实⽤的是 inputStream,此时从指定文件中读取数据到控制台。
 	Scanner scanner = new Scanner(System.in);
 	while (scanner.hasNextLine()) {
 	System.out.println(scanner.nextLine());
 	}
 	scanner.close();
} catch (IOException e) {
 	e.printStackTrace();
}
// 因为此时重定向了标准输⼊流,此时的标准输⼊流就是上⽅try中的inputStream
// 但是!!! try结构结束之后,这个inputStream会被close。
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
 	System.out.println(scanner.nextLine());
}
scanner.close();

标准输入流的相关操作:

public class Program1 {
    public static void main(String[] args) {
        //Scanner类:读取指定文件中的数据
        try (Scanner scanner = new Scanner(new File("file\\source"))) {
            //循环并判断是否读完
            while (scanner.hasNextLine()) {
                String string = scanner.nextLine();
                System.out.println(string);
            }
            //Scanner对象在使用完成后,是需要关闭的
            scanner.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

标准输出流的相关操作:

public class Program2 {
    public static void main(String[] args) {
        //备份最初始的标准输出流
        PrintStream tmp = System.out;
        //1.实例化一个打印流对象
        try (PrintStream ps = new PrintStream(new FileOutputStream("file\\log",true));){
            //重定项系统标准输出流
            System.setOut(ps);
            System.out.println("hello world");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        System.setOut(tmp);
        System.out.println("你好 世界");
    }
}
12.7 转换流

全称——字节字符转换流,底层的实现是字节流,这样能保证数据读写过程中不会出现字节丢失问题,上层提供的操作⽅法,是字符流的方法。所以,转换流保留了字符操作的便利性,又有字节流的可靠性。最常⻅的使⽤场景,就是读取指定的字符集的⽂本⽂件。

当我们需要读取一个文本数据的时候,由于不同字符集(编码格式)的存在,会导致读取的过程中,出现乱码的问题,可以使用转换流解决这个读写过程中的字符乱码问题。

使用场景:

  1. 使用指定的字符集,读取某文件中的数据
  2. 使用指定的字符集,向一个指定的文件中写数据。

涉及到的两个类:InputStreamReader、OutputStreamWriter

用转换流和指定的字符集对某一文本做读操作:

public class Program1 {
    public static void main(String[] args) {
        //InputStream和一个指定的字符集实例化一个转换流对象
        try (InputStreamReader reader = new InputStreamReader(new FileInputStream("file\\source2"), "gbk") ){
            //1.从流中读取数据
            char[] array = new char[1024];
            int length = 0;
            while ((length=reader.read(array))!=-1){
                System.out.println(new String(array,0,length));
            }
        }catch (UnsupportedEncodingException e){        //捕捉不支持的编码格式
            e.printStackTrace();
        }
        catch(FileNotFoundException e){
            e.printStackTrace();
        }
        catch (IOException e){
            e.printStackTrace();
        }
        //法二:以下读写会稍微快一点
//        try {
//            BufferedReader reader = new BufferedReader(new BufferedReader(new InputStreamReader(new FileInputStream("file\target"))))
//        }catch (UnsupportedEncodingException e){
//            e.printStackTrace();
//        }catch (IOException e){
//            e.printStackTrace();
//        }catch (FileNotFoundException e) {
//            e.printStackTrace();
//        }
    }
}

用转换流和指定的字符集对某一文本做写操作:

public class Program2 {
    public static void main(String[] args) {
        //1.实例化一个采用了指定字符集的输出流
        try(OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("file\\target",true),"utf-8")){
            //2.将数据写入到文本中
            osw.write("\n落霞与孤鹜齐飞,秋水共长天一色");
            osw.flush();
        }catch (UnsupportedEncodingException e){
            e.printStackTrace();
        }catch (FileNotFoundException e){
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}
12.8 Properties

Propeties是集合框架中的一个类,父类是Hashtable,主要作用是读写一个 .properties文件。

properties文件,是一个属性列表文件,在这个文件中,可以存储一些简单的配置数据。

.properties文件的规则:

  1. 是以键值对的形式进行存储的,键和值以等号分隔,不需要加空格,不同的键值对以换行分隔。
  2. 重点:所有的键值对都是以String类型存储的,但不需要也不能写双引号。
  3. 不能写中文(注释可以)
  4. 除了键、等号、值,不需要出现其他任何字符

使用Properties来读写文件:

public class Program {
    public static void main(String[] args) {
        //实例化一个Properties类的对象
        Properties properties = new Properties();
        try {
            //1.从指定的流中读取数据到集合中
            properties.load(new BufferedReader(new FileReader("file\\config.properties")));
            //具有所有Map方法,例:
            properties.remove("name");
            //2。新添键值对
            properties.setProperty("gender","unknown");
            //3.遍历这个集合
            properties.forEach((key,value)-> System.out.printf("%s=%s\n",key,value));
            //4.将一个Properties文件中的数据,写入到指定的Properties文件中
            //参数:OutputStream out:文件的存储路径, String comments:提交日志
            properties.store(new FileOutputStream("file\\config.properties"),"log");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
12.9 对象流

将程序中的某⼀个对象,以⽂件的形式序列化到本地。

两个常用流:ObjectInputStream、ObjectOutputStream

作用:将程序中的某些对象,以文件的形式序列化到本地。(如果没有持久化存储,这个对象将在程序结束的时候被销毁)

序列化:将对象以文件的形式保存到本地,用ObjectOutputStream。

反序列化:将本地的某个文件存储的信息读取出来并给某一个对象的属性赋值,用ObjectInputStream。

注意事项:

  1. NotSerializableException异常:需要序列化的对象对应的类(包含内部类),必须要实现 Serializable 接口。
  2. 如果需要序列化多个对象,不能通过public FileOutputStream(String name, boolean append)方法拼接对象的信息,而是将所有对象存入一个集合,将这个集合整体序列化到本地。

通过对象进行序列化和反序列化的操作:

public class Program {
    public static void main(String[] args) {
        //1.实例化一个Person对象
        Person person = new Person("xiaoming",20,Gender.Male,new Dog("小白",2));
        //序列化
        save(person);
        Person xiaoming = loadData();
        System.out.println(xiaoming);

    }
    //序列化:将一个Person对象以文件的形式保存到本地
    private static void save(Person person){
        //1.实例化一个ObjectOutputStream对象
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("file\\person"))){
            //2.序列化
            oos.writeObject(person);
            oos.flush();
            System.out.println("序列化完成");        //生成的二进制文件乱码
        }catch (FileNotFoundException e){
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //反序列化:从本地序列化的文件中读取数据,转成对象并返回
    private static Person loadData(){
        //1.实例化一个ObjectInputStream对象
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("file\\person"))){
            //2.反序列化
            Object object = ois.readObject();
            //3.判断object对象是否是一个person对象
            if(object instanceof Person){
                return (Person)object;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
class Person implements Serializable {
    private String name;
    private int age;
    private Gender gender;
    private Dog pet;
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", gender=" + gender +
                ", pet=" + pet +
                '}';
    }

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

    public Person(String name, int age, Gender gender, Dog pet) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.pet = pet;
    }
}
class Dog implements Serializable{
    private String name;
    private int age;
    public Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

12.10 NIO(重点,常见)

NIO:New IO、Non-Blocking IO(非阻塞型IO)

是JDK1.4出现的用来替代传统的IO的一套新的API。NIO与传统的IO有相同的功能,但操作方式不一样,NIO是面向缓冲区(Buffer)、基于通道(Channel)。

在JDK1.7之后,添加了若干新的元素,被称为NIO.2

NIO和IO的区别:

  • NIO是面向缓冲区的,IO是面向流的
  • NIO是非阻塞型,IO是阻塞型
12.10.1 缓冲区Buffer(双向流动)

缓冲区,其实是一个容器,类似于一个数组,这个容器中只能存储基本数据类型的数据。

缓冲区,按照存储的数据类型不同,可以分为以下几个(缓冲区)类:

ByteBufferShortBufferIntBufferLongBuffer
FloatBufferDoubleBufferCharBuffer

以上缓冲区均拥有相同的父类-Buffer类,所以这些类有相同的方式来存储和管理数据。

注意:

  1. 没有存储boolean的缓冲区
  2. Buffer是一个抽象类,不能实例化对象。因此,在实际应用中,用的还是子类的对象。

常见操作:

  1. 获取缓冲区的对象:

    由于缓冲区都是抽象类,不能直接通过new的方式实例化,需要通过静态方法allocate()获取对象。

    注:缓冲区是一个容器,对缓冲区的操作也就只有读和写操作。

    缓冲区的两种模式:读模式 和 写模式

    读模式:在这个模式下,一般情况下是进行缓冲区的数据读取操作。

    写模式:在这个模式下,一般情况下是进行缓冲区的数据写入操作。

  2. 缓冲区的常见属性

    1. position:当前操作的下标

    2. limit:能够操作的空间数(是一次读写操作的上限,当切换成读模式时,数值才会改变)

    3. capacity:缓冲区的容量

    4. mark:标记,在当前缓冲区的position位置中添加一个标记,配合reset方法,使position重置为mark标记位。

      以上四个属性都是private权限,因此不能直接获取

      四个属性的大小关系:

      mark<=position<=limit<=capacity

      一旦不满足以上关系就会出现异常

  3. 缓冲区的常见操作:

    1. allocate(int capacity)

      因为所有的缓冲区,都是抽象类,不能实例化对象。因此,缓冲区的开辟,需要通过这个⽅法,或者allocateDirect() 进⾏开辟。在开辟缓冲区的时候,需要传⼊⼀个参数,这个参数,就代表这个缓冲区的最⼤容量。如果类⽐到数组,这⾥就是数组的⻓度。这个容量,⼀旦确定了,缓冲区开辟了,就不能改变了。缓冲区,分为“读”模式,和“写”模式。

    2. put():往缓冲区写数据,如果写的数据超出缓冲区的容量,会出现异常BufferOverflowException。

    3. flip():将缓冲区切换成读模式

    4. get():从缓冲区读取数据,若读取的数据超出limit,则会出现异常BufferUnderflowException。

    5. rewind():重置操作,将position重置为0,重置mark(写模式时limit的范围也会重置为0,读模式时limit不变)

    6. clear():清空,将position、limit、capacity、mark都重置为初始状态,并使缓冲区切换到写模式。

    7. mark():在当前的position位置添加一个标记。

    8. reset():重置position为mark的位置(如果没有设置mark值就reset则会异常)

    常见操作:

    public class Program1 {
        public static void main(String[] args) {
            //通过allocate方法获取一个缓冲区对象
            ByteBuffer buffer = ByteBuffer.allocate(10);
            //
            System.out.println("------allocate()-------");
            System.out.println("capacity = "+buffer.capacity());      //缓冲区的capacity
            System.out.println("limit = "+buffer.limit());
            System.out.println("position = "+buffer.position());
            //向缓冲区中添加数据
            buffer.put("hello".getBytes());
            System.out.println("------put()-------");
            System.out.println("capacity = "+buffer.capacity());
            System.out.println("limit = "+buffer.limit());
            System.out.println("position = "+buffer.position());
            //重置position
            buffer.rewind();
            System.out.println("------rewind()-------");
            System.out.println("capacity = "+buffer.capacity());
            System.out.println("limit = "+buffer.limit());
            System.out.println("position = "+buffer.position());
    //        //切换读模式
    //        buffer.flip();
    //        System.out.println("------flip()-------");
    //        System.out.println("capacity = "+buffer.capacity());
    //        System.out.println("limit = "+buffer.limit());
    //        System.out.println("position = "+buffer.position());
            //从缓冲区中读取数据
            byte[] dst= new byte[buffer.limit()];
            buffer.get(dst);       //无参方法时得到一个字节,可以通过新建字节数组一次性读取多个字节
            System.out.println(new String(dst));
            System.out.println("------get()-------");
            System.out.println("capacity = "+buffer.capacity());
            System.out.println("limit = "+buffer.limit());
            System.out.println("position = "+buffer.position());
            buffer.rewind();
            System.out.println("------rewind()-------");
            System.out.println("position = "+buffer.position());
            System.out.println("limit = "+buffer.limit());
            byte[] dst2 = new byte[2];
            buffer.get(dst2);
         System.out.println("position = "+buffer.position());
            //在当前位置添加一个标记
            buffer.mark();
            buffer.get(dst2);
            System.out.println(new String(dst2));       //ll
            buffer.reset();
            System.out.println("position = "+buffer.position());
            buffer.get(dst2);
            System.out.println(new String(dst2));       //ll
        }
    }
    

    其实缓冲区就是一个容器,对缓冲区的操作就是读和写。所谓两种模式只是从逻辑上分类。

    出于规范在对应模式进行对应操作。

    所谓的flip()方法、clear()方法只是重置了position和limit的值,本质上出发,缓冲区没有所谓的读和写模式。也就是说:“读模式”下可以进行写操作;“写模式”下可以进行读操作。

    public class Program2 {
        public static void main(String[] args) {
            //开辟一个缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(10);        //limit:10,position:0
            buffer.put("hello".getBytes());                     //limit:10,position:5 hello
    //        buffer.flip();                                      //limit:5,position:0
            buffer.rewind();                                    //limit:10,position:0
            byte[] dst = new byte[5];
            buffer.get(dst);                                    //limit:10,position:5
            System.out.println(new String(dst));                //hello
    //        buffer.get(byte);                                   //limit:10,position:10
            buffer.flip();                                      //limit:5,position:0
            buffer.put("world".getBytes());                     //limit:5,position:5 world
            buffer.clear();                                     //limit:10,position:0
            buffer.get(dst);
            System.out.println(new String(dst));                //world
        }
    }
    

    直接缓冲区、非直接缓冲区

    区别:

    1. 直接缓冲区,使用allocate方法实现。

      直接缓冲区,使用allocateDirect方法开辟。

    2. 非直接缓冲区,是建立在JVM内存中的。

      直接缓冲区是建立在物理内存中的。

    3. 直接缓冲区的读写效率高。

      虽然直接缓冲区的读写效率远远高于非直接缓冲区,但实际使用中,还是以非直接缓冲区的使用为主。因为使用直接缓冲区有一些弊端:

      1. 由于直接缓冲区是建立在物理内存的,当我们需要进行写操作的时候,将数据从程序写入到直接缓冲区。此时程序已经丧失了对这个数据操作的可能性。这些数据什么时候写入到文件中,是由操作系统完成的。
      2. 直接缓冲区建立于物理内存,如果物理内存处理不佳,会导致内存泄漏。
      //开辟一个直接缓冲区
      //在物理内存中开辟一个大小1024的直接缓冲区
      ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
      //验证一个缓冲区是否是直接缓冲区
      System.out.println(buffer.isDirect());
      
12.10.2 通道Channel

是一个文件和程序的连接。通道本身不负责数据的传递、数据的传递由缓冲区负责。

通道(Channel)是程序与⽂件之间的连接,在NIO中,数据传递是由缓冲区完成的,通道不负责数据的传递。通道在 java.nio.channels 包中,常⻅的Channel⼦类有:

本地⽂件通道:FileChannel

⽹络⽂件通道:SocketChannel、ServerSocketChannel、DatagramChannel

Channel是一个接口,常用实现类:FileChannel

FileChannel是一个抽象类,不能直接实例化对象。FileChannel对象的获取需要获得指定的途径:

  1. 可以使用支持通道的类,所提供的getChannel()方法来获取。
    1. 本地IO:FileInputStream、FileOutputStream
    2. ⽹络⽂件通道:Socket、ServerSocket、DatagramSocket
  2. 在NIO.2中,通过FileChannel类静态方法 open()获取通道。
  3. 在NIO.2中,使用Files类中的new方法。
12.10.3 路径Path类

一个用来描述文件路径的接口,在实际应用中,很少关心其实现类,Path对象的获取更多情况下,是通过一个工具类Paths获取的。

Path.get(String first,String...more)

方法的作用:会将参数列表中的每一个组成部分拼接起来,组成一个完整的路径,并返回描述这个路径的一个Path对象。

三种通过NIO进行读写数据并传递的操作:

/**
 * 使用 FileChannel.open() 方法
 *
 * Path: 是一个用来描述一个文件路径的接口。常见的使用方法:
 *       Paths.get(String first, String... more): 拼接文件路径,得到Path对象
 *
 * StandardOpenOption:
 *       READ           : 读文件
 *       WRITE          : 写文件
 *       APPEND         : 追加数据
 *       CREATE         : 如果没有这个文件,就创建一个文件;如果存在这个文件,就不做任何操作。
 *       CREATE_NEW     : 如果没有这个文件,就创建一个文件;如果存在这个文件,就抛异常。
 */
public class Program1 {
    private static String src = "file\\source";     //源文件路径
    private static String dst = "file\\target";     //目标文件路径

    public static void main(String[] args) {
        //1.使用支持通道的类,所提供的getChannel()方法来获取。
        method01();
        //2.通过NIO.2中FileChannel类的静态方法open()打开一个通道
        method02();
        //3.通过NIO.2中的Files的new方法,开辟一个通道
        method03();
    }
    //1.使用支持通道的类,所提供的getChannel()方法来获取。
    private static void method01(){
        try (FileInputStream fis = new FileInputStream(src); FileOutputStream fos = new FileOutputStream(dst)){
            //提供的getChannel方法,获取通道
            FileChannel inChannel = fis.getChannel();
            FileChannel outChannel = fos.getChannel();
            //实例化一个缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            while(inChannel.read(buffer)!=-1){          //将数据写入到缓冲区
                //读取
                buffer.flip();              //切换到读模式
                outChannel.write(buffer);   //将数据通过outChannel通道,写入到指定的文件中
                buffer.clear();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
    //2.通过NIO.2中FileChannel类的静态方法open()打开一个通道
    private static void method02(){
        FileChannel inChannel = null;        //选择StandardOpenOption枚举中你需要的操作
        FileChannel outChannel = null;
        try {
            //打开通道,读取src文件中的内容
            inChannel = FileChannel.open(Paths.get(src), StandardOpenOption.READ);
            //打开通道,将数据写入到dst中
            outChannel = FileChannel.open(Paths.get(dst),StandardOpenOption.WRITE,StandardOpenOption.CREATE,StandardOpenOption.READ);
            //如果使用非直接缓冲区读写数据,这个过程参考method01
            //以下是使用直接缓冲区进行数据的读写
            //使用内存映射文件
            MappedByteBuffer min = inChannel.map(FileChannel.MapMode.READ_ONLY,0,inChannel.size());
            MappedByteBuffer mout = outChannel.map(FileChannel.MapMode.READ_WRITE,0,inChannel.size());
            //对直接缓冲区进行读写
            byte[] array = new byte[min.limit()];
            min.get(array);
            mout.put(array);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(inChannel!=null){
                try {
                    //关闭通道
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(outChannel!=null) {
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    //3.通过NIO.2中的Files的new方法,开辟一个通道
    private static void method03() {
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            inChannel = (FileChannel) Files.newByteChannel(Paths.get(src), StandardOpenOption.READ);
            outChannel = (FileChannel) Files.newByteChannel(Paths.get(dst), StandardOpenOption.WRITE, StandardOpenOption.CREATE);
            //实现两个通道之间的数据传递(这种方式是使用直接缓冲区操作的)
            //法一:
//            inChannel.transferTo(0,inChannel.size(),outChannel);
            //法二:
            outChannel.transferFrom(inChannel,0,inChannel.size());
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (inChannel != null) {
                try {
                    //关闭通道
                    inChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (outChannel != null) {
                try {
                    outChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
12.10.4 分散聚合

分散:将数据写入到多个缓冲区,分散写入。

聚合:从多个缓冲区中读取数据,聚合读取。

public class Program2 {
    public static void main(String[] args) {
        try {
            FileChannel inChannel = FileChannel.open(Paths.get("file\\source"), StandardOpenOption.READ);
            FileChannel outChannel = FileChannel.open(Paths.get("file\\target"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
            //将多个缓冲区存入一个数组
            ByteBuffer[]buffers = new ByteBuffer[10];
            for (int i = 0; i < buffers.length; i++) {
                buffers[i]=ByteBuffer.allocate(2014);
            }
            //分散写:将数据写入到多个缓冲区
            inChannel.read(buffers);
            //聚合读:读取多个缓冲区的数据
            outChannel.write(buffers);

        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}
12.10.5 Files类

是一个用来操作file的工具类。

Files的常见操作:

public class Program1 {
    public static void main(String[] args) {
        try {
            //1.创建文件
            Files.createFile(Paths.get("file\\a.txt"));
            //2.创建文件夹
            Files.createDirectory(Paths.get("file\\abc"));
            //3.创建多级目录
            Files.createDirectories(Paths.get("file\\abc\\a\\b\\c"));
            //4.删除文件(空文件夹):如果删除的文件(夹)不存在,则抛出异常
            Files.delete(Paths.get("file\\a.txt"));
            Files.delete(Paths.get("file\\abc\\a\\b"));
            //5.尝试删除文件(夹),返回操作结果
            boolean ret5 = Files.deleteIfExists(Paths.get("file\\a.txt"));
            //6.移动、重命名文件
            Files.move(Paths.get("file\\input"),Paths.get("file\\output"));
            //7.拷贝文件
            Files.copy(Paths.get("file\\source"),Paths.get("file\\dst"));
            //获取文件大小
            Files.size(Paths.get("file\\source"));
            //判断可读
            //判断可写
            //判断可执行
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值