Java零基础入门9.0
- Java系列第九章- IO流
- 1. 初识IO流
- 2. IO流的体系
- 2.1 字节流
- 2.1.1 FileOutputStream 字符串输出流
- 2.1.1 - I 字符串输出流的细节
- 2.1.1 - II FileOutputStream写数据的3种方式
- 2.1.1 -III FileOutputStream写数据的两个小问题
- 2.1.2 FileInputStream 字符串输入流
- 2.1.2 - I FileInputStream 书写细节
- 2.1.2 - II FileInputStream 循环读取
- 2.2 文件拷贝
- 2.2.1 文件拷贝的弊端
- 2.2.2 FileInputStream 一次读取多个字节
- 2.2.3 文件拷贝改写
- 2.3 字符流
- 2.3.1 FileReader
- 2.3.1 - I 空参read
- 2.3.1 - II 有参read
- 2.3.1- III 字符输入流原理解析
- 2.3.2 FileWriter
- 2.3.2 - I 字符输入流原理解析
- 2.4 字节流和字符流综合练习
- 2.4.1 练习一:拷贝文件夹
- 2.4.2 练习二:加密和解密文件夹
- 2.4.3 练习三:修改文件夹中的数据
- 3. IO流中不同JDK版本捕获异常的方式
- 4.字符集
- 4.1 计算机储存规则
- 4.2 ASCII 字符集
- 4.3 GBK 字符集
- 4.4 字符集详解
- 4.5 乱码
- 4.6 编码和解码的方法
- 5.IO流体系高级流
- 5.1 缓冲流
- 5.1.1 字节缓存流
- 5.1.1 - I 字节缓存流提高效率原理
- 5.1.2 字符缓存流
- 5.1.3 综合练习
- 5.1.3 - I 练习一:拷贝文件
- 5.1.3 - II 练习二:拷贝文件
- 5.1.3 - III 练习三:软件运行次数
- 5.2 转换流
- 5.2.1 基本练习
- 5.2.1 - I 练习1:手动创建GBK文件把中文读取到内存当中
- 5.2.2 - II 把一段中文按照GBK的形式写到本地文件
- 5.2.3 - III 将本地文件的GBK转换为UTF-8
- 5.2.4 - IIIV
- 5.3 序列流(对象操作输出流)
- 5.3.1 序列化流(对象操作输出流)
- 5.3.2 反序列化流(对象操作输出流)
- 5.3.3 序列化流和反序列化流的细节
- 5.3.4 序列化流和反序列化流的综合练习:用对象流读写多个对象
- 5.4 打印流
- 5.4.1 字节打印流
- 5.4.2 字符打印流
- 5.5 解压缩流
- 5.6 压缩流
- 5.6.1 压缩单个文件
- 5.6.2 压缩多个文件
- 5.7 常用工具包 Commons-io
- 5.8 Hutool工具包
- 6. IO流的综合练习
- 6.1 网络爬虫
- 6.1.1 爬取姓式
- 6.1.2 爬取名字
- 6.1.3 数据处理
- 6.2 利用糊涂包生成假数据
- 6.3 带权重的随机数
- 6.4 登录注册
- 6.5 配置文件
- 6.5.1 properties配置文件
Java系列第九章- IO流
1. 初识IO流
IO流:存储和读取数据的解决方案
比如:
在玩游戏的时候,游戏进度的数据是保存在内存当中的
内存中的数据特点就是不能永久化的存储程序,程序停止,数据丢失
这时就需要添加一个存档功能,在存储的过程当中就是把数据保存到文件当中
想实现以上:
- 要知道文件在哪里
- 要知道如何进行数据传输,包括如何保存数据、如何读取数据
- IO流就是为此出现的,其与File文件息息相关
File类只能对文件本身进行操作,不能读写文件里面储存的数据
IO流做的事情:
IO流:用于读写文件中的数据(可以读写文件,或网络中的数据…)
- output:写出数据,把程序中的数据保存到本地文件当中
- input:读取数据,把本地数据加载到程序当中
IO流中,谁在读,谁在写?以谁为参照物看读写的方向呢?
以程序(内存)为参照物进行读写
IO流的分类:
纯文本文件:Windows自带的记事本打开能读懂的文件
小结:
2. IO流的体系
2.1 字节流
字节流子类:
字节流读取中文的时候会出现乱码,文件中不要有中文
2.1.1 FileOutputStream 字符串输出流
FileOutputStream
操作本地文件的字节输出流,可以把程序中的数据写到本地文件中
import java.io.FileOutputStream;
import java.io.IOException;public class Main {public static void main(String[] args) throws IOException {//1.创建对象//写出 输出流 OutputStream//本地文件 File//异常抛出处理,检查一下当前文件下是否有a.txtFileOutputStream fos = new FileOutputStream("javaprogram1\\a.txt");//写出数据fos.write(97);//3.释放资源fos.close();}}
图解:
FileOutputStream的原理
就是创建了一个程序与文件之间的通道
write就是将书写的内容通过通道传输到文件之中
close就是再将这个连接的通道“敲碎”
2.1.1 - I 字符串输出流的细节
1、创建字节输出流对象
- 细节1:参数是字符串表示的路径或者是File对象都是可以的
- 细节2:如果文件不存在会创建一个新的文件,但是要保证父级路径是存在的
- 细节3:如果文件已经存在,则会清空文件
2、写数据
- 细节:write方法的参数是整数,但是实际上写道本地文件中的是整数在ASCII上对应的字符
比如:97 - a
;100 - d
如果想要写入97,就57-"9",55 - "7"
写两次
3、释放资源
- 每次使用完流之后都要释放资源,就是将文件从内存运行中停止,这样后续就能够继续操作文件了,不然是操作不了的
2.1.1 - II FileOutputStream写数据的3种方式
1.一次写一个字节数据
2.一次写一个字节数组的数据
原本a.txt文件存在,那么清空时就把文件内容删除掉,然后重新写入想要的内容,所以不会是覆盖效果
3.一次写一个数组的部分数据
关于wirte的第三种使用:
- 参数一:数组
参数二:起始索引
参数三:个数
2.1.1 -III FileOutputStream写数据的两个小问题
写数据的两个小问题:想要换行写以及再次写不会清空而是继续写的续写
换行写:
再次写出一个换行符就可以了
- windows:
\r\n
, \r表示回车,\n表示换行 - Linux:
\n
- Mac:
\r
细节:
- 在Windows操作系统当中,java对回车换行进行了优化,
虽然完整的是\r\n
,但是我们写其中一个\r或者\n
就可以
java也可以实现换行,因为java在底层会补全
建议:
不要省略,还是写全了
续写:
如果想要续写,打开续写开关即可
开关位置:创建对象的第二个参数
默认false:表示关闭续写,此时创建对象会清空文件
手动传递true:表示打开续写,此时创建对象不会清空文件
public class Main {public static void main(String[] args) throws IOException {//1.创建对象FileOutputStream fos = new FileOutputStream("javaprogram1\\a.txt",true);//写出数据String str = "fklwesnmfgklsdrniogjdlsdkg";byte[] bytes1 = str.getBytes();fos.write(bytes1);String wrap = "\r\n";//换行符byte[] bytes2 = wrap.getBytes();fos.write(bytes2);String str2 = "6666666666";byte[] bytes3 = str2.getBytes();fos.write(bytes3);//3.释放资源fos.close();}}
小结:
2.1.2 FileInputStream 字符串输入流
public class Main {public static void main(String[] args) throws IOException {FileInputStream fis = new FileInputStream("javaprogram1\\a.txt");int b1 = fis.read();//只能读取一个字符,读不到了就返回-1System.out.println(b1);fis.close();}}
如果还想读后面的数据,笨方法是一个一个写
2.1.2 - I FileInputStream 书写细节
1、创建字节输入流对象
细节:如果文件不存在,就直接报错
java为什么会这么设计呢?
- 输出流:不存在,创建——把数据写到文件当中
- 输入流:不存在,为什么不直接创建而是报错? 因为创建出来的文件是没有数据的,没有任何意义。
所以java就没有设计这种无意义的逻辑,文件不存在直接报错
程序中最重要的是:数据
2、读取数据
- 细节1:一次读一个字节,读出来的是数据在ASCII上对应的数字
- 细节2:读到文件末尾了,read方法返回-1
- 细节3:如果后面是空格,空格对应的ASCII码是32
- 细节4:如果后面是-1的,那么读取时先读
-
,再读1
,不会把其作为一个整体
3、释放资源
- 细节:每一次使用完流必须要释放资源
2.1.2 - II FileInputStream 循环读取
public static void main(String[] args) throws IOException {FileInputStream fis = new FileInputStream("javaprogram1\\a.txt");//循环读取int b;while((b=fis.read())!=-1){System.out.print((char)b);}fis.close();}a.txt:
fsdejfkijsadrhgeasdklhgukjsadrhg
为什么要定义b,我把读取到的数据直接进行判断然后打印不行吗?
比如:
public static void main(String[] args) throws IOException {FileInputStream fis = new FileInputStream("javaprogram1\\a.txt");//循环读取while ((fis.read()) != -1) {System.out.println(fis.read());}fis.close();}a.txt:
abcde
read:表示读取数据,而且每读取一个数据就移动一次指针
- 当写了两个read的时候,在开始的时候
读取到a
进入到循环体当中,又调用了一次fis.read
,所以第二次读的时候读到b,也就是98
- 再次进行循环,循环中又有一个read,第三次读取
读到c
,然后到循环体中第四次读取读到d
,所以就是100
- 再循环,第五次
读到e
,到循环体中第六次读取,往后指针指向为空
,所以返回-1
2.2 文件拷贝
public static void main(String[] args) throws IOException {//读取文件FileInputStream fis = new FileInputStream("javaprogram1\\a.txt");//要拷贝到的文件FileOutputStream fos = new FileOutputStream("javaprogram1\\b.txt");//循环读取int b;long beginTime = System.currentTimeMillis();//开始时间while ((b = fis.read()) != -1) {fos.write(b);}//一个释放资源原则:先开的最后关闭fos.close();fis.close();//计算拷贝时间long endTime = System.currentTimeMillis();//结束时间System.out.println("拷贝文件花费了:" + (endTime - beginTime) + "毫秒");}
2.2.1 文件拷贝的弊端
为什么会慢呢?
2.2.2 FileInputStream 一次读取多个字节
public static void main(String[] args) throws IOException {//读取文件FileInputStream fis = new FileInputStream("javaprogram1\\a.txt");//设计读取的数组byte[] bytes = new byte[2];//一次读取多个字节数据,具体读多少,跟数组长度有关//返回值:本次读取到了多少给字节数据int len1 = fis.read(bytes);System.out.println(len1);String str1 = new String(bytes);System.out.println(str1);int len2 = fis.read(bytes);System.out.println(len2);String str2 = new String(bytes);System.out.println(str2);int len3 = fis.read(bytes);System.out.println(len3);String str3 = new String(bytes);System.out.println(str3);//为什么是ed?int len4 = fis.read(bytes);System.out.println(len4);//-1String str4 = new String(bytes);System.out.println(str4);//为什么最后还是edfis.close();}
图解:
第一次读取:
第二次读取:
- 在第二次读取到cd的时候,会把原来ab的位置覆盖
然后把数组里面的内容拿出来变成字符串,所以打印cd
第三次读取:
- 第三次读取的时候,数据只剩下e没有两个,所以只能读到一个e把c覆盖了,而d没有被覆盖
读了一个数据,所以这次len为1
因此,在读取的时候打印的就是e和残留的d
如果还要读取第四次,那么不管还是原来空参的read方法,还是现在带有数组的read方法,只要读不到数据方法就返回-1,还要打印的话里面数组数据没有被任何数据覆盖,所以打印的就是ed
解决方案:
字符串中的方法可以把字节数组的一部分变成字符串
public static void main(String[] args) throws IOException {//读取文件FileInputStream fis = new FileInputStream("javaprogram1\\a.txt");//设计读取的数组byte[] bytes = new byte[2];//一次读取多个字节数据,具体读多少,跟数组长度有关//返回值:本次读取到了多少给字节数据int len1 = fis.read(bytes);System.out.println(len1);String str1 = new String(bytes,0,len1);//表示每次获取数组中从0索引开始,一共要把len1个元素变成字符串System.out.println(str1);int len2 = fis.read(bytes);System.out.println(len2);String str2 = new String(bytes,0,len2);System.out.println(str2);int len3 = fis.read(bytes);System.out.println(len3);String str3 = new String(bytes,0,len3);System.out.println(str3);fis.close();}
2.2.3 文件拷贝改写
public static void main(String[] args) throws IOException {//读取文件FileInputStream fis = new FileInputStream("javaprogram1\\a.txt");FileOutputStream fos = new FileOutputStream("javaprogram1\\b.txt");//拷贝int len;long start = System.currentTimeMillis();byte[] bytes = new byte[1024];while((len=fis.read(bytes))!=-1){fos.write(bytes,0,len);System.out.println(len);}//释放资源fos.close();fis.close();long end = System.currentTimeMillis();System.out.println((end - start)+"毫秒");}
2.3 字符流
2.3.1 FileReader
2.3.1 - I 空参read
1、创建字符输入流对象
2、读取数据
3、释放资源
read() 细节:
- read():默认也是一个字节一个字节的读取的,如果遇到中文就会一次读取多个
- 在读取之后,方法的底层还会进行解码并转成十进制
最终把这个十进制作为返回值
这个十进制的数据也表示在字符集上的数字
英文:
文件里面的二进制数据比如 0110 0001
read方法进行读取,解码并转成十进制比如27721
想要看到中文汉字,就是把这些十进制数据,再进行强转就可以了
public static void main(String[] args) throws IOException {//1.创建对象并关联本地文件FileReader fr = new FileReader("javaprogram1\\a.txt");//2.读取数据//字符流的底层也是字节流,默认也是一个字节,UTF-8一次读三给字节//如果遇到中文就会一次读取多个,GBK一次读两个字节,UTF-8一次读三个字节int ch;while((ch= fr.read())!=-1){System.out.print((char) ch);}//3.释放资源fr.close();}
2.3.1 - II 有参read
public static void main(String[] args) throws IOException {//1.创建对象并关联本地文件FileReader fr = new FileReader("javaprogram1\\a.txt");char[] chars = new char[2];int len;//read(chars):读取数据,解码,强转三步合并了,把强转之后的字符放到数组当中//相当于空参的read+强转类型转换while ((len= fr.read(chars))!=-1){//把数组中的数据变成字符串再进行打印System.out.print(new String(chars,0,len));}fr.close();}
2.3.1- III 字符输入流原理解析
创建输入流对象,底层会创建长度为:8192
的字节数组——缓冲区
1、在内存中定义未赋值的变量ch
2、第一次读取,判断数据是否可以被读取;如果没有,从文件中读取数据,尽可能装满缓冲区,每次都从缓冲区中读取数据提高效率
3、第一次从缓冲区中读,读的是第一个字节,按照UTF-8
的形式进行解码并且转成十进制,再赋值给变量ch
,所以其记录的就是97
,然后强制打印a
4、第二次读取中,发现剩下的3个字节是中文的,所以一次性会读取3个字节按照UTF-8
的形式进行解码,转成十进制25105
,再赋值给ch,然后强转成字符再进行打印
5、第三次读取中,发现在内存当中已经没有要读的东西了,然后从文件中读取。但是,文件中也没有剩余的数据了,那么就返回-1
,最后把-1赋值给ch
public static void main(String[] args) throws IOException {FileReader fr = new FileReader("javaprogram1\\a.txt");fr.read();//把文件中的数据放到缓冲区当中//然后被FileWriter清空了文件FileWriter fw = new FileWriter("javaprogram1\\b.txt");//如果再次使用fr进行读取,会读取到数据吗?//会把缓冲区中的数据全部读取完毕,但是只能读取缓冲区中的数据,文件中剩余数据无法再次读取//因为已经被清空掉了int ch;while((ch=fr.read())!=-1){System.out.print((char)ch);}fw.close();fr.close();}
2.3.2 FileWriter
FileWriter构造方法:
FileWriter成员方法:
FileWriter书写细节:
public static void main(String[] args) throws IOException {FileWriter fw = new FileWriter("javaprogram1\\a.txt");//写出一个字符fw.write(25105);//我//写出一个字符串fw.write("我是煞笔");//我是煞笔//写出一个字符串数组char[] chars = {'a','b','c','我'};fw.write(chars);//abc我//写出字符数组的一部分fw.write(chars,0,2);//abfw.close();}
2.3.2 - I 字符输入流原理解析
在写出的时候,它会把所有的数据按照UTF-8
进行编码,一个中文变成3个字节,一个英文变成1个字节
跟字节流是不一样的:
字节流是没有缓冲区,是直接写到文件当中的目的地
字符流是有缓冲区的
- 那么数据是什么时候才能真正地保存到目的地呢?
flush和close方法
区别:
- 如果用flush,那么下面还可以继续写数据
- 如果用close,那么就直接关流,下面写数据就报错
当数据超过缓冲区的存储时,比如8193,缓冲区放不下,那么此时缓冲区装不下,其数据就会自动地保存到文件当中
flush方法相当于把已经放到缓冲区的数据,刷新到本地文件,所以当程序运行完毕之后,上面的数据在文件当中就已经有了,而下面的数据还在缓冲区当中
如果下面进行关流,那么它在断开链接之前,首先会检查一下缓冲区里面有没有数据。如果有,就会把剩余的所有数据都刷新到本地当中
2.4 字节流和字符流综合练习
2.4.1 练习一:拷贝文件夹
public class Main {public static void main(String[] args) throws IOException {//1.创建数据源File src = new File("F:\\aaa\\ccc");//2.创建对象表示目的地File dest =new File("F:\\aaa\\fff");//3.调用方法开始拷贝copydir(src,dest);}public static void copydir(File src,File dest) throws IOException {dest.mkdirs();//如果dest文件夹不存在那么自动创建//1.进入数据源File[] files = src.listFiles();//2.遍历数组if(files!=null){for (File file : files) {if(file.isFile()){//3.判断为文件,就用字节流拷贝//拷贝的时候一定是从文件开始,从文件结束FileInputStream fis = new FileInputStream(file);//假设a.txt//所以拷贝的时候要拷贝到dest文件夹里面FileOutputStream fos = new FileOutputStream(new File(dest,file.getName()));//将其拷贝到dest当作,这个文件也叫了a.txt//利用字节数组进行拷贝,速度快点byte[] bytes = new byte[1024];int len;while((len=fis.read())!=-1){fos.write(bytes,0,len);}fos.close();fis.close();System.out.println("拷贝成功");}else {//4.为文件夹,就递归//第二个参数表示要拷贝文件夹的里面copydir(file,new File(dest,file.getName()));}}}}
}
2.4.2 练习二:加密和解密文件夹
了解^
:异或,在二进制下两给相同就false,两个不同就true
加密:
public static void main(String[] args) throws IOException {//1.创建对象关联原始文件FileInputStream fis = new FileInputStream("javaprogram1\\a.txt");//2.创建对象关联加密文件FileOutputStream fos = new FileOutputStream("javaprogram1\\b.txt");//3.加密处理int b;while((b=fis.read())!=-1){fos.write(b^2);//数字任意定}//4.释放资源fos.close();fis.close();}
2.4.3 练习三:修改文件夹中的数据
1.
public class Main {public static void main(String[] args) throws IOException {//1.读取数据//在读取的时候我需要的是那个整体,而不是直接索引打印,这样的话只能看而不能用//所以要把其拼接到StringBuilder当中FileReader fr = new FileReader("javaprogram1\\a.txt");StringBuilder sb = new StringBuilder();int b;while ((b = fr.read()) != -1) {sb.append((char) b);}fr.close();System.out.println(sb);//2.排序//那就先把StringBuilder里面的数据先变成字符串之后才能调用split方法按照 - 进行切割String sbStr = sb.toString();String[] arrStr = sbStr.split("-");//然后想要把字符串变成int类型的,就循环一遍用Integer.parseInt,然后把转换的数存到数组当中ArrayList<Integer> list = new ArrayList<>();for (String s : arrStr) {int i = Integer.parseInt(s);list.add(i);}Collections.sort(list);System.out.println(list);//3.写出FileWriter fw = new FileWriter("javaprogram1\\b.txt");for (int i = 0; i < list.size(); i++) {if (i == list.size() - 1) {fw.write(list.get(i) + "");} else {fw.write(list.get(i) + "-");}}fw.close();}}
2.简化版
public class Main {public static void main(String[] args) throws IOException {//1.读取数据//在读取的时候我需要的是那个整体,而不是直接索引打印,这样的话只能看而不能用//所以要把其拼接到StringBuilder当中FileReader fr = new FileReader("javaprogram1\\a.txt");StringBuilder sb = new StringBuilder();int b;while ((b = fr.read()) != -1) {sb.append((char) b);}fr.close();System.out.println(sb);//2.排序Integer[] arr = Arrays.stream(sb.toString().split("-")).map(Integer::parseInt)//方法引用,把字符串转换为整数.sorted()//默认升序排序.toArray(Integer[]::new);//排完后收集起来,收集到int类型的数组当中System.out.println(Arrays.toString(arr));//3.写出FileWriter fw =new FileWriter("javaprogram1\\b.txt");//那么用上面去把数组里面的 ,变成 - 呢?//用repalce方法String s = Arrays.toString(arr).replace(",", "-");//根据数组逗号位置,从索引1开始截取,到数组最后一个位置停止String result = s.substring(1, s.length() - 1);System.out.println(result);fw.write(result);fw.close();}}
3. IO流中不同JDK版本捕获异常的方式
老版本try…catch下面还有个finally
特点:finally里面的代码一定被执行,除非虚拟机停止(JVM退出),什么意思?
如果在try当中写了一个system.exit(0)或者因为其他原因导致虚拟机都停止了,那么finally里面的代码是执行不到的
所以就非常适合将释放资源等扫尾代码放到finally之中
麻烦代码:
public class Main {public static void main(String[] args) {//ctrl + alt + t 快捷迅速环绕FileInputStream fis = null;FileOutputStream fos = null;try {//读取文件fis = new FileInputStream("javaprogram1\\a.txt");fos = new FileOutputStream("javaprogram1\\b.txt");//拷贝int len;byte[] bytes = new byte[1024];while ((len = fis.read(bytes)) != -1) {fos.write(bytes, 0, len);System.out.println(len);}} catch (IOException e) {e.printStackTrace();}finally {//释放资源//fos fis只能在try里面的局部变量中使用//一般来说可以把其放到大括号外面,但这次不能把整个创建对象都放到外面去//因为这个创建对象的代码它是有编译时异常的,但如果放到外面没有初始化那也会报错//所以放到外面只定义空值,而不会去创建其对象//之后报错的就是close方法,还要对其进行异常处理//再嵌套try……catch方法即可//可如果读入文件的时候没有当前路径,fis记录的值就还是null//在finall用null去调用方法,就肯定会报空指针异常//因此还要写一个非空判断if(fos != null){try {fos.close();} catch (IOException e) {e.printStackTrace();}}if(fis != null){try {fis.close();} catch (IOException e) {e.printStackTrace();}}}}
}
简化代码的接口:
在JDK7的时候java推出了一个简单的接口AutoCloseable
不同JDK下的书写方式:
JDK7的方法了解就行,JDK7注意:
不能把所有创建对象的代码都写在小括号当中,
只有实现了AutoCloseable的类才能在小括号当中创建对象
JDK7写法:
public static void main(String[] args) {try (FileInputStream fis = new FileInputStream("javaprogram1\\a.txt");FileOutputStream fos = new FileOutputStream("javaprogram1\\b.txt")){//拷贝int len;byte[] bytes = new byte[1024];while((len=fis.read(bytes))!=-1){fos.write(bytes,0,len);System.out.println(len);}} catch (IOException e) {e.printStackTrace();}//释放资源的代码直接不要}
JDK9写法:
public static void main(String[] args) throws FileNotFoundException {//在外面进行抛出处理FileInputStream fis = new FileInputStream("javaprogram1\\a.txt");FileOutputStream fos = new FileOutputStream("javaprogram1\\b.txt");try (fis ; fos){//拷贝int len;byte[] bytes = new byte[1024];while((len=fis.read(bytes))!=-1){fos.write(bytes,0,len);System.out.println(len);}} catch (IOException e) {e.printStackTrace();}//释放资源的代码直接不要}
4.字符集
4.1 计算机储存规则
4.2 ASCII 字符集
首先是熟悉的ASCII表,就是一个字符集,也叫做编码表
128个数据(对于西方的sucker来说足够使用了)
所以计算机在存储英文的时候一个字节就足以
ASCII编码规则:前面补0,补齐8位
解码直接转,前面补不补零无所谓
如果是汉字该怎么办?
4.3 GBK 字符集
英文用一个字节存储,完全兼容ASCII
GBK英文编码规则:不足8位,前面补0
汉字:两个字节存储
前面的字节叫做高位字节
后面的字节叫做低位字节
高位字节二进制一定以1开头,转成十进制之后是一个负数
汉字编码规则:不需要变动
英文与中文的比较:
小结:
4.4 字符集详解
本来可以用1个字节表示的,却硬要用2个字节,导致空间过于浪费
所以后面又出了UTF-8编码规则:
比如英文UTF-8编码时:
中文UTF-8编码时:
练习:
UTF-8是一个字符集吗?
不是,UTF-8只是字符集中的一个编码方式
小结:
4.5 乱码
原因1:读取数据时未读完整个汉字
原因2:编码和解码时的方式不统一
2.
如何不产生乱码?
在拷贝的时候,数据没有丢失,在用记事本打开的时候用的字符集和编码表,同数据源是一样的,那是不会出现乱码的
4.6 编码和解码的方法
public static void main(String[] args) throws FileNotFoundException, UnsupportedEncodingException {//1.编码String str = "ai你哟";byte[] bytes1 = str.getBytes();//-28-67-96表示你 -27-109-97表示哟System.out.println(Arrays.toString(bytes1));//这个GBK方式有编译时异常,抛出即可byte[] bytes2 = str.getBytes("GBK");System.out.println(Arrays.toString(bytes2));//-60 -29组成 你 -45 -76组成哟//2.解码String s1 = new String(bytes1);System.out.println(s1);String s2 = new String(bytes1, "GBK");System.out.println(s2);//当原本用UTF-8的编码却用GBK解码,结果就会出现乱码}
5.IO流体系高级流
5.1 缓冲流
缓冲流一共有四种
5.1.1 字节缓存流
第一个构造是关于字节输入流,在它里面会默认有一个8192长度的缓冲区
第二个是除了传递一个字节输入流以为,还能手动设定缓冲区的大小
小练习:
1.一次操作一个字节
public static void main(String[] args) throws IOException {BufferedInputStream bis = new BufferedInputStream(new FileInputStream("javaprogram\\a.txt"));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("javaprogram\\b.txt"));int b;while((b=bis.read())!=-1){bos.write(b);}//释放资源//为什么只需要关闭缓冲流,而基本流不用关闭了?//源码在关闭缓冲流的时候会自动关闭基本流bos.close();bis.close();}
2.一次读写多个字节
public static void main(String[] args) throws IOException {BufferedInputStream bis = new BufferedInputStream(new FileInputStream("javaprogram\\a.txt"));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("javaprogram\\b.txt"));byte[] arr= new byte[3];int len;while((len=bis.read(arr))!=-1){bos.write(arr,0,len);}//释放资源//为什么只需要关闭缓冲流,而基本流不用关闭了?//源码在关闭缓冲流的时候会自动关闭基本流bos.close();bis.close();}
5.1.1 - I 字节缓存流提高效率原理
真正把数据写到文件的还是基本流,它会把缓冲区中的数据写道本地文件当中
细节:缓存输入流的缓冲区与缓冲输出流的不是一同个东西
中间的变量b
就是充当一个倒手,在左右这两个缓冲区之间进行来回的倒腾数据,这一段都是在内存当中进行的,运行速度很快
所以倒手的时间可以忽略不记,其真正节约的是读和写时,跟硬盘打交道的时间
数组的倒手,运行更快
5.1.2 字符缓存流
字符缓冲流的构造方法:
字符缓冲流的特有方法:
方法会自动判断你是上面操作系统
1.单行输入
public static void main(String[] args) throws IOException {//1.创建字符缓冲流的对象BufferedReader br = new BufferedReader(new FileReader("javaprogram1\\a.txt"));//2.读取数据//细节//readLine方法在读取的时候,一次读一整行,遇到回车换行结束// 但是不会把回车换行读到内存当中String lien1 = br.readLine();System.out.println(lien1);String line2 = br.readLine();System.out.println(line2);br.close();}
2.全部输入
public static void main(String[] args) throws IOException {//1.创建字符缓冲流的对象BufferedReader br = new BufferedReader(new FileReader("javaprogram1\\a.txt"));String line;while((line=br.readLine())!=null){System.out.println(line);}}
3.字符缓冲输出
public static void main(String[] args) throws IOException {//1.创建字符缓冲流的对象BufferedWriter bw = new BufferedWriter(new FileWriter("javaprogram1\\b.txt"));//2.写出bw.write("阿斯蒂芬和");//换行 br.wirte("\r\n")这个方法不够跨平台bw.newLine();//这个方法是BufferedWriter这个类独有的换行bw.close();}
小结:
5.1.3 综合练习
5.1.3 - I 练习一:拷贝文件
方法2和4都是比较快的
5.1.3 - II 练习二:拷贝文件
1.ArrayList排序
public static void main(String[] args) throws IOException {BufferedReader br = new BufferedReader(new FileReader("javaprogram1\\a.txt"));String line;ArrayList<String> list = new ArrayList<>();while((line=br.readLine())!=null){list.add(line);}br.close();//2.排序//排序规则:按照每一行前面的序号进行排序Collections.sort(list, new Comparator<String>() {@Overridepublic int compare(String o1, String o2) {//获取o1 o2int i1 = Integer.parseInt(o1.split("\\.")[0]);int i2 = Integer.parseInt(o2.split("\\.")[0]);return i1-i2;//1 2 3 4 5 6..}});//写出BufferedWriter bw = new BufferedWriter(new FileWriter("javaprogram1\\b.txt"));for (String s : list) {bw.write(s);bw.newLine();}bw.close();}
2.TreeMap排序
public static void main(String[] args) throws IOException {BufferedReader br = new BufferedReader(new FileReader("javaprogram1\\a.txt"));String line;//2.排序TreeMap<Integer,String> tm = new TreeMap<>();while((line=br.readLine())!=null){String[] arr = line.split("\\.");//0索引时 表序号 1索引时 表内容tm.put(Integer.parseInt(arr[0]),arr[1]);//要是想看到序号arr[1]变成line就行}System.out.println(tm);br.close();//写出BufferedWriter bw = new BufferedWriter(new FileWriter("javaprogram1\\b.txt"));//然后获取treemap的每个键值对对象entrySet,然后遍历Set<Map.Entry<Integer, String>> entries = tm.entrySet();for (Map.Entry<Integer, String> entry : entries) {//entry表示每个键值对,获取其值String value = entry.getValue();bw.write(value);bw.newLine();}bw.close();}
5.1.3 - III 练习三:软件运行次数
IO流原则:
什么时候用就什么时候创建
什么时候不用再什么时候关闭
public static void main(String[] args) throws IOException {BufferedReader br = new BufferedReader(new FileReader("javaprogram1\\a.txt"));String line = br.readLine();//0 字符串int count = Integer.parseInt(line);//表示当前软件又运行了一次count++;//1//2.判断if(count<=3){System.out.println("欢迎使用本软件,第"+count+"次免费使用");}else{System.out.println("本软件只能免费使用3次");}//3.把当前自增之后的count写出到文件当中,这样就可以保证每次重新调用的时候count不被刷新BufferedWriter bw = new BufferedWriter(new FileWriter("javaprogram1\\a.txt"));bw.write(count+"");//后面加""的目的是让其变为字符串bw.close();}
5.2 转换流
转换流是字符流和字节流之间的桥梁
当创建了转换流对象的时候,需要包装一个字节输入流,再包装之后这个字节流它就变成了字符流,就拥有了字符流的特性:
- 读取数据不会乱码
- 可以根据字符集一次读取多个字节
所以转换流的输入流
也叫InputStreamReader
前面的InputStream表示可以把字节流转换成字符流,后面的Reader表示转化流本身是字符流的一员,爹是Reader
同理,转化流的输出流叫OutputStreamReader
idea
默认的字符编码是UTF-8
文件另存的文件编码ANSI
就是GBK
- 第一个参数的是需要关联一个字节输入流,使用平台默认的字符编码
- 第二个参数除了关联字节输入流以外,还需要指定char set name字符编码,可以小写也可以大写(专业点)
- 下面两个同理一样的,一般都是用上面两个
5.2.1 基本练习
5.2.1 - I 练习1:手动创建GBK文件把中文读取到内存当中
public static void main(String[] args) throws IOException {InputStreamReader isr = new InputStreamReader(new FileInputStream("javaprogram1\\gbkfile.txt"),"GBK");int ch;while((ch=isr.read())!=-1){System.out.print((char)ch);}isr.close();System.out.println("------------------------------");FileReader fr = new FileReader("javaprogram1\\gbkfile.txt", Charset.forName("GBK"));int b;while((b=fr.read())!=-1){System.out.print((char)b);}fr.close();}
基本流里指定用来表示字符编码的,使用Charset.forName
5.2.2 - II 把一段中文按照GBK的形式写到本地文件
- 需求2
public static void main(String[] args) throws IOException {OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("javaprogram1\\gbkfile.txt"),"GBK");osw.write("李在干神魔");osw.close();//上面这个方法了解一下就行了FileWriter fw = new FileWriter("javaprogram1\\gbkfile.txt");fw.write("李在干神魔");fw.close();}
5.2.3 - III 将本地文件的GBK转换为UTF-8
- 需求3
JDK11以前的方案
public static void main(String[] args) throws IOException {InputStreamReader isr = new InputStreamReader(new FileInputStream("javaprogram1\\gbkfile.txt"),"GBK");OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("javaprogram1\\a.txt"),"UTF-8");int b;while((b= isr.read())!=-1){osw.write(b);}osw.close();isr.close();}
替代方案:
5.2.4 - IIIV
public static void main(String[] args) throws IOException {FileInputStream fis = new FileInputStream("javaprogram1\\a.txt");//用fis读取肯定会有乱码,所以要把字节流变成字符流InputStreamReader isr = new InputStreamReader(fis);//要读一整行InputStreamReader搞定不了,缓冲流才行BufferedReader br = new BufferedReader(isr);String line;while ((line=br.readLine())!=null){System.out.println(line);}br.close();}
总结:
5.3 序列流(对象操作输出流)
5.3.1 序列化流(对象操作输出流)
区别于直接写对象进文件当中,序列化流写进的数据我们看不懂,要通过反序列化流把数据
Serializable接口
里面是没有抽象方法,这被称为标记型接口
。可以理解成:一个物品的合格证
一旦实现了这个接口,那么就表示当前的类就可以被序列化
public static void main(String[] args) throws IOException {Student stu = new Student("magua",24);//创建序列化流的对象/对象操作输出流ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("javaprogram1\\a.txt"));//写出数据oos.writeObject(stu);oos.close();}
5.3.2 反序列化流(对象操作输出流)
public static void main(String[] args) throws IOException, ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(new FileInputStream("javaprogram1\\a.txt"));Object o = ois.readObject();System.out.println(o);ois.close();}
如果说一个类实现了一个接口,表明这个类就是可序列化的,那么Java底层会根据其成员变量、静态变量、构造方法、成员方法……计算出里面的序列号
,也就是版本号
但是此时如果修改了里面javabean里面的代码,就会重新计算其版本号。当用反序列化流读取版本号1到内存时,两个版本号不一样就直接报错
处理方案:
固定版本号就行了
static:表示这类的所有对象都共享同一个版本号
final:最终表示版本号不会发生变化
long:版本号的数字比较长,所以不要用int来说
serialLVersionUID:统一的变量名ID
idea系统设置快速设置serialLVersionUID
设置完成之后,alt+回车 自动生成
5.3.3 序列化流和反序列化流的细节
对上面反序化流
1.
Student类:
public class Student implements Serializable {private static final long serialVersionUID = 650157805827481085L;private String name;private int age;private String address;public Student() {}public Student(String name, int age) {this.name = name;this.age = age;}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return age*/public int getAge() {return age;}/*** 设置* @param age*/public void setAge(int age) {this.age = age;}//toString重写@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", address='" + address + '\'' +'}';}
}
2.如果不想某个进行序列化流本地文件,比如address对其保密,那么重信生成JavaBean即可,然后把自动生成的serialVersionUID删掉,在不想序列化流的地方加transient
- transient:瞬态关键字
作用:不会把当前属性序列化到本地文件当中
Student类:
public class Student implements Serializable {@Serialprivate static final long serialVersionUID = 650157805827481085L;private String name;private int age;private transient String address;public Student() {}public Student( String name, int age, String address) {this.name = name;this.age = age;this.address = address;}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return age*/public int getAge() {return age;}/*** 设置* @param age*/public void setAge(int age) {this.age = age;}/*** 获取* @return address*/public String getAddress() {return address;}/*** 设置* @param address*/public void setAddress(String address) {this.address = address;}public String toString() {return "Student{name = " + name + ", age = " + age + ", address = " + address + "}";}
}序列化测试:public static void main(String[] args) throws IOException {Student stu = new Student("magua",25,"guangdoor");ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("javaprogram1\\a.txt"));oos.writeObject(stu);oos.close();}反序列化测试:public static void main(String[] args) throws IOException, ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(new FileInputStream("javaprogram1\\a.txt"));Student o = (Student) ois.readObject();System.out.println(o);ois.close();}
可见地址无法被序列查询
小结:
5.3.4 序列化流和反序列化流的综合练习:用对象流读写多个对象
Stuend类同上,不用transient
序列化测试类:public static void main(String[] args) throws IOException {Student stu1 = new Student("magua",25,"guangdoor");Student stu2 = new Student("zhangsan",26,"xinrimuli");Student stu3 = new Student("lisi",27,"wuhu");ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("javaprogram1\\a.txt"));oos.writeObject(stu1);oos.writeObject(stu2);oos.writeObject(stu3);oos.close();}反序列化测试类:public static void main(String[] args) throws IOException, ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(new FileInputStream("javaprogram1\\a.txt"));Student o1 = (Student) ois.readObject();Student o2 = (Student) ois.readObject();Student o3 = (Student) ois.readObject();System.out.println(o1);System.out.println(o2);System.out.println(o3);ois.close();}
但是这样的代码真的好吗?
假设序列化对象是别人创建的,序列化了多少个忘记了,就只能到序列化的文件看,但是这是看不懂的。当进行反序列化的时候,就不知道序列化多少个了,总不能一直读读到出异常
解决方案:
如果要把多个对象序列化到本地文件当中,就一般都会把这些所有对象放到集合里面,再序列化集合就行了
序列化测试:public static void main(String[] args) throws IOException {Student stu1 = new Student("magua",25,"guangdoor");Student stu2 = new Student("zhangsan",26,"xinrimuli");Student stu3 = new Student("lisi",27,"wuhu");ArrayList<Student> list =new ArrayList<>();list.add(stu1);list.add(stu2);list.add(stu3);ObjectOutputStream oos =new ObjectOutputStream(new FileOutputStream("javaprogram1\\a.txt"));oos.writeObject(list);oos.close();}反序列化测试:public static void main(String[] args) throws IOException, ClassNotFoundException {ObjectInputStream ois = new ObjectInputStream(new FileInputStream("javaprogram1\\a.txt"));ArrayList<Student> list = (ArrayList<Student>)ois.readObject();//强转ois读取的对象为ArrayList<Student>for (Student student : list) {System.out.println(student);}ois.close();}
5.4 打印流
5.4.1 字节打印流
构成方法:
成员方法:
细说参数:
- 第一个构造我们可以传递字节输出流
- 第二个构造除了可传递字节输出流以外,还有boolean类型的变量autoFlush,自动刷新
- 第三个构造,有3个参数,第1参数字节输出流的基本流,第2参数自动刷新,第3参数字符串形式的encoding(字符编码)
- 第四个构造,与第三个构造类似,不过后面的字符编码是Charset类型的,不能写成字符串
- 第五个构造,直接关联文件的路径(字符串类型的)
- 下方构造是关联文件路径的,同理一、二、三、四,只不过有个字符编码类型是csn
public static void main(String[] args) throws IOException, ClassNotFoundException {PrintStream ps = new PrintStream(new FileOutputStream("javaprogram1\\a.txt"),true,"UTF-8");ps.println(97);//写出+自动刷新+自动换行ps.print(true);ps.printf("%s李在赣神魔%s","akm","阿克曼");ps.close();}
还有,System.out.println
就用到了打印流
System
是javabean里面已经定义好的一个类
out
是在System里面的一个静态变量,System.out相当于获取的就是一个打印流的对象,不需要自己来创建,虚拟机会创建。其默认指向控制台
public static void main(String[] args) throws IOException {//获取打印流的对象,此打印流在虚拟机启动的时候,有虚拟机创建,默认指向控制台//特殊的打印流,系统中的标准输出流,不能关闭,在系统中是唯一的PrintStream ps = System.out;//调用打印流中的方法println//写出数据,自动换行,自动刷新ps.println("123");ps.close();//当流关闭了,再也无法打印ps.println("aslkifdhas");System.out.println("12314");}
5.4.2 字符打印流
字符流底层有缓冲区,想要自动刷新需要开启true
构造方法:与字节打印流差不多
成员方法:基本上与字节打印流一模一样
代码展示:
public static void main(String[] args) throws IOException, ClassNotFoundException {PrintWriter pw = new PrintWriter(new FileWriter("javaprogram1\\a.txt"),true);pw.println("阿克曼,李在赣神魔");pw.print("曼?不想look可以blue");pw.close();}
小结:
5.5 解压缩流
要想解压,电脑当中首先要有一个压缩包,那么这个压缩包要是zip
作为后缀的,不能是其他
1.
public class Main {public static void main(String[] args) throws IOException {//1.创建一个File表示要解压的压缩包File src = new File("F:aaa.zip");//2.创建一个File表示解压的目的地File dest = new File("F:\\");//调用方法unzip(src,dest);}public static void unzip(File src , File dest) throws IOException {//解压的本质:把压缩包里面的每一个文件或者文件夹读取出来,按照层级拷贝到目的地当中//创建一个解压缩流用来读取压缩包中的数据ZipInputStream zip = new ZipInputStream(new FileInputStream(src));//要先获取到压缩包里面的每一个zipentry对象ZipEntry entry = zip.getNextEntry();System.out.println(entry);}}
2.
public class Main {public static void main(String[] args) throws IOException {//1.创建一个File表示要解压的压缩包File src = new File("F:aaa.zip");//2.创建一个File表示解压的目的地File dest = new File("F:\\");//调用方法unzip(src,dest);}public static void unzip(File src , File dest) throws IOException {//解压的本质:把压缩包里面的每一个文件或者文件夹读取出来,按照层级拷贝到目的地当中//创建一个解压缩流用来读取压缩包中的数据ZipInputStream zip = new ZipInputStream(new FileInputStream(src));//要先获取到压缩包里面的每一个zipentry对象ZipEntry entry;//保证zip包里面对各个对象文件不为空才循环while((entry=zip.getNextEntry())!=null){System.out.println(entry);//isDirectory()用于判断是不是文件夹if(entry.isDirectory()){//是文件夹,则需要在目的地 dest处创建一个同样的文件夹File file = new File(dest,entry.toString());//但是第二个参数不能写zip entry,没有这个类型,就要把其变成字符串才行file.mkdirs();//如果创建目录,则该函数返回true}else {//为文件:则需要读取到压缩包中的文件,并把他存放到目的地dest文件夹中(按照层级目录进行存放)FileOutputStream fos = new FileOutputStream(new File(dest,entry.toString()));int b;while((b=zip.read())!=-1){//写到目的地fos.write(b);}fos.close();//表示在压缩包中的一个文件处理完了zip.closeEntry();;}}zip.close();}}
5.6 压缩流
5.6.1 压缩单个文件
ublic class Main {public static void main(String[] args) throws IOException {//1.创建表示要压缩的文件File src = new File("F:\\w.txt");//2.创建表示压缩包的位置File dest = new File("F:\\");toZip(src,dest);}public static void toZip(File src, File dest) throws IOException {//创建压缩流关联压缩包//aaa.zip是在F盘的根目录下面的,压缩的时候把w.txt这文件写到压缩包当中,所以就不能只写一个dest(这个只是表示F盘的目录)//要添加子级路径才可以ZipOutputStream zos =new ZipOutputStream(new FileOutputStream(new File(dest,"aaa.zip")));//创建zipentry对象,表示压缩包里面的每一个文件和文件夹ZipEntry entry = new ZipEntry("w.txt");//把ZipEntry放到压缩包当中 putNextEntryzos.putNextEntry(entry);//把src文件中的数据写到压缩包当中FileInputStream fis = new FileInputStream(src);int b;while((b=fis.read())!=-1){zos.write(b);}fis.close();zos.closeEntry();zos.close();}}
5.6.2 压缩多个文件
ZipEntry里面的参数:表示压缩包里面的路径
所以就可以在压缩包里面,创建不同层级的子文件夹
ZipEntry entry = new ZipEntry("bbb\\w.txt");
代码案例:
public class Main {public static void main(String[] args) throws IOException {//1.创建File对象表示要压缩的文件夹File src = new File("F:\\aaa");//2.创建表示压缩包放在哪里,也就是父级路径File destParent = src.getParentFile();//根目录F://3.创建表示压缩包的路径File dest = new File(destParent, src.getName() + ".zip");//4.创建压缩流关联压缩包ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(dest));//5.获取src里面的每一个文件,变成ZipEntry对象,放到压缩包当中toZip(src,zos,src.getName());//释放zos.close();}//要获取src里面的每一个文件,变成ZipEntry对象,放入导压缩包当中//参数一:数据流//参数二:压缩流//参数三:压缩包内部的路径public static void toZip(File src,ZipOutputStream zos, String name) throws IOException {//1.进入src文件夹File[] files = src.listFiles();//遍历src文件夹数组for (File file : files) {if(file.isFile()){//如果为文件,则变成ZipEntry对象,放入到压缩包当中//但是如果ZipEntry里面放入file.toString的话,里面会有创建根目录也就是D盘在压缩包里面//当不想要根目录,想要数据源本身的文件夹路径开始时,传递src.getName(相当于传递了aaa)+\\+file.getName(文件名)ZipEntry entry = new ZipEntry(src.getName() + "\\" + file.getName());zos.putNextEntry(entry);//读取文件中的数据写到压缩包FileInputStream fis = new FileInputStream(file);int b;while((b=fis.read())!=-1){zos.write(b);}fis.close();zos.closeEntry();}else {//如果为文件夹-则递归toZip(file,zos,name+"\\"+file.getName());}}}
}
5.7 常用工具包 Commons-io
Commons-io使用步骤:
Commons-io 常见方法:
1.FileUtils类
2.IOUtils类
导入Commons-io
到idea的lib中
当包可以展开时就说明导入成功
代码案例:
public class Main {public static void main(String[] args) throws IOException {File src1 = new File("javaprogram1\\a.txt");File dest1 = new File("javaprogram1\\b.txt");FileUtils.copyFile(src1,dest1); //复制文件src的内容到dest文件里面File src2 = new File("F:\\aaa");File dest2 = new File("F:\\bbb");FileUtils.copyDirectory(src2,dest2);//复制aaa文件夹里面的内容到bbb文件夹当中FileUtils.copyDirectoryToDirectory(src2,dest2);//复制aaa整个文件夹到bbb文件夹当中FileUtils.deleteDirectory(src2);//删除文件夹aaaFileUtils.cleanDirectory(dest2);//清空文件夹bbb}
}
5.8 Hutool工具包
Hutool官网
Hutool中文使用文档
API帮助文档
代码案例:
public class Main {public static void main(String[] args) throws IOException {//FileUtil类的file:强大之处:可以便捷拼接路径File file = FileUtil.file("F:\\", "aaa", "bbb");System.out.println(file);//FileUtil类的touch:当父级路径不存在时不会报错,会帮助把父级路径一起创建出来File touch = FileUtil.touch(file);System.out.println(touch);//FileUtil类的writeLines:把集合种的数据写到文件中,覆盖ArrayList<String> list1 = new ArrayList<>();list1.add("abc");list1.add("abc");list1.add("abc");File file2 = FileUtil.writeLines(list1, "F:\\a.txt", "UTF-8");System.out.println(file2);//FileUtil类的appendLines:把集合种的数据添加文件中,不覆盖ArrayList<String> list2 = new ArrayList<>();list2.add("was");list2.add("was");list2.add("was");File file3 = FileUtil.writeLines(list2, "F:\\a.txt", "UTF-8");System.out.println(file3);//FileUtil类的readLines:指定字符编码,把文件中的数据读到集合中List<String> list = FileUtil.readLines("F:\\a.txt", "UTF-8");System.out.println(list);}
}
6. IO流的综合练习
6.1 网络爬虫
6.1.1 爬取姓式
public class Test1 {public static void main(String[] args) throws IOException {//1.定义变量记录网址String boyNameNet = "http://www.haoming8.cn/baobao/10881.html";String girlNameNet = "http://www.haoming8.cn/baobao/7641.html";String familyNameNet = "https://hanyu.baidu.com/shici/detail?pid=0b2f26d4c0ddb3ee693fdb1137ee1b0d";//2.爬取数据,把王总上所有的数据拼接成一个字符串String boyNameStr = webCrawler(boyNameNet);String girlNameStr = webCrawler(girlNameNet);String familyNameStr = webCrawler(familyNameNet);//3.这些获取了网站的前端代码字符串,还需要通过正则表达式将想要的内容获取出来ArrayList<String> familyNameTempList = getDate(familyNameStr,"(.{4})(,|。)",1);System.out.println(familyNameTempList);}/*作用:根据正则表达式获取字符串中的数据参数一:代表完整的字符串参数二:正则表达式参数三:代表正则表达式的第几组,赵钱孙李。显然要获取在标点符号前面返回值:真正想要的值*/private static ArrayList<String> getDate(String str, String regex,int index) {//1.创建集合存放数据ArrayList<String> list = new ArrayList<>();//2.按照正则表达式的规则获取数据 Pattern.compilePattern pattern = Pattern.compile(regex);//按照pattern的规则,将网址的前端代码字符串转换为姓氏Matcher matcher = pattern.matcher(str);//文本匹配器while(matcher.find()){//用find查找想要的文本元素是否被找到了list.add(matcher.group(index));//将文本匹配器获取正则第index括号里面的东西}return list;}/*作用:从网络中爬取数据,把数据拼接成字符串返回形参:网址返回值:爬取到所有的数据*/public static String webCrawler(String net) throws IOException {//1.定义StringBuilder拼接爬取到的数据StringBuilder sb = new StringBuilder();//2.创建URL对象,这个就表示网址//统一资源标识符(Uniform Resource Identifier ,URL)是采用一种特定语法标识一个资源的字符串。//所标识的资源可能是服务器上的一个文件。Java的URL网络类可以让你通过URL去练级网络服务器并获取资源URL url = new URL(net);//3.链接上这个网址//细节:必须要保证网络畅通,而且这个网址可以链接上URLConnection conn = url.openConnection();//4.读取数据,怎么读取?//一般通过IO流读取,获取输入流读到,但是InputStream是字节流,网站有中文该怎么办?//所以要转换为字符流InputStreamReaderInputStreamReader isr = new InputStreamReader(conn.getInputStream());int b;while((b=isr.read())!=-1){sb.append((char) b);//一定要强转,不然获取到的是数字}isr.close();//5.直接返回StringBuilder的字符串形式return sb.toString();}
}
接下来的任务就是把集合里面的姓氏分开
6.1.2 爬取名字
AnyRule插件:在字符编码中获取范围,比如说中文的范围
女孩的网站名字需要重新找新的规则,不能以空格和回车为标准进行分割
所以要将一整行作为一个元素来进行分割处理
ArrayList<String> boyNameTempList = getDate(boyNameStr,"([\\u4E00-\\u9FA5]{2})(、|。)",1);//如果是(..)(、|。),这样获取的名字里面就有数字标点等组合,而要求的是只要有汉字System.out.println(boyNameTempList);ArrayList<String> girlNameTempList = getDate(girlNameStr,"((.. ){4}(..))",0);System.out.println(girlNameTempList);
6.1.3 数据处理
//4.处理数据//先处理familyNameTempList姓氏//方案:把每一个姓氏拆开并添加到一个新的集合当中ArrayList<String> familyNameList = new ArrayList<>();for (String str : familyNameTempList) {//此时str为 赵钱孙李 周吴郑王//任务就是将每个姓氏都拆开for (int i = 0; i < str.length(); i++) {char c = str.charAt(i);familyNameList.add(c+"");//以字符串的形式放进集合}}System.out.println(familyNameList);//男生名字//处理方案:去除其中的重复元素ArrayList<String> boyNameList = new ArrayList<>();for (String str : boyNameTempList) {if(!boyNameList.contains(str)){//如果男生名字集合里面没有相同的内容才指向添加boyNameList.add(str);}}System.out.println(boyNameList);//女生的名字//处理方案:把里面读到每一个元素用空格进行切割,就可以得到ArrayList<String> girlNameList = new ArrayList<>();for (String str : girlNameTempList) {String[] arr = str.split(" ");//获取到分割后的女孩名字字符串数组后,再将器每个元素循环出来放到集合当中for (int i = 0; i < arr.length; i++) {girlNameList.add(arr[i]);}}System.out.println(girlNameList);
生成数据
public class Test1 {public static void main(String[] args) throws IOException {//1.定义变量记录网址String boyNameNet = "http://www.haoming8.cn/baobao/10881.html";String girlNameNet = "http://www.haoming8.cn/baobao/7641.html";String familyNameNet = "https://hanyu.baidu.com/shici/detail?pid=0b2f26d4c0ddb3ee693fdb1137ee1b0d";//2.爬取数据,把王总上所有的数据拼接成一个字符串String boyNameStr = webCrawler(boyNameNet);String girlNameStr = webCrawler(girlNameNet);String familyNameStr = webCrawler(familyNameNet);//3.这些获取了网站的前端代码字符串,还需要通过正则表达式将想要的内容获取出来ArrayList<String> familyNameTempList = getDate(familyNameStr,"(.{4})(,|。)",1);ArrayList<String> boyNameTempList = getDate(boyNameStr,"([\\u4E00-\\u9FA5]{2})(、|。)",1);//如果是(..)(、|。),这样获取的名字里面就有数字标点等组合,而要求的是只要有汉字ArrayList<String> girlNameTempList = getDate(girlNameStr,"((.. ){4}(..))",0);//4.处理数据//先处理familyNameTempList姓氏//方案:把每一个姓氏拆开并添加到一个新的集合当中ArrayList<String> familyNameList = new ArrayList<>();for (String str : familyNameTempList) {//此时str为 赵钱孙李 周吴郑王//任务就是将每个姓氏都拆开for (int i = 0; i < str.length(); i++) {char c = str.charAt(i);familyNameList.add(c+"");//以字符串的形式放进集合}}System.out.println(familyNameList);//男生名字//处理方案:去除其中的重复元素ArrayList<String> boyNameList = new ArrayList<>();for (String str : boyNameTempList) {if(!boyNameList.contains(str)){//如果男生名字集合里面没有相同的内容才指向添加boyNameList.add(str);}}System.out.println(boyNameList);//女生的名字//处理方案:把里面读到每一个元素用空格进行切割,就可以得到ArrayList<String> girlNameList = new ArrayList<>();for (String str : girlNameTempList) {String[] arr = str.split(" ");//获取到分割后的女孩名字字符串数组后,再将器每个元素循环出来放到集合当中for (int i = 0; i < arr.length; i++) {girlNameList.add(arr[i]);}}System.out.println(girlNameList);//5.生成数据//姓名(唯一)-性别-年龄ArrayList<String> list = getInfos(familyNameList, boyNameList, girlNameList, 10, 10);Collections.shuffle(list);System.out.println(list);//6.写出数据,用流BufferedWriter bw = new BufferedWriter(new FileWriter("javaprogram1\\a.txt"));for (String str : list) {bw.write(str);bw.newLine();}bw.close();}/*作用:获取男生和女生的信息:比如张三-男-24参数一:装着姓氏的集合参数二:装着男生名字的集合参数三:装着女生名字的集合参数四:表示男生的个数参数五:表示女生的个数*/public static ArrayList<String> getInfos(ArrayList<String> familyNameList,ArrayList<String> boyNameList,ArrayList<String> girlNameList,int boyCount,int girlCount){//1.生成男生不重复的姓名HashSet<String> boyhs = new HashSet<>();while(true){if(boyhs.size()==boyCount){//男生名字足够了break;}//随机通过Collections.shuffle打乱集合的方式进行Collections.shuffle(familyNameList);Collections.shuffle(boyNameList);//拼接boyhs.add(familyNameList.get(0)+boyNameList.get(0));}System.out.println(boyhs);//2.生成女生不重复的姓名HashSet<String> girlhs = new HashSet<>();while(true){if(girlhs.size()==girlCount){//男生名字足够了break;}//随机通过Collections.shuffle打乱集合的方式进行Collections.shuffle(familyNameList);Collections.shuffle(girlNameList);//拼接girlhs.add(familyNameList.get(0)+girlNameList.get(0));}System.out.println(girlhs);//3.调试成名字-男-年龄 添加到集合当中ArrayList<String> list = new ArrayList<>();Random r = new Random();//想要[18~27] - 18 = 0~9//例如:尾部+1 => 9+1=10for (String boyName : boyhs) {int age = r.nextInt(10) + 18;//加上18后就到了18~27的范围选取list.add(boyName+"-男"+"-"+age);}//4.调试成名字-女-年龄 添加到集合当中for (String girlName : boyhs) {int age = r.nextInt(8) + 18;//加上18后就到了16~25的范围选取list.add(girlName+"-女"+"-"+age);}return list;}/*作用:根据正则表达式获取字符串中的数据参数一:代表完整的字符串参数二:正则表达式参数三:代表正则表达式的第几组,赵钱孙李。显然要获取在标点符号前面返回值:真正想要的值*/private static ArrayList<String> getDate(String str, String regex,int index) {//1.创建集合存放数据ArrayList<String> list = new ArrayList<>();//2.按照正则表达式的规则获取数据 Pattern.compilePattern pattern = Pattern.compile(regex);//按照pattern的规则,将网址的前端代码字符串转换为姓氏Matcher matcher = pattern.matcher(str);//文本匹配器while(matcher.find()){//用find查找想要的文本元素是否被找到了list.add(matcher.group(index));//将文本匹配器获取正则第index括号里面的东西}return list;}/*作用:从网络中爬取数据,把数据拼接成字符串返回形参:网址返回值:爬取到所有的数据*/public static String webCrawler(String net) throws IOException {//1.定义StringBuilder拼接爬取到的数据StringBuilder sb = new StringBuilder();//2.创建URL对象,这个就表示网址//统一资源标识符(Uniform Resource Identifier ,URL)是采用一种特定语法标识一个资源的字符串。//所标识的资源可能是服务器上的一个文件。Java的URL网络类可以让你通过URL去练级网络服务器并获取资源URL url = new URL(net);//3.链接上这个网址//细节:必须要保证网络畅通,而且这个网址可以链接上URLConnection conn = url.openConnection();//4.读取数据,怎么读取?//一般通过IO流读取,获取输入流读到,但是InputStream是字节流,网站有中文该怎么办?//所以要转换为字符流InputStreamReaderInputStreamReader isr = new InputStreamReader(conn.getInputStream());int b;while((b=isr.read())!=-1){sb.append((char) b);//一定要强转,不然获取到的是数字}isr.close();//5.直接返回StringBuilder的字符串形式return sb.toString();}
}
6.2 利用糊涂包生成假数据
Hutool包爬取
public class Test1 {public static void main(String[] args) throws IOException {//1.定义变量记录网址String boyNameNet = "http://www.haoming8.cn/baobao/10881.html";String girlNameNet = "http://www.haoming8.cn/baobao/7641.html";String familyNameNet = "https://hanyu.baidu.com/shici/detail?pid=0b2f26d4c0ddb3ee693fdb1137ee1b0d";//爬取利用糊涂包直接获取网址的前端代码String familyNameStr = HttpUtil.get(familyNameNet);String boyNameStr = HttpUtil.get(boyNameNet);String girlNameStr = HttpUtil.get(girlNameNet);//3.利用正则表达式获取数据,把其中符合要求的数据获取出来// ArrayList<String> familyNameTempList = getDate(familyNameStr,"(.{4})(,|。)",1);// ArrayList<String> boyNameTempList = getDate(boyNameStr,"([\\u4E00-\\u9FA5]{2})(、|。)",1);//如果是(..)(、|。),这样获取的名字里面就有数字标点等组合,而要求的是只要有汉字// ArrayList<String> girlNameTempList = getDate(girlNameStr,"((.. ){4}(..))",0);//对照着写,可以写ArrayList集合接收,只不过该方法ReUtil.findAll返回的是List集合,要进行强转成ArrayList,要么就直接用List接收List<String> familyNameTempList = ReUtil.findAll("(.{4})(,|。)", familyNameStr, 1);List<String> boyNameTempList = ReUtil.findAll("([\\u4E00-\\u9FA5]{2})(、|。)", boyNameStr, 1);List<String> girlNameTempList = ReUtil.findAll("((.. ){4}(..))", girlNameStr, 0);System.out.println(familyNameTempList);System.out.println(boyNameTempList);System.out.println(girlNameTempList);//4.处理数据和生成数据还是要自己写的ArrayList<String> familyNameList = new ArrayList<>();for (String str : familyNameTempList) {//此时str为 赵钱孙李 周吴郑王//任务就是将每个姓氏都拆开for (int i = 0; i < str.length(); i++) {char c = str.charAt(i);familyNameList.add(c+"");//以字符串的形式放进集合}}System.out.println(familyNameList);//男生名字//处理方案:去除其中的重复元素ArrayList<String> boyNameList = new ArrayList<>();for (String str : boyNameTempList) {if(!boyNameList.contains(str)){//如果男生名字集合里面没有相同的内容才指向添加boyNameList.add(str);}}System.out.println(boyNameList);//女生的名字//处理方案:把里面读到每一个元素用空格进行切割,就可以得到ArrayList<String> girlNameList = new ArrayList<>();for (String str : girlNameTempList) {String[] arr = str.split(" ");//获取到分割后的女孩名字字符串数组后,再将器每个元素循环出来放到集合当中for (int i = 0; i < arr.length; i++) {girlNameList.add(arr[i]);}}System.out.println(girlNameList);//5.生成数据//姓名(唯一)-性别-年龄ArrayList<String> list = getInfos(familyNameList, boyNameList, girlNameList, 10, 10);Collections.shuffle(list);System.out.println(list);//6.写出数据采用hutool包//细节://糊涂包的相对路径,不是相当于当前项目而言的,而是相对class文件而言的FileUtil.writeLines(list,"names.txt","UTF-8");}/*作用:获取男生和女生的信息:比如张三-男-24参数一:装着姓氏的集合参数二:装着男生名字的集合参数三:装着女生名字的集合参数四:表示男生的个数参数五:表示女生的个数*/public static ArrayList<String> getInfos(ArrayList<String> familyNameList,ArrayList<String> boyNameList,ArrayList<String> girlNameList,int boyCount,int girlCount){//1.生成男生不重复的姓名HashSet<String> boyhs = new HashSet<>();while(true){if(boyhs.size()==boyCount){//男生名字足够了break;}//随机通过Collections.shuffle打乱集合的方式进行Collections.shuffle(familyNameList);Collections.shuffle(boyNameList);//拼接boyhs.add(familyNameList.get(0)+boyNameList.get(0));}System.out.println(boyhs);//2.生成女生不重复的姓名HashSet<String> girlhs = new HashSet<>();while(true){if(girlhs.size()==girlCount){//男生名字足够了break;}//随机通过Collections.shuffle打乱集合的方式进行Collections.shuffle(familyNameList);Collections.shuffle(girlNameList);//拼接girlhs.add(familyNameList.get(0)+girlNameList.get(0));}System.out.println(girlhs);//3.调试成名字-男-年龄 添加到集合当中ArrayList<String> list = new ArrayList<>();Random r = new Random();//想要[18~27] - 18 = 0~9//例如:尾部+1 => 9+1=10for (String boyName : boyhs) {int age = r.nextInt(10) + 18;//加上18后就到了18~27的范围选取list.add(boyName+"-男"+age);}//4.调试成名字-女-年龄 添加到集合当中for (String girlName : boyhs) {int age = r.nextInt(8) + 18;//加上18后就到了16~25的范围选取list.add(girlName+"-女"+age);}return list;}}
6.3 带权重的随机数
了解一下微服务:
用户在上网的时候不知道该访问哪台服务器,所以在中间就会有一个服务网关,它会根据算法来计算哪个服务器人多了哪个人少了,然后在调整服务器的权重,让用户到人少的地方
梳理过程:
那么这10%的概率怎么计算呢?
之前学的是往集合里面添加七个1、三个0,再根据1和0的占比情况来决定概率
但是这样做只适合数据比较少的情况(男生和女生这两个种类),一旦数据比较多,种类一旦多起来了,就不能再这样子表示了
new方案:
去求出每个数据中的权重占比
Student类:
public class Student {private String name;private String gender;private int age;private double weight;public Student() {}public Student(String name, String gender, int age, double weight) {this.name = name;this.gender = gender;this.age = age;this.weight = weight;}/*** 获取* @return name*/public String getName() {return name;}/*** 设置* @param name*/public void setName(String name) {this.name = name;}/*** 获取* @return gender*/public String getGender() {return gender;}/*** 设置* @param gender*/public void setGender(String gender) {this.gender = gender;}/*** 获取* @return age*/public int getAge() {return age;}/*** 设置* @param age*/public void setAge(int age) {this.age = age;}/*** 获取* @return weight*/public double getWeight() {return weight;}/*** 设置* @param weight*/public void setWeight(double weight) {this.weight = weight;}public String toString() {return name+"-"+gender+"-"+age+"-"+weight;}
}测试类:
public class Test1 {public static void main(String[] args) throws IOException {//1.把文件中所有的学生信息读取到内存当中,并封装一个Student对象再放到集合里,才方便统一进行管理ArrayList<Student> StudnetList = new ArrayList<>();BufferedReader br = new BufferedReader(new FileReader("javaprogram1\\names.txt"));String line;while((line=br.readLine())!=null){String[] arr = line.split("-");Student stu = new Student(arr[0], arr[1], Integer.parseInt(arr[2]), Double.parseDouble(arr[3]));StudnetList.add(stu);}br.close();System.out.println(StudnetList);//2.计算总权重double weight = 0;for (Student stu : StudnetList) {weight = weight + stu.getWeight();}System.out.println(weight);//3.计算每个人的权重占比double[] arr = new double[StudnetList.size()];int index = 0;for (Student stu : StudnetList) {arr[index]=stu.getWeight()/weight;index++;}System.out.println(Arrays.toString(arr));//蓝色权重占比部分完成//4.计算权重占比范围for (int i = 1; i < arr.length; i++) {arr[i] = arr[i]+arr[i-1];}System.out.println(Arrays.toString(arr));//5.随机抽取//获取一个0 ~ 1.0之间的随机数double Rnumber = Math.random();//小数参与的计算是不精确的,当时不影响范围的选取System.out.println(Rnumber);//接下来判断number在arr中的位置,就不要一个一个遍历了,不够便捷//二分查找法//Arrays.binarySearch方法返回:-插入点-1 ,这个插入点表示如果在数组当中就应该是什么位置//怎么获取number这个数据在数组当中的插入点位置?//获取插入点 = -方法返回值-1int result = -Arrays.binarySearch(arr,Rnumber)-1;Student stu = StudnetList.get(result);System.out.println(stu);//6.被点到了,概率降低,修改当前学生的权重double w = stu.getWeight() / 2;stu.setWeight(w);//7.把集合中的数据再次写到文件中BufferedWriter bw = new BufferedWriter(new FileWriter("javaprogram1\\names.txt"));for (Student student : StudnetList) {bw.write(student.toString());bw.newLine();}bw.close();}
}
6.4 登录注册
public static void main(String[] args) throws IOException {//1.读取正确的用户名和密码BufferedReader br = new BufferedReader(new FileReader("javaprogram1\\a.txt"));String line = br.readLine();//一行信息br.close();String[] userInfo = line.split("&");System.out.println(Arrays.toString(userInfo));String[] userList = userInfo[0].split("=");String[] passwordList = userInfo[1].split("=");System.out.println(Arrays.toString(userList));System.out.println(Arrays.toString(passwordList));String rightUsername = userList[1];String rightPassword = passwordList[1];//2.键盘录入输入Scanner sc = new Scanner(System.in);System.out.println("请输入用户名:");String username = sc.nextLine();System.out.println("请输入密码:");String password = sc.nextLine();//3.判断if(rightUsername.equals(username)&&rightPassword.equals(password)){//不能用username == rightUsername && password==rightPassword,因为原本不是统一在栈里面的真实数据System.out.println("登录成功");}else {System.out.println("登录失败");}}
2.
修改以下代码:
public class Main {public static void main(String[] args) throws IOException {//1.读取正确的用户名和密码BufferedReader br = new BufferedReader(new FileReader("javaprogram1\\a.txt"));String line = br.readLine();//一行信息br.close();String[] userInfo = line.split("&");System.out.println(Arrays.toString(userInfo));String[] userList = userInfo[0].split("=");String[] passwordList = userInfo[1].split("=");String[] countList = userInfo[2].split("=");System.out.println(Arrays.toString(userList));System.out.println(Arrays.toString(passwordList));System.out.println(Arrays.toString(countList));String rightUsername = userList[1];String rightPassword = passwordList[1];int count = Integer.parseInt(countList[1]);//2.键盘录入输入Scanner sc = new Scanner(System.in);System.out.println("请输入用户名:");String username = sc.nextLine();System.out.println("请输入密码:");String password = sc.nextLine();//3.判断if(rightUsername.equals(username)&&rightPassword.equals(password)&&count<3){//不能用username == rightUsername && password==rightPassword,因为原本不是统一在栈里面的真实数据System.out.println("登录成功");writeInfo("username"+rightUsername+"&password="+rightPassword+"&count=0");}else {count++;if(count<3){System.out.println("登录失败,还剩下"+(3-count)+"次机会");}else {System.out.println("登录失败,用户账号被锁定");}writeInfo("username"+rightUsername+"&password="+rightPassword+"&count="+count);}}public static void writeInfo(String content) throws IOException {BufferedWriter bw = new BufferedWriter(new FileWriter("javaprogram1\\a.txt"));bw.write(content);bw.close();}
}
6.5 配置文件
6.5.1 properties配置文件
properties配置文件都是按照键值对的形式存储的
properties不是一个泛型类,那么在添加数据的时候就可以添加任意的数据类型,但是一般只会添加字符串类型的数据
properties作为map集合的基本用法:
public class Main {public static void main(String[] args) {//1.创建Properties prop = new Properties();//2.添加数据prop.put("aaa","111");prop.put("bbb","222");prop.put("ccc","333");prop.put("ddd","444");//3.遍历集合Set<Object> keys = prop.keySet();for (Object key : keys) {Object value = prop.get(key);System.out.println(key+"="+value);}System.out.println("==================");Set<Map.Entry<Object, Object>> entries = prop.entrySet();for (Map.Entry<Object, Object> entry : entries) {Object key = entry.getKey();Object value = entry.getValue();System.out.println(key+"="+value);}}
}
properties与IO流结合的操作:
public static void main(String[] args) throws IOException {//1.创建Properties prop = new Properties();//2.添加数据prop.put("aaa","111");prop.put("bbb","222");prop.put("ccc","333");prop.put("ddd","444");//麻烦的代码/*BufferedWriter bw = new BufferedWriter(new FileWriter("javaprogram1\\a.txt"));Set<Map.Entry<Object, Object>> entries = prop.entrySet();for (Map.Entry<Object, Object> entry : entries) {Object key = entry.getKey();Object value = entry.getValue();bw.write(key+"="+value);bw.newLine();}bw.close();*///使用properties的特有方法FileOutputStream fos = new FileOutputStream("javaprogram1\\a.txt");prop.store(fos,"test");fos.close();//读取本地properties文件里面的数据FileInputStream fis = new FileInputStream("javaprogram1\\a.txt");prop.load(fis);fis.close();System.out.println(prop);}