九, IO流
1. 初识IO流
-
IO流: 存储和读取数据的解决方案, I: Input, O: Output, 流: 像水流一样传输数据
-
IO流的作用: 用于读写数据(本地文件, 网络)
-
IO流按照流向可以分为两类:
(1) 输出流: 程序->文件 (2) 输入流: 文件->程序 -
IO流按照操作文件的类型可以分为两类:
(1) 字节流: 可以操作所有类型的文件 (2) 字符流: 只能操作纯文本文件(用Windows系统自带的记事本打开并且能读懂的文件, .txt, .xml, .md, .lrc)



2. 字节输出流
2.1 基本用法
- FileOutputStream: 操作本地文件的字节输出流, 可以把程序中的数据写到本地文件中
// 写出一段文字到本地文件中(暂时不写中文)
// 1. 创建字节输出流对象
/*
细节一: 参数是字符串表示的路径或者是File对象都是可以的
细节二: 如果写出的文件不存在, 会自动创建, 但是要保证父级文件夹存在
细节三: 如果文件已经存在, 会覆盖
*/
FileOutputStream fos = new FileOutputStream("D:\\Java\\a.txt");
// 2. 写出数据
/*
细节: write方法的参数是整数, 但是实际上写到本地文件中的是整数在ASCII表中对应的字符
97 对应的字符是a
*/
fos.write(97);
// 3. 释放资源: 每次使用完流之后都要释放
fos.close();

2.2 换行写
/*
换行写: 再次写出一个换行符即可
Windows: \r\n
Linux: \n
Mac: \r
细节:
在Windows操作系统, java对回车换行进行了优化
虽然完整的是\r\n, 但是可以只写其中一个, java就能识别, 因为java在底层会补全
建议: 不要省略. 全写了
*/
FileOutputStream fos = new FileOutputStream("D:\\java\\awei.txt");
String str = "asndbanf";
fos.write(str.getBytes());
String wrap = "\r\n";
fos.write(wrap.getBytes());
String str2 = "asndbanf";
fos.write(str2.getBytes());
fos.close();
2.3 续写
/*
续写:
如果想要续写, 打开续写开关即可
开关位置: 创建对象的第二个参数
默认false: 表示管必续写, 此时创建对象会清空文件
手动传递true: 表示打开续写, 此时创建对象不会清空文件
*/
FileOutputStream fos = new FileOutputStream("D:\\java\\awei.txt", true);
String str = "asndbanf";
fos.write(str.getBytes());
String wrap = "\r\n";
fos.write(wrap.getBytes());
String str2 = "asndbanf";
fos.write(str2.getBytes());
fos.close();
3. 字节输入流
3.1 字节输入流基本用法
- FileInputStream: 操作本地文件中的字节输入流, 可以把本地文件中的数据读取到程序中来
// 文件里暂时不写中文
/*
1. 创建字节输入流对象:
细节1: 如果文件不存在, 就直接报错
2. 读取数据:
细节1: 一次读一个字节, 读出来的是在ASCII码对应的int值
细节2: 如果文件已经读完, 就返回-1
3. 释放资源: 每次使用完必须释放资源
*/
FileInputStream fis = new FileInputStream("D:\\Java\\awei.txt");
int b;
while((b = fis.read()) != -1){
System.out.print((char)b);
}
fis.close();
3.2 文件拷贝(使用FileInputStream一次读取多个字节)

- public int read(byte[] buffer) 一次读取一个字节数组数据


FileInputStream fis = new FileInputStream("D:\\Java\\awei.txt");
FileOutputStream fos = new FileOutputStream("D:\\Java\\a.txt");
byte[] bytes = new byte[1024];
int len;
while((len = fis.read(bytes)) != -1){
fos.write(bytes, 0, len);
}
fos.close();
fis.close();
4. 字符集(读中文)
4.1 ASCII和GBK
-
计算机最小的存储单元是一个字节
-
ASCII字符集中, 一个英文占一个字节, 二进制第一位是0
-
简体中文版Windows, 默认使用GBK字符集, GBK字符集完全兼容ASCII字符集
-
GBK: 规则1: 汉字用两个字节存储, 规则2: 高位字节二进制一定以1开头, 转成十进制之后是负数, 是为了和英文区分开



