Java 黑马程序员学习笔记(进阶篇26)
一、Commons-io 工具包
1. FileUtils - 文件操作工具类
(1) 作用:
简化文件 / 目录的创建、复制、移动、删除、读取等操作。
| 方法 | 功能 | 示例 |
|---|---|---|
readFileToString(File file, String encoding) | 读取文件内容为字符串(指定编码) | String content = FileUtils.readFileToString(new File("test.txt"), "UTF-8"); |
writeStringToFile(File file, String data, String encoding) | 写入字符串到文件(覆盖 / 追加) | FileUtils.writeStringToFile(new File("test.txt"), "Hello", "UTF-8"); |
copyFile(File src, File dest) | 复制文件 | FileUtils.copyFile(new File("a.txt"), new File("b.txt")); |
copyDirectory(File srcDir, File destDir) | 复制目录(含子目录和文件) | FileUtils.copyDirectory(new File("dir1"), new File("dir2")); |
deleteDirectory(File dir) | 删除目录(含所有内容) | FileUtils.deleteDirectory(new File("dir")); |
listFiles(File dir, String[] extensions, boolean recursive) | 按后缀过滤文件(递归 / 非递归) | File[] files = FileUtils.listFiles(new File("dir"), new String[]{"txt"}, true); |
(2) 示例:文件复制与内容读取
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;public class CommonsIoDemo {public static void main(String[] args) {File srcFile = new File("D:\\test\\source.txt");File destFile = new File("D:\\test\\dest.txt");try {// 1. 复制文件FileUtils.copyFile(srcFile, destFile);System.out.println("文件复制成功");// 2. 读取文件内容(UTF-8编码)String content = FileUtils.readFileToString(destFile, "UTF-8");System.out.println("文件内容:" + content);// 3. 追加内容到文件FileUtils.writeStringToFile(destFile, "\n追加的内容", "UTF-8", true);System.out.println("内容追加成功");} catch (IOException e) {e.printStackTrace();}}
}二、综合练习
1. 百家姓网页数据爬取与正则分组提取
题目描述:
在名字数据处理场景中,常需要从网页提取特定格式的文本(如百家姓的四字一组格式)。本题要求你基于提供的代码框架,实现一个程序,从指定的百家姓网页中爬取内容,并通过正则表达式的分组功能提取目标数据。
具体需求:
(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)的爬取接口。
(2) 核心功能:
- 完善
webCrawler方法:通过URL和URLConnection建立网络连接,使用InputStreamReader将网页字节流转换为字符流,读取并返回网页的文本内容。 - 完善
getData方法:接收网页源码、正则表达式和分组索引,使用Pattern和Matcher进行正则匹配,提取匹配结果中指定分组的内容,存入ArrayList<String>并返回。 - 在
main方法中,调用上述方法从百家姓网页提取 “四字一组” 的内容(如 “赵钱孙李”“周吴郑王”),正则表达式已指定为(\\W{4})(,|。),需提取第一个分组(索引为 1)的内容,并打印结果。
技术要求:
- 必须使用
java.net包中的URL和URLConnection类实现网络通信。 - 使用
InputStreamReader处理字符流读取,确保网页文本正确解析。 - 理解正则表达式的分组机制:
(\\W{4})为第一个分组(目标提取内容),(,|。)为第二个分组,分组索引从 1 开始。 - 正确处理 IO 异常,确保流资源关闭;集合需准确存储提取的目标数据。
提示:
URL url = new URL(net)用于将 URL 字符串转换为可操作的 URL 对象,是网络连接的入口。InputStreamReader isr = new InputStreamReader(conn.getInputStream())负责将网络字节流转换为字符流,支持文本内容的直接读取。matcher.group(index)用于获取正则匹配中第index个分组的内容,本题需提取第一个分组(四字部分)。
输出要求:
程序运行后,需打印从百家姓网页中提取的所有 “四字一组” 内容(如[赵钱孙李, 周吴郑王, ...])。
package demo3;import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;public class test1 {public static void main(String[] args) throws IOException {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";String familyNameStr = webCrawler(familyNameNet);String boyNameStr = webCrawler(boyNameNet);String girlNameStr = webCrawler(girlNameNet);ArrayList<String> familyNameTempList = getData(familyNameStr,"(\\W{4})(,|。)",1); // 不太理解System.out.println(familyNameTempList);}private static ArrayList<String> getData(String str, String regex,int index) {ArrayList<String> list = new ArrayList<>();Pattern pattern = Pattern.compile(regex);Matcher matcher = pattern.matcher(str);while((matcher.find())) {list.add(matcher.group(index));}return list;}public static String webCrawler(String net) throws IOException {StringBuilder sb = new StringBuilder();URL url = new URL(net); // 不太理解URLConnection conn = url.openConnection();InputStreamReader isr = new InputStreamReader(conn.getInputStream()); // 不太理解int ch;while ((ch = isr.read()) != -1) {sb.append((char) ch);}isr.close();return sb.toString();}
}
关键逻辑 1:正则表达式 (\\W{4})(,|。) 是什么意思?
① (\\W{4}):
\\W是正则表达式的元字符,表示 “非单词字符”(在中文场景下可匹配汉字,因为汉字不属于字母 / 数字 / 下划线)。{4}表示匹配前面的字符(这里是\\W)4 次。- 括号
()表示 “分组”,这部分是第一个分组(分组索引从 1 开始),目标是匹配 “4 个汉字”(比如 “赵钱孙李”“周吴郑王”)。
② (,|。):
|表示 “或”,匹配 “,”(中文逗号)或 “。”(中文句号)。- 括号
()是第二个分组,匹配的是四字后面的标点符号。
关键逻辑 2:index=1 为什么要传 1?
getData 方法中用 matcher.group(index) 获取匹配结果,这里的 index 对应正则表达式中的 “分组索引”:
- 分组索引从 1 开始,
group(1)对应第一个分组(\\W{4})(4 个汉字); group(2)对应第二个分组(,|。)(标点符号);group(0)对应整个匹配的完整字符串(比如 “赵钱孙李,”)。
关键逻辑 3:URL url = new URL(net);
这行代码是将字符串格式的网页地址转换为可操作的 URL 对象,是网络爬虫的 “入口”。
net是传入的网页地址字符串(比如"https://hanyu.baidu.com/..."),但字符串本身无法直接用于网络连接,需要转换为 Java 能识别的URL对象。URL类是 Java 提供的用于处理 “统一资源定位符”(即网页地址)的工具类,它封装了 URL 的解析、协议(http/https)、主机名、端口等信息,后续可以通过这个对象建立网络连接。
关键逻辑 4:InputStreamReader isr = new InputStreamReader(conn.getInputStream());
这行代码是将网络字节流转换为字符流,方便读取网页的文本内容(源码)。
先看
conn.getInputStream():conn是通过url.openConnection()建立的网络连接对象,getInputStream()会从这个连接中获取 “字节输入流”(网页内容在网络中传输时是以字节形式存在的)。再看
InputStreamReader:字节流(InputStream)只能按字节读取数据,而网页源码是文本(比如 HTML 标签、中文文字),需要按 “字符” 处理(比如一个汉字可能占 2 个字节)。InputStreamReader是 “字节流→字符流” 的转换桥梁,它会根据默认编码(或指定编码)把字节转换为字符,方便后续用read()方法按字符读取。
2. 题目:学生体重加权随机抽样与数据更新
题目描述:
在学生数据处理场景中,常需要根据特定权重(如体重)进行随机抽样,并对抽样结果进行数据修改。本题要求你基于提供的代码框架,实现一个程序,从指定文件中读取学生信息,通过加权随机抽样算法选中目标学生,修改其体重后将数据写回文件。
具体需求:
(1) 数据读取目标:从 names.txt 文件中读取学生信息,文件中每行格式为 姓名-性别-年龄-体重(例如:张三-男-20-50.0),预留对男生名字文件(如 boys.txt)和女生名字文件(如 girls.txt)的读取接口。
(2) 核心功能:
- 完善
readStudentData方法:通过BufferedReader和FileReader建立文件连接,读取文件内容并解析为Student对象,存入ArrayList<Student>并返回。 - 完善
weightedRandomSelect方法:接收学生列表,计算所有学生的体重总和,构建体重占比的概率区间数组,生成随机数并通过Arrays.binarySearch确定选中学生的索引,返回该索引。 - 完善
updateAndWriteBack方法:接收学生列表和选中索引,将对应学生的体重减半,使用BufferedWriter将更新后的所有学生信息写回原文件。 - 在
main方法中,调用上述方法从names.txt提取学生数据,通过加权随机抽样选中学生,打印选中结果,修改体重并写回文件。
技术要求:
- 必须使用
java.io包中的BufferedReader和BufferedWriter类实现文件读写。 - 使用
ArrayList存储Student对象,确保数据的动态管理。 - 理解加权随机抽样的核心逻辑:体重总和计算、概率区间构建、
Arrays.binarySearch的返回值处理。 - 正确处理 IO 异常,确保流资源关闭;数据解析时需处理字符串分割和类型转换。
提示:
BufferedReader br = new BufferedReader(new FileReader("names.txt"))用于建立文件读取连接,是文件数据读取的入口。String[] arr = line.split("-")负责将每行文件内容按-分割为学生信息数组,需注意数组长度校验。Arrays.binarySearch(arr, number)的返回值处理:未找到时返回-插入点-1,通过公式-返回值-1可获取正确索引。
package demo4;import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;public class test2 {public static void main(String[] args) throws IOException {ArrayList<Student> list = new ArrayList<>();BufferedReader br = new BufferedReader(new FileReader("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]));list.add(stu);}br.close();double weight = 0;for (Student stu : list) {weight = weight + stu.getWeight();}double[] arr = new double[list.size()]; // 不太理解int index = 0;for (Student stu : list) {arr[index] = stu.getWeight() / weight;index++;}for (int i = 1; i < arr.length; i++) {arr[i] = arr[i] + arr[i - 1]; // 不太理解}double number = Math.random();int result = -Arrays.binarySearch(arr, number) - 1; // 不太理解Student stu = list.get(result);System.out.println(stu);double w = stu.getWeight() / 2;stu.setWeight(w);BufferedWriter bw = new BufferedWriter(new FileWriter("names.txt"));for (Student s : list) {bw.write(s.toString());bw.newLine();}bw.close();}
}
关键逻辑 1:double[] arr = new double[list.size()];
- 作用:创建一个和学生列表
list长度相同的double类型数组。 - 用途:这个数组是一个临时容器,用来存储每个学生的 “相对权重” 或者说 “概率密度”。
关键逻辑 2:for (int i = 1; i < arr.length; i++) { arr[i] = arr[i] + arr[i - 1]; }
(1) 作用:将这个数组从一个概率数组转换成一个前缀和数组(也叫累积分布函数 CDF)。
(2) 这是最关键的一步,我们来举例说明:
假设我们有 3 个学生,他们的体重分别是:
- 学生 A:体重 = 100.0
- 学生 B:体重 = 200.0
- 学生 C:体重 = 300.0
- 总重量
weight= 600.0
第一步:在这个循环执行之前,arr 数组存储的是每个学生体重占总重量的比例(概率):
arr[0](学生 A 的概率) =100.0 / 600.0≈0.1667(16.67%)arr[1](学生 B 的概率) =200.0 / 600.0≈0.3333(33.33%)arr[2](学生 C 的概率) =300.0 / 600.0≈0.5(50%)- 此时数组是:
[0.1667, 0.3333, 0.5]
第二步:执行这个 for 循环:
① 当 i=1 时:
arr[1] = arr[1] + arr[0]arr[1] = 0.3333 + 0.1667 = 0.5
② 当 i=2 时:
arr[2] = arr[2] + arr[1]arr[2] = 0.5 + 0.5 = 1.0
③ 循环执行之后,arr 数组变成了前缀和数组:
arr[0]≈0.1667arr[1]≈0.5arr[2]=1.0- 此时数组是:
[0.1667, 0.5, 1.0]
(3) 为什么要这么做?
[0, 0.1667)这个区间对应学生 A。[0.1667, 0.5)这个区间对应学生 B。[0.5, 1.0]这个区间对应学生 C。