4.2 Unicode(万国码)
-
UTF: Unicode Transfer Format

-
UTF-8编码规则: 使用1-4个字节进行保存(ASCII:1个字节表示, 中文:3个字节表示)
-
UTF-8: 不是字符集, 是Unicode字符集中的一种编码方式
4.3 乱码
-
出现的原因:
- 读取数据时未读完整个汉字
- 编码和解码方式不统一
-
如何不产生乱码?
- 不要用字节流读取文本文件
- 编码解码时使用同一个码表, 同一个编码方式
4.4 编码和解码

/*
Java中编码的方式:
public byte[] getBytes() 使用默认方式进行编码
public byte[] getBytes(String charsetName) 使用指定的编码进行编码
Java中解码的方式:
String (byte[] bytes) 使用默认方式进行解码
String (byte[] bytes, String charsetName) 使用指定的编码进行解码
*/
// 1. 编码
String str = "ai你哟";
byte[] bytes1 = str.getBytes();
System.out.println(Arrays.toString(bytes1));
byte[] bytes2 = str.getBytes("GBK");
System.out.println(Arrays.toString(bytes2));
// 2. 解码
String str1 = new String(bytes1);
System.out.println(str1);
String str2 = new String(bytes1, "GBK");
System.out.println(str2);
5. 字符流
5.1 初识字符流
- 字符流的底层其实就是字节流, 字符流 = 字节流 + 字符集
- 字符流的特点:
- 输入流: 一次读一个字节, 遇到中文时, 一次读多个字节
- 输出流: 底层会把数据按照指定的编码方式进行编码, 变成字节再写到文件中
5.2 读取数据
/*
1. 创建对象
public FileReader(File file) 创建字符输入流关联本地文件
public FileReader(String fileName) 创建字符输入流关联本地文件
2. 读取数据
public int read() 读取数据, 读到末尾返回-1
public int read(char[] buffer) 读取duoge数据, 读到末尾返回-1
3. 关闭流
public void close() 释放资源/关流
*/
FileReader fr = new FileReader("D:\\Java\\awei.txt");
// 字符流的底层也是字节流, 默认也是一个一个字节读取
// 遇到中文读取多个, GBK一次读取两个, Unicode一次读取三个字节
// int ch;
// while((ch = fr.read()) != -1){
// // 读取之后, 方法的底层还会解码并转成十进制, 所以我们强制转换为char类型
// System.out.print((char)ch);
// }
int len;
char[] chars = new char[1024];
while((len = fr.read(chars)) != -1){
//空参的read + 强制转换类型
System.out.print(new String(chars, 0, len));
}
fr.close();
5.3 写数据


FileWriter fw = new FileWriter("D:\\Java\\awei.txt");
// fw.write(25105);
// fw.write("你好?");
char[] chs = {'a', 'b', 'c'};
// fw.write(chs);
fw.write(chs, 1, 2);
fw.close();
5.4 字符流原理解析
-
字节流没有缓冲区
字符输入流


-
字符输出流
-
何时写入目的地

-

6. 缓冲流

6.1 字节缓冲流
- 原理: 底层自带了长度为8192的字节数组(8KB)缓冲区提高性能

/*
利用字节缓冲流拷贝文件
字节缓冲输入流的构造方法:
public BufferedInputStream(InputStream is)
字节缓冲输出流的构造方法:
public BufferedOutputStream(OutputStream os)
*/
// 1. 创建字节缓冲流对象
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\Java\\awei.txt"));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\Java\\awei_copy2.txt"));
// 2. 读取数据并写入
// int b;
int len;
byte[] bytes = new byte[1024];
while((len = bis.read(bytes)) != -1){
bos.write(bytes, 0, len);
}
// 3. 释放资源
bos.close();
bis.close();
6.2 字符缓冲流
- 字符基本流其实已经自带了缓冲区(8192的字符数组, java当中, 一个字符是2B, 所有是16KB), 所以字符缓冲流的效率并没有很大的提升, 但还是需要学习, 因为有很好用的方法


/*
字符缓冲输入流的:
构造方法:
public BufferedReader(Reader r)
特有方法:
public String readLine() 读取一整行, 但是不会把回车换行符号读到内存当中
字符缓冲输出流的:
构造方法:
public BufferedWriter(Writer w)
特有方法:
public void newLine() 跨平台的换行
*/
// 1. 创建字符缓冲流对象
BufferedReader br = new BufferedReader(new FileReader("D:\\Java\\awei.txt"));
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\Java\\awei_copy.txt"));
// 2. 读取数据, 写出对象
bw.write("hello world!");
// bw.write("\r\n"); // 平台不一样可能导致不能换行
bw.newLine();
bw.write("hello world!");
bw.newLine();
String line;
// System.out.println(line);
while((line = br.readLine()) != null){
System.out.println(line);
}
// 3. 释放资源
bw.close();
br.close();
7. 转换流
-
字节流没有读取一整行的方法
-
转换流是字符流和字节流之间的桥梁, 作用在于
- 指定字符集读写数据(JDK11后淘汰)
- 字节流想要使用字符流中的方法
-
字符转换输入流: InputStreamReader, 字符转换输出流: OutputStreamWriter
/*
将本地文件中的GBK文件, 转换成UTF-8
*/
// 1. JDK11以前的方案
// InputStreamReader isr = new InputStreamReader(new FileInputStream("hello.txt"), "GBK");
// OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("hello1.txt"), "UTF-8");
//
// int b;
// while((b = isr.read()) != -1){
// osw.write(b);
// }
// osw.close();
// isr.close();
// 2. 现在的替代方案
FileReader fr = new FileReader("hello.txt", Charset.forName("GBK"));
FileWriter fw = new FileWriter("hello1.txt", Charset.forName("UTF-8"));
int b;
while((b = fr.read()) != -1){
fw.write(b);
}
fw.close();
fr.close();

8. 序列化流和反序列化流(字节流)

8.1 序列化流(对象操作输出流)
- 序列化流: 可以把Java中的对象写到文件中

/*
需求: 利用序列化流/对象操作输出流, 把一个对象写到本地文件中
要序列化的类需要实现Serializable接口, 否则会报NotSerializableException
接口中其里面没有抽象方法, 是一个标记型接口, 一旦实现了这个接口, 该类对象就可以被序列化(类似于合格证)
构造方法:
public ObjectOutputStream(OutputStream out) throws IOException 把基本流变成高级流
成员方法:
public final void writeObject(Object obj) throws IOException
*/
// 1. 创建对象
Dog dog = new Dog("旺财", 10);
// 2. 创建序列化流的对象/对象操作输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Java\\dog.txt"));
// 3. 写出数据
oos.writeObject(dog);
// 4. 释放资源
oos.close();
- 如果不想把属性序列化到本地文件中, 可以使用transient(瞬态关键字), 该关键字标记的成员变量不参与序列化过程
- 序列化流写到文件中的数据不能修改, 一旦修改, 就不能读取回来
8.2 反序列化流(对象操作输入流)
- 反序列化流: 可以把序列化到本地文件中的对象, 读取到程序中来

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Java\\dog.txt"));
Object o = ois.readObject();
System.out.println(o);
ois.close();
-
序列化对象后, 修改了Javabean类, 再次反序列化, 会出问题, 会抛出InvalidclassException异常
解决方案:给Javabean类添加serialVersionUID(序列号、版本号)

-
使用IDEA中的自动生成UID,设置中调整



9. 打印流
- 打印流不能操作数据源, 只能操作目的地(不能读, 只能写)

9.1 字节打印流
- 字节打印流: 默认自动刷新, 特有的println自动换行



9.2 字符打印流
- 字符流底层有缓冲区, 想要自动刷新需要开启, 特有的println自动换行



10. 压缩流和解压缩流

10.1 解压缩流
public static void main(String[] args) throws ParseException, IOException, ClassNotFoundException {
// 1. 创建一个File表示要解压的压缩包
File src = new File("D:\\Java\\zip.zip");
// 2. 创建一个File表示要解压到的目录
File dest = new File("D:\\Java\\");
// 调用方法
unzip(src, dest);
}
// 定义一个方法来解压
public static void unzip(File src, File dest) throws IOException {
// 解压的本质: 把压缩包里面的每一个文件夹或者文件读取出来, 按照层级拷贝到目的地当中
// 创建一个解压缩流来读取压缩包中的数据
ZipInputStream zis = new ZipInputStream(new FileInputStream(src));
// 要先获取到压缩包里面的每一个ZipEntry对象, 才能读取压缩包里面的内容
// for(int i = 0; i < 100; ++i){
// System.out.println(zis.getNextEntry());
// }
// 获取压缩包里面的下一个ZipEntry对象, 表示当前在压缩包中获取到的文件或者文件夹
ZipEntry entry;
while((entry = zis.getNextEntry()) != null){
// System.out.println(entry);
// 文件夹: 需要在目的地dest创建一个同样的文件夹
if(entry.isDirectory()){
File file = new File(dest, entry.toString());
file.mkdirs();
}else{
// 文件: 需要读取压缩包中的文件, 并把它存取到目的地dest文件夹中(按照层级目录进行存放)
FileOutputStream fos = new FileOutputStream(new File(dest, entry.toString()));
byte[] buf = new byte[1024];
int len;
while((len = zis.read(buf)) != -1){
fos.write(buf, 0, len);
}
fos.close();
zis.closeEntry();
}
}
zis.close();
}
10.2 压缩流
- 压缩的本质: 把每一个(文件/文件夹)看成ZipEntry对象放到压缩包中


11. 常用工具包
11.1 Commons-io
- Commons-io: 是apache开源基金组织提供的一组有关IO操作的工具包
- Commons-io使用步骤:
- (1) 在项目中创建一个文件夹lib
- (2) 将jar包复制粘贴到lib文件夹
- (3) 右键点击jar包,选择Add as Library -> 点击OK
- (4) 在类中导包使用


Commons-io下载
11.2 Hutool工具包

12. 网络爬虫
12.1 制造假数据(姓名)
/*
制造假数据:
获取姓氏: https://hanyu.baidu.com/shici/detail?pid=0b2f26d4c0ddb3ee693fdb1137ee1b0d&from=kg0
获取男生名字: http://www.haoming8.cn/baobao/10881.html
获取女生名字: http://www.haoming8.cn/baobao/7641.html
*/
// 1. 定义变量记录网址
String familyNameNet = "https://hanyu.baidu.com/shici/detail?pid=0b2f26d4c0ddb3ee693fdb1137ee1b0d&from=kg0";
String boyNameNet = "http://www.haoming8.cn/baobao/10881.html";
String girlNameNet = "http://www.haoming8.cn/baobao/7641.html";
// 2. 爬取数据, 把网址上所有的数据拼接成一个字符串
String s1 = webCrawler(familyNameNet);
String s2 = webCrawler(boyNameNet);
String s3 = webCrawler(girlNameNet);
// System.out.println(s2);
// 3. 通过正则表达式, 把其中符合要求的数据获取出来
// 0表示获取所有, 1表示第一组
ArrayList<String> familyNameList = getData(s1, "([^\\w]{4})(,|。)", 1);
ArrayList<String> boyNameList = getData(s2, "([\\u4E00-\\u9FA5]{2})(、|。)", 1);
ArrayList<String> girlNameList = getData(s3, "(.. ){4}..", 0);
// System.out.println(familyNameList);
// System.out.println(boyNameList);
// System.out.println(girlNameList);
// 4. 处理数据
ArrayList<String> familyNameList1 = new ArrayList<>();
for(String s : familyNameList){
for(int i = 0; i < s.length(); ++i){
familyNameList1.add(s.charAt(i) + "");
}
}
// System.out.println(familyNameList1);
ArrayList<String> boyNameList1 = new ArrayList<>();
for(String s : boyNameList){
if(boyNameList1.contains(s)) continue;
boyNameList1.add(s);
}
// System.out.println(boyNameList1);
ArrayList<String> girlNameList1 = new ArrayList<>();
for(String s : girlNameList){
String[] arr = s.split(" ");
for(int i = 0; i < arr.length; ++i){
girlNameList1.add(arr[i]);
}
}
// System.out.println(girlNameList1);
// 5. 生成姓名(唯一) - 性别 - 年龄数据
ArrayList<String> infos = getInfos(familyNameList1, boyNameList1, girlNameList1, 70, 50);
System.out.println(infos);
// 6. 保存数据
BufferedWriter bw = new BufferedWriter(new FileWriter("D:\\Java\\fakeData.txt"));
for(String info : infos){
bw.write(info);
bw.newLine();
}
bw.close();
}
// 获取男生和女生的信息
/*
参数一: 装着姓氏的集合
参数二: 装着男生名字的集合
参数三: 装着女生名字的集合
参数四: 男生的个数
参数五: 女生的个数
*/
private static ArrayList<String> getInfos(ArrayList<String> familyNameList1, ArrayList<String> boyNameList1, ArrayList<String> girlNameList1, int boyCount, int girlCount) {
// 1. 生成男生不重复的名字
HashSet<String> boys = new HashSet<>();
while(true){
if(boyCount == boys.size()) break;
// 随机
Collections.shuffle(familyNameList1);
Collections.shuffle(boyNameList1);
boys.add(familyNameList1.get(0) + boyNameList1.get(0));
}
// 2. 生成女生不重复的名字
HashSet<String> girls = new HashSet<>();
while(true) {
if (girlCount == girls.size()) break;
// 随机
Collections.shuffle(familyNameList1);
Collections.shuffle(girlNameList1);
girls.add(familyNameList1.get(0) + girlNameList1.get(0));
}
// System.out.println(girls);
// 3. 张三-男-23
Random r = new Random();
ArrayList<String> res = new ArrayList<>();
for(String boy : boys){
int age = r.nextInt(9) + 18;
res.add(boy + "-男-" + age);
}
for(String girl : girls){
int age = r.nextInt(9) + 18;
res.add(girl + "-女-" + age);
}
return res;
}
private static ArrayList<String> getData(String s1, String regex, int index) {
ArrayList<String> list = new ArrayList<>();
Pattern p = Pattern.compile(regex);
// 按照pattern的规则, 到s1中去获取数据
Matcher matcher = p.matcher(s1);
while(matcher.find()){
String group = matcher.group(index);
list.add(group);
// System.out.println(group);
}
return list;
}
public static String webCrawler(String net) throws IOException {
// 1. 定义StringBuilder拼接爬取到的数据
StringBuilder sb = new StringBuilder();
// 2. 创建一个URL对象
URL url = new URL( net);
// 3. 连接上该网络
URLConnection urlConnection = url.openConnection();
// 4. 读取数据
// 有中文要转变成字符流
InputStreamReader isr = new InputStreamReader(urlConnection.getInputStream());
int ch;
while((ch=isr.read()) != -1){
sb.append((char)ch);
}
// 5. 释放资源
isr.close();
// 6. 把读取到的数据返回
return sb.toString();
}
- 利用糊涂包生成假数据

浙公网安备 33010602011771号