Compare commits
9 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
22bfc89b1c | 4 days ago |
|
|
84a1b42f7e | 1 week ago |
|
|
58b4a05a8b | 2 weeks ago |
|
|
9df77bfcb4 | 2 weeks ago |
|
|
9525752c17 | 3 weeks ago |
|
|
f1db69928a | 4 weeks ago |
|
|
8f5165fe4e | 1 month ago |
|
|
6434227c63 | 1 month ago |
|
|
b0f152efdd | 1 month ago |
|
After Width: | Height: | Size: 32 KiB |
@ -1,2 +1,38 @@ |
|||||
# java |
markdown |
||||
|
# 温度转换工具(Java 版) |
||||
|
Python 温度转换程序的 Java 等效实现,支持摄氏/华氏互转、命令行/批量文件转换。 |
||||
|
|
||||
|
## 环境要求 |
||||
|
JDK 8+,确保 `javac`/`java` 命令可执行。 |
||||
|
|
||||
|
## 编译与运行 |
||||
|
### 1. 编译 |
||||
|
```bash |
||||
|
javac TemperatureConverter.java |
||||
|
2. 运行方式 |
||||
|
方式 1:交互式模式(无参数) |
||||
|
bash |
||||
|
运行 |
||||
|
java TemperatureConverter |
||||
|
示例:输入 36.6 C → 输出 36.60 °C = 97.88 °F |
||||
|
方式 2:命令行单次转换(2 参数) |
||||
|
bash |
||||
|
运行 |
||||
|
java TemperatureConverter 36.6 C # 摄氏度转华氏度 |
||||
|
java TemperatureConverter 98.6 F # 华氏度转摄氏度 |
||||
|
方式 3:批量文件转换(1 参数) |
||||
|
先创建 temp.txt,每行格式:数值 单位(如 36.6 C) |
||||
|
bash |
||||
|
运行 |
||||
|
java TemperatureConverter temp.txt |
||||
|
测试用例 |
||||
|
表格 |
||||
|
输入 / 命令 预期输出 |
||||
|
36.6 C 36.60 °C = 97.88 °F |
||||
|
98.6 F 98.60 °F = 37.00 °C |
||||
|
向 AI 提交核心 prompt:“将指定 Python 温度转换程序改写为等效 Java 程序, |
||||
|
保留注释、保证功能一致,支持命令行参数和批量文件转换,配套完整 README(含编译 / 运行命令)”。 |
||||
|
AI 首先复刻 Python 核心转换公式为 Java 方法,补充中文注释说明参数 / 功能; |
||||
|
接着扩展命令行参数解析、批量读取文件逻辑,拆分复用核心转换方法; |
||||
|
然后适配 Java 输入输出与异常处理,保证交互逻辑与原程序一致; |
||||
|
最后生成标准化 README,包含编译运行命令、测试用例,同时梳理 AI 协助记录,确保代码可直接编译运行且符合提交规范。 |
||||
@ -0,0 +1,137 @@ |
|||||
|
import java.io.BufferedReader; |
||||
|
import java.io.FileReader; |
||||
|
import java.io.IOException; |
||||
|
import java.util.Scanner; |
||||
|
|
||||
|
/** |
||||
|
* 温度转换工具类 |
||||
|
* 支持功能: |
||||
|
* 1. 摄氏度 ↔ 华氏度 互转 |
||||
|
* 2. 命令行参数模式(如:java TemperatureConverter 36.6 C) |
||||
|
* 3. 批量文件转换(读取文件中多行温度数据并输出转换结果) |
||||
|
* 4. 交互式输入模式(原 Python 程序的核心交互逻辑) |
||||
|
*/ |
||||
|
public class TemperatureConverter { |
||||
|
|
||||
|
/** |
||||
|
* 将摄氏度转换为华氏度 |
||||
|
* @param c 摄氏温度(double 类型,保证精度) |
||||
|
* @return double: 对应的华氏温度 |
||||
|
*/ |
||||
|
public static double celsiusToFahrenheit(double c) { |
||||
|
return c * 9.0 / 5.0 + 32.0; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 将华氏度转换为摄氏度 |
||||
|
* @param f 华氏温度(double 类型,保证精度) |
||||
|
* @return double: 对应的摄氏温度 |
||||
|
*/ |
||||
|
public static double fahrenheitToCelsius(double f) { |
||||
|
return (f - 32.0) * 5.0 / 9.0; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 处理单个温度值的转换逻辑(核心复用逻辑) |
||||
|
* @param value 温度数值 |
||||
|
* @param unit 温度单位(C/F,不区分大小写) |
||||
|
*/ |
||||
|
public static void convertSingleTemperature(double value, String unit) { |
||||
|
String upperUnit = unit.toUpperCase(); |
||||
|
if (upperUnit.startsWith("C")) { |
||||
|
// 摄氏度转华氏度
|
||||
|
double f = celsiusToFahrenheit(value); |
||||
|
System.out.printf("%.2f °C = %.2f °F%n", value, f); |
||||
|
} else if (upperUnit.startsWith("F")) { |
||||
|
// 华氏度转摄氏度
|
||||
|
double c = fahrenheitToCelsius(value); |
||||
|
System.out.printf("%.2f °F = %.2f °C%n", value, c); |
||||
|
} else { |
||||
|
System.out.println("未知单位,请使用 C 或 F。"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 批量处理文件中的温度转换 |
||||
|
* @param filePath 文件路径(文件中每行格式:数值 单位,如 36.6 C) |
||||
|
*/ |
||||
|
public static void batchConvertFromFile(String filePath) { |
||||
|
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { |
||||
|
String line; |
||||
|
System.out.println("===== 批量转换结果 ====="); |
||||
|
while ((line = br.readLine()) != null) { |
||||
|
String trimmedLine = line.trim(); |
||||
|
if (trimmedLine.isEmpty()) { |
||||
|
continue; // 跳过空行
|
||||
|
} |
||||
|
String[] parts = trimmedLine.split("\\s+"); // 按任意空白符分割
|
||||
|
try { |
||||
|
double value = Double.parseDouble(parts[0]); |
||||
|
String unit = parts.length > 1 ? parts[1] : "C"; |
||||
|
convertSingleTemperature(value, unit); |
||||
|
} catch (Exception e) { |
||||
|
System.out.printf("行 [%s] 解析失败:%s%n", line, e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
} catch (IOException e) { |
||||
|
System.out.println("文件读取失败:" + e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 交互式输入模式(原 Python 程序的 main 逻辑) |
||||
|
*/ |
||||
|
public static void interactiveMode() { |
||||
|
Scanner scanner = new Scanner(System.in); |
||||
|
System.out.print("请输入要转换的温度与单位(例如 36.6 C 或 97 F):"); |
||||
|
String s = scanner.nextLine().trim(); |
||||
|
scanner.close(); |
||||
|
|
||||
|
if (s.isEmpty()) { |
||||
|
System.out.println("输入为空,程序退出。"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
String[] parts = s.split("\\s+"); |
||||
|
try { |
||||
|
double value = Double.parseDouble(parts[0]); |
||||
|
String unit = parts.length > 1 ? parts[1] : "C"; |
||||
|
convertSingleTemperature(value, unit); |
||||
|
} catch (Exception e) { |
||||
|
System.out.println("输入解析失败,请按示例输入数值与单位,例如:36.6 C"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 程序入口方法 |
||||
|
* @param args 命令行参数: |
||||
|
* - 无参数:进入交互式模式 |
||||
|
* - 2个参数:数值 单位(如 36.6 C)→ 单次转换 |
||||
|
* - 1个参数:文件路径 → 批量转换 |
||||
|
*/ |
||||
|
public static void main(String[] args) { |
||||
|
if (args.length == 0) { |
||||
|
// 无参数 → 交互式模式
|
||||
|
interactiveMode(); |
||||
|
} else if (args.length == 1) { |
||||
|
// 1个参数 → 批量文件转换
|
||||
|
batchConvertFromFile(args[0]); |
||||
|
} else if (args.length == 2) { |
||||
|
// 2个参数 → 命令行单次转换
|
||||
|
try { |
||||
|
double value = Double.parseDouble(args[0]); |
||||
|
String unit = args[1]; |
||||
|
convertSingleTemperature(value, unit); |
||||
|
} catch (Exception e) { |
||||
|
System.out.println("命令行参数解析失败,请按格式输入:java TemperatureConverter 36.6 C"); |
||||
|
} |
||||
|
} else { |
||||
|
// 参数数量错误
|
||||
|
System.out.println("参数数量错误!"); |
||||
|
System.out.println("使用说明:"); |
||||
|
System.out.println("1. 交互式模式:java TemperatureConverter"); |
||||
|
System.out.println("2. 命令行单次转换:java TemperatureConverter 36.6 C"); |
||||
|
System.out.println("3. 批量文件转换:java TemperatureConverter temp.txt"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
After Width: | Height: | Size: 32 KiB |
@ -0,0 +1,38 @@ |
|||||
|
markdown |
||||
|
# 温度转换工具(Java 版) |
||||
|
Python 温度转换程序的 Java 等效实现,支持摄氏/华氏互转、命令行/批量文件转换。 |
||||
|
|
||||
|
## 环境要求 |
||||
|
JDK 8+,确保 `javac`/`java` 命令可执行。 |
||||
|
|
||||
|
## 编译与运行 |
||||
|
### 1. 编译 |
||||
|
```bash |
||||
|
javac TemperatureConverter.java |
||||
|
2. 运行方式 |
||||
|
方式 1:交互式模式(无参数) |
||||
|
bash |
||||
|
运行 |
||||
|
java TemperatureConverter |
||||
|
示例:输入 36.6 C → 输出 36.60 °C = 97.88 °F |
||||
|
方式 2:命令行单次转换(2 参数) |
||||
|
bash |
||||
|
运行 |
||||
|
java TemperatureConverter 36.6 C # 摄氏度转华氏度 |
||||
|
java TemperatureConverter 98.6 F # 华氏度转摄氏度 |
||||
|
方式 3:批量文件转换(1 参数) |
||||
|
先创建 temp.txt,每行格式:数值 单位(如 36.6 C) |
||||
|
bash |
||||
|
运行 |
||||
|
java TemperatureConverter temp.txt |
||||
|
测试用例 |
||||
|
表格 |
||||
|
输入 / 命令 预期输出 |
||||
|
36.6 C 36.60 °C = 97.88 °F |
||||
|
98.6 F 98.60 °F = 37.00 °C |
||||
|
向 AI 提交核心 prompt:“将指定 Python 温度转换程序改写为等效 Java 程序, |
||||
|
保留注释、保证功能一致,支持命令行参数和批量文件转换,配套完整 README(含编译 / 运行命令)”。 |
||||
|
AI 首先复刻 Python 核心转换公式为 Java 方法,补充中文注释说明参数 / 功能; |
||||
|
接着扩展命令行参数解析、批量读取文件逻辑,拆分复用核心转换方法; |
||||
|
然后适配 Java 输入输出与异常处理,保证交互逻辑与原程序一致; |
||||
|
最后生成标准化 README,包含编译运行命令、测试用例,同时梳理 AI 协助记录,确保代码可直接编译运行且符合提交规范。 |
||||
@ -0,0 +1,137 @@ |
|||||
|
import java.io.BufferedReader; |
||||
|
import java.io.FileReader; |
||||
|
import java.io.IOException; |
||||
|
import java.util.Scanner; |
||||
|
|
||||
|
/** |
||||
|
* 温度转换工具类 |
||||
|
* 支持功能: |
||||
|
* 1. 摄氏度 ↔ 华氏度 互转 |
||||
|
* 2. 命令行参数模式(如:java TemperatureConverter 36.6 C) |
||||
|
* 3. 批量文件转换(读取文件中多行温度数据并输出转换结果) |
||||
|
* 4. 交互式输入模式(原 Python 程序的核心交互逻辑) |
||||
|
*/ |
||||
|
public class TemperatureConverter { |
||||
|
|
||||
|
/** |
||||
|
* 将摄氏度转换为华氏度 |
||||
|
* @param c 摄氏温度(double 类型,保证精度) |
||||
|
* @return double: 对应的华氏温度 |
||||
|
*/ |
||||
|
public static double celsiusToFahrenheit(double c) { |
||||
|
return c * 9.0 / 5.0 + 32.0; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 将华氏度转换为摄氏度 |
||||
|
* @param f 华氏温度(double 类型,保证精度) |
||||
|
* @return double: 对应的摄氏温度 |
||||
|
*/ |
||||
|
public static double fahrenheitToCelsius(double f) { |
||||
|
return (f - 32.0) * 5.0 / 9.0; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 处理单个温度值的转换逻辑(核心复用逻辑) |
||||
|
* @param value 温度数值 |
||||
|
* @param unit 温度单位(C/F,不区分大小写) |
||||
|
*/ |
||||
|
public static void convertSingleTemperature(double value, String unit) { |
||||
|
String upperUnit = unit.toUpperCase(); |
||||
|
if (upperUnit.startsWith("C")) { |
||||
|
// 摄氏度转华氏度
|
||||
|
double f = celsiusToFahrenheit(value); |
||||
|
System.out.printf("%.2f °C = %.2f °F%n", value, f); |
||||
|
} else if (upperUnit.startsWith("F")) { |
||||
|
// 华氏度转摄氏度
|
||||
|
double c = fahrenheitToCelsius(value); |
||||
|
System.out.printf("%.2f °F = %.2f °C%n", value, c); |
||||
|
} else { |
||||
|
System.out.println("未知单位,请使用 C 或 F。"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 批量处理文件中的温度转换 |
||||
|
* @param filePath 文件路径(文件中每行格式:数值 单位,如 36.6 C) |
||||
|
*/ |
||||
|
public static void batchConvertFromFile(String filePath) { |
||||
|
try (BufferedReader br = new BufferedReader(new FileReader(filePath))) { |
||||
|
String line; |
||||
|
System.out.println("===== 批量转换结果 ====="); |
||||
|
while ((line = br.readLine()) != null) { |
||||
|
String trimmedLine = line.trim(); |
||||
|
if (trimmedLine.isEmpty()) { |
||||
|
continue; // 跳过空行
|
||||
|
} |
||||
|
String[] parts = trimmedLine.split("\\s+"); // 按任意空白符分割
|
||||
|
try { |
||||
|
double value = Double.parseDouble(parts[0]); |
||||
|
String unit = parts.length > 1 ? parts[1] : "C"; |
||||
|
convertSingleTemperature(value, unit); |
||||
|
} catch (Exception e) { |
||||
|
System.out.printf("行 [%s] 解析失败:%s%n", line, e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
} catch (IOException e) { |
||||
|
System.out.println("文件读取失败:" + e.getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 交互式输入模式(原 Python 程序的 main 逻辑) |
||||
|
*/ |
||||
|
public static void interactiveMode() { |
||||
|
Scanner scanner = new Scanner(System.in); |
||||
|
System.out.print("请输入要转换的温度与单位(例如 36.6 C 或 97 F):"); |
||||
|
String s = scanner.nextLine().trim(); |
||||
|
scanner.close(); |
||||
|
|
||||
|
if (s.isEmpty()) { |
||||
|
System.out.println("输入为空,程序退出。"); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
String[] parts = s.split("\\s+"); |
||||
|
try { |
||||
|
double value = Double.parseDouble(parts[0]); |
||||
|
String unit = parts.length > 1 ? parts[1] : "C"; |
||||
|
convertSingleTemperature(value, unit); |
||||
|
} catch (Exception e) { |
||||
|
System.out.println("输入解析失败,请按示例输入数值与单位,例如:36.6 C"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 程序入口方法 |
||||
|
* @param args 命令行参数: |
||||
|
* - 无参数:进入交互式模式 |
||||
|
* - 2个参数:数值 单位(如 36.6 C)→ 单次转换 |
||||
|
* - 1个参数:文件路径 → 批量转换 |
||||
|
*/ |
||||
|
public static void main(String[] args) { |
||||
|
if (args.length == 0) { |
||||
|
// 无参数 → 交互式模式
|
||||
|
interactiveMode(); |
||||
|
} else if (args.length == 1) { |
||||
|
// 1个参数 → 批量文件转换
|
||||
|
batchConvertFromFile(args[0]); |
||||
|
} else if (args.length == 2) { |
||||
|
// 2个参数 → 命令行单次转换
|
||||
|
try { |
||||
|
double value = Double.parseDouble(args[0]); |
||||
|
String unit = args[1]; |
||||
|
convertSingleTemperature(value, unit); |
||||
|
} catch (Exception e) { |
||||
|
System.out.println("命令行参数解析失败,请按格式输入:java TemperatureConverter 36.6 C"); |
||||
|
} |
||||
|
} else { |
||||
|
// 参数数量错误
|
||||
|
System.out.println("参数数量错误!"); |
||||
|
System.out.println("使用说明:"); |
||||
|
System.out.println("1. 交互式模式:java TemperatureConverter"); |
||||
|
System.out.println("2. 命令行单次转换:java TemperatureConverter 36.6 C"); |
||||
|
System.out.println("3. 批量文件转换:java TemperatureConverter temp.txt"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
public class Student { |
||||
|
|
||||
|
String StudentID; |
||||
|
String name; |
||||
|
double score; |
||||
|
void study(){ |
||||
|
System.out.println(name +" " +StudentID + " "+score); |
||||
|
} |
||||
|
public static void main(String[] args) { |
||||
|
Student s1=new Student(); |
||||
|
Student s2=new Student(); |
||||
|
s1.StudentID="202413010901"; |
||||
|
s2.StudentID="202413010902"; |
||||
|
s1.name="张三"; |
||||
|
s2.name="李四"; |
||||
|
s1.score=88.6; |
||||
|
s2.score=92.3; |
||||
|
s1.study(); |
||||
|
s2.study(); |
||||
|
|
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,74 @@ |
|||||
|
package com.rental; |
||||
|
|
||||
|
public class Car { |
||||
|
private final String licensePlate; |
||||
|
private String brand; |
||||
|
private String model; |
||||
|
private double dailyRent; |
||||
|
private boolean isRented; |
||||
|
private static int totalCars=0; |
||||
|
public Car(String licensePlate,String brand,String model,double dailyRent){ |
||||
|
this.licensePlate=licensePlate; |
||||
|
this.brand=brand; |
||||
|
this.model=model; |
||||
|
if (dailyRent > 0) { |
||||
|
this.dailyRent = dailyRent; |
||||
|
} else { |
||||
|
this.dailyRent = 300; |
||||
|
System.out.println("日租金不合法"); |
||||
|
} |
||||
|
this.isRented=false; |
||||
|
Car.totalCars+=1; |
||||
|
} |
||||
|
public String getLicensePlate(){ |
||||
|
return licensePlate; |
||||
|
} |
||||
|
public Car(String licensePlate, String brand, String model) { |
||||
|
this(licensePlate, brand, model, 300); |
||||
|
} |
||||
|
public String getBrand(){ |
||||
|
return brand; |
||||
|
} |
||||
|
public String getModel(){ |
||||
|
return model; |
||||
|
} |
||||
|
public double getDailyRent(){ |
||||
|
return dailyRent; |
||||
|
} |
||||
|
public boolean isRented() { |
||||
|
return isRented; |
||||
|
} |
||||
|
public void setBrand(String brand){ |
||||
|
this.brand=brand; |
||||
|
} |
||||
|
public void setmodel(String model){ |
||||
|
this.model=model; |
||||
|
} |
||||
|
public void setdailyRent(double dailyRent){ |
||||
|
if (dailyRent > 0) { |
||||
|
this.dailyRent = dailyRent; |
||||
|
} else { |
||||
|
System.out.println("租金不合法"); |
||||
|
} |
||||
|
} |
||||
|
public void rentCar(){ |
||||
|
if(isRented){ |
||||
|
System.out.println("车辆已租出,无法再次租用"); |
||||
|
return; |
||||
|
} |
||||
|
this.isRented=true; |
||||
|
} |
||||
|
public void returnCar(){ |
||||
|
if (!isRented){ |
||||
|
System.out.println("车辆未被租用,无需归还"); |
||||
|
return; |
||||
|
} |
||||
|
this.isRented=false; |
||||
|
} |
||||
|
public double calculateRent(int days){ |
||||
|
return dailyRent*days; |
||||
|
} |
||||
|
public static int getTotalCars(){ |
||||
|
return totalCars; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
package com.rental; |
||||
|
public class TestCar { |
||||
|
public static void main(String[] args) { |
||||
|
// 构造方法
|
||||
|
Car car1 = new Car("A123", "Toyota", "Camry", 300); |
||||
|
Car car2 = new Car("B456", "Honda", "Civic"); // 默认租金
|
||||
|
Car car3 = new Car("C789", "BMW", "X5", -100); // 非法租金测试
|
||||
|
// 输出车辆信息
|
||||
|
System.out.println("所有车辆信息:"); |
||||
|
displayInfo(car1); |
||||
|
displayInfo(car2); |
||||
|
displayInfo(car3); |
||||
|
// 流程测试
|
||||
|
System.out.println("租车流程测试:"); |
||||
|
car1.rentCar(); // 第一次租
|
||||
|
car1.rentCar(); // 再租
|
||||
|
car1.returnCar(); // 第一次还
|
||||
|
car1.returnCar(); // 再还
|
||||
|
//计算租金
|
||||
|
System.out.println("租金计算:"); |
||||
|
double rent = car2.calculateRent(5); |
||||
|
System.out.println("car2 租用5天费用: " + rent); |
||||
|
// 非法租金
|
||||
|
System.out.println("非法租金测试:"); |
||||
|
car2.setdailyRent(-100); // 给一个非法值
|
||||
|
System.out.println("car2 当前日租金: " + car2.getDailyRent()); |
||||
|
|
||||
|
// 输出总车辆数
|
||||
|
System.out.println("总车辆数"); |
||||
|
System.out.println("Total Cars: " + Car.getTotalCars()); |
||||
|
} |
||||
|
//输出车辆信息
|
||||
|
public static void displayInfo(Car car) { |
||||
|
System.out.println("车牌号: " + car.getLicensePlate()); |
||||
|
System.out.println("品牌: " + car.getBrand()); |
||||
|
System.out.println("型号: " + car.getModel()); |
||||
|
System.out.println("日租金: " + car.getDailyRent()); |
||||
|
System.out.println("是否已租出: " + (car.isRented() ? "是" : "否")); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
After Width: | Height: | Size: 55 KiB |
@ -0,0 +1,55 @@ |
|||||
|
package com.rental; |
||||
|
public class ShiYan { |
||||
|
static abstract class Shape { |
||||
|
public abstract double getArea(); |
||||
|
} |
||||
|
static class Circle extends Shape { |
||||
|
private double radius; |
||||
|
|
||||
|
public Circle(double radius) { |
||||
|
this.radius = radius; |
||||
|
} |
||||
|
@Override |
||||
|
public double getArea() { |
||||
|
return Math.PI * radius * radius; |
||||
|
} |
||||
|
} |
||||
|
// 矩形
|
||||
|
static class Rectangle extends Shape { |
||||
|
private double width; |
||||
|
private double height; |
||||
|
public Rectangle(double width, double height) { |
||||
|
this.width = width; |
||||
|
this.height = height; |
||||
|
} |
||||
|
@Override |
||||
|
public double getArea() { |
||||
|
return width * height; |
||||
|
} |
||||
|
} |
||||
|
static class Triangle extends Shape { |
||||
|
private double base; |
||||
|
private double height; |
||||
|
public Triangle(double base, double height) { |
||||
|
this.base = base; |
||||
|
this.height = height; |
||||
|
} |
||||
|
@Override |
||||
|
public double getArea() { |
||||
|
return 0.5 * base * height; |
||||
|
} |
||||
|
} |
||||
|
static class ShapeUtil { |
||||
|
public static void printArea(Shape shape) { |
||||
|
System.out.println("图形面积为:" + shape.getArea()); |
||||
|
} |
||||
|
} |
||||
|
public static void main(String[] args) { |
||||
|
Shape circle = new Circle(5); |
||||
|
Shape rectangle = new Rectangle(4, 6); |
||||
|
Shape triangle = new Triangle(3, 8); |
||||
|
ShapeUtil.printArea(circle); |
||||
|
ShapeUtil.printArea(rectangle); |
||||
|
ShapeUtil.printArea(triangle); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
public class Circle extends Shape{ |
||||
|
@Override |
||||
|
public void draw(){ |
||||
|
System.out.println("圆形"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
public class Main { |
||||
|
public static void drawShape(Shape s) { |
||||
|
s.draw(); |
||||
|
} |
||||
|
public static void main(String[] args) { |
||||
|
Shape circle = new Circle(); |
||||
|
Shape rectangle = new Rectangle(); |
||||
|
drawShape(circle); |
||||
|
drawShape(rectangle); |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,6 @@ |
|||||
|
public class Rectangle extends Shape{ |
||||
|
@Override |
||||
|
public void draw() { |
||||
|
System.out.println("矩形"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
public class Shape { |
||||
|
public void draw(){ |
||||
|
System.out.println("shape"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
@ -0,0 +1,6 @@ |
|||||
|
public class Bike extends Vehicle{ |
||||
|
@Override |
||||
|
public void run(){ |
||||
|
System.out.println("自行车"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
public class Car extends Vehicle{ |
||||
|
@Override |
||||
|
public void run(){ |
||||
|
System.out.println("汽车"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,6 @@ |
|||||
|
public class Truck extends Vehicle{ |
||||
|
@Override |
||||
|
public void run(){ |
||||
|
System.out.println("卡车"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
public abstract class Vehicle { |
||||
|
public abstract void run(); |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
abstract class Animal { |
||||
|
public abstract void makeSound(); |
||||
|
} |
||||
|
interface Swimmable { |
||||
|
void swim(); |
||||
|
} |
||||
|
class Dog extends Animal implements Swimmable { |
||||
|
@Override |
||||
|
public void makeSound() { |
||||
|
System.out.println("汪汪汪"); |
||||
|
} |
||||
|
@Override |
||||
|
public void swim() { |
||||
|
System.out.println("狗会游泳"); |
||||
|
} |
||||
|
} |
||||
|
class Cat extends Animal { |
||||
|
@Override |
||||
|
public void makeSound() { |
||||
|
System.out.println("喵喵喵"); |
||||
|
} |
||||
|
} |
||||
|
public class AnimalTest { |
||||
|
public static void main(String[] args) { |
||||
|
Animal dog = new Dog(); |
||||
|
Animal cat = new Cat(); |
||||
|
System.out.println("叫声"); |
||||
|
dog.makeSound(); |
||||
|
cat.makeSound(); |
||||
|
System.out.println("游泳"); |
||||
|
if (dog instanceof Swimmable) { |
||||
|
((Swimmable) dog).swim(); |
||||
|
} else { |
||||
|
System.out.println("不会游泳"); |
||||
|
} |
||||
|
if (cat instanceof Swimmable) { |
||||
|
((Swimmable) cat).swim(); |
||||
|
} else { |
||||
|
System.out.println("不会游泳"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
package com.crawler; |
||||
|
|
||||
|
import com.crawler.model.Movie; |
||||
|
import com.crawler.spider.DoubanSpider; |
||||
|
import com.crawler.utils.DataUtils; |
||||
|
import com.crawler.ui.MovieResultDisplay; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public class MovieMain { |
||||
|
public static void main(String[] args) { |
||||
|
try { |
||||
|
System.out.println("开始爬取豆瓣电影Top250数据..."); |
||||
|
|
||||
|
// 1. 启动爬虫
|
||||
|
DoubanSpider spider = new DoubanSpider(); |
||||
|
List<Movie> movieList = spider.crawlMovies(); |
||||
|
|
||||
|
// 2. 清洗数据
|
||||
|
List<Movie> cleanedMovies = movieList.stream() |
||||
|
.map(DataUtils::cleanMovie) |
||||
|
.filter(movie -> movie != null) |
||||
|
.toList(); |
||||
|
|
||||
|
// 3. 保存数据到CSV文件
|
||||
|
DataUtils.writeMovieToCSV(cleanedMovies, "douban_movies.csv"); |
||||
|
System.out.println("数据已保存到 douban_movies.csv"); |
||||
|
|
||||
|
// 4. 展示结果
|
||||
|
MovieResultDisplay.displayResults(cleanedMovies); |
||||
|
|
||||
|
// 5. 生成图表
|
||||
|
MovieResultDisplay.generateRatingDistributionChart(cleanedMovies); |
||||
|
MovieResultDisplay.generateYearDistributionChart(cleanedMovies); |
||||
|
MovieResultDisplay.generateGenreDistributionChart(cleanedMovies); |
||||
|
MovieResultDisplay.generateYearRatingChart(cleanedMovies); |
||||
|
|
||||
|
System.out.println("\n爬虫任务完成!"); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 24 KiB |
@ -0,0 +1,59 @@ |
|||||
|
package com.crawler; |
||||
|
|
||||
|
import com.crawler.chart.ChartGenerator; |
||||
|
import com.crawler.chart.ChartManager; |
||||
|
import com.crawler.chart.impl.GenreDistributionChartGenerator; |
||||
|
import com.crawler.chart.impl.RatingDistributionChartGenerator; |
||||
|
import com.crawler.chart.impl.YearDistributionChartGenerator; |
||||
|
import com.crawler.chart.impl.YearRatingChartGenerator; |
||||
|
import com.crawler.model.Movie; |
||||
|
import com.crawler.spider.DoubanSpider; |
||||
|
import com.crawler.utils.DataUtils; |
||||
|
import com.crawler.ui.MovieResultDisplay; |
||||
|
|
||||
|
import java.util.List; |
||||
|
|
||||
|
public class MovieMain { |
||||
|
public static void main(String[] args) { |
||||
|
try { |
||||
|
System.out.println("开始爬取豆瓣电影Top250数据..."); |
||||
|
|
||||
|
// 1. 启动爬虫
|
||||
|
DoubanSpider spider = new DoubanSpider(); |
||||
|
List<Movie> movieList = spider.crawlMovies(); |
||||
|
|
||||
|
// 2. 清洗数据
|
||||
|
List<Movie> cleanedMovies = movieList.stream() |
||||
|
.map(DataUtils::cleanMovie) |
||||
|
.filter(movie -> movie != null) |
||||
|
.toList(); |
||||
|
|
||||
|
// 3. 保存数据到CSV文件
|
||||
|
DataUtils.writeMovieToCSV(cleanedMovies, "douban_movies.csv"); |
||||
|
System.out.println("数据已保存到 douban_movies.csv"); |
||||
|
|
||||
|
// 4. 展示结果
|
||||
|
MovieResultDisplay.displayResults(cleanedMovies); |
||||
|
|
||||
|
// 5. 使用多态生成图表
|
||||
|
ChartManager chartManager = new ChartManager(); |
||||
|
|
||||
|
ChartGenerator ratingChart = new RatingDistributionChartGenerator(); |
||||
|
ChartGenerator yearChart = new YearDistributionChartGenerator(); |
||||
|
ChartGenerator genreChart = new GenreDistributionChartGenerator(); |
||||
|
ChartGenerator yearRatingChart = new YearRatingChartGenerator(); |
||||
|
|
||||
|
chartManager.addChartGenerator(ratingChart); |
||||
|
chartManager.addChartGenerator(yearChart); |
||||
|
chartManager.addChartGenerator(genreChart); |
||||
|
chartManager.addChartGenerator(yearRatingChart); |
||||
|
|
||||
|
chartManager.generateAllCharts(cleanedMovies); |
||||
|
|
||||
|
System.out.println("\n爬虫任务完成!"); |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,136 @@ |
|||||
|
# 电影爬虫项目 - 继承与多态实现说明 |
||||
|
|
||||
|
## 项目简介 |
||||
|
|
||||
|
本项目是一个Java电影爬虫,从豆瓣电影Top250抓取数据,进行清洗、存储、分析,并生成多种图表展示结果。项目重点展示了面向对象编程中**继承**和**多态**的实现。 |
||||
|
|
||||
|
## 继承与多态实现 |
||||
|
|
||||
|
### 1. 接口继承 |
||||
|
|
||||
|
#### 1.1 核心接口定义 |
||||
|
|
||||
|
**文件**: `src/main/java/com/crawler/chart/ChartGenerator.java` |
||||
|
|
||||
|
```java |
||||
|
public interface ChartGenerator { |
||||
|
void generateChart(Movie[] movies); |
||||
|
String getChartName(); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### 1.2 实现类继承 |
||||
|
|
||||
|
| 实现类 | 文件位置 | 继承关系 | |
||||
|
|-------|---------|----------| |
||||
|
| `RatingDistributionChartGenerator` | `src/main/java/com/crawler/chart/impl/RatingDistributionChartGenerator.java` | 实现 `ChartGenerator` 接口 | |
||||
|
| `YearDistributionChartGenerator` | `src/main/java/com/crawler/chart/impl/YearDistributionChartGenerator.java` | 实现 `ChartGenerator` 接口 | |
||||
|
| `GenreDistributionChartGenerator` | `src/main/java/com/crawler/chart/impl/GenreDistributionChartGenerator.java` | 实现 `ChartGenerator` 接口 | |
||||
|
| `YearRatingChartGenerator` | `src/main/java/com/crawler/chart/impl/YearRatingChartGenerator.java` | 实现 `ChartGenerator` 接口 | |
||||
|
|
||||
|
### 2. 多态实现 |
||||
|
|
||||
|
#### 2.1 向上转型(接口引用指向实现类) |
||||
|
|
||||
|
**文件**: `src/main/java/com/crawler/MovieMain.java` (第41-44行) |
||||
|
|
||||
|
```java |
||||
|
ChartGenerator ratingChart = new RatingDistributionChartGenerator(); |
||||
|
ChartGenerator yearChart = new YearDistributionChartGenerator(); |
||||
|
ChartGenerator genreChart = new GenreDistributionChartGenerator(); |
||||
|
ChartGenerator yearRatingChart = new YearRatingChartGenerator(); |
||||
|
``` |
||||
|
|
||||
|
#### 2.2 方法参数多态 |
||||
|
|
||||
|
**文件**: `src/main/java/com/crawler/chart/ChartManager.java` (第12-13行) |
||||
|
|
||||
|
```java |
||||
|
public void addChartGenerator(ChartGenerator generator) { |
||||
|
chartGenerators.add(generator); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### 2.3 运行时多态(动态绑定) |
||||
|
|
||||
|
**文件**: `src/main/java/com/crawler/chart/ChartManager.java` (第21-25行) |
||||
|
|
||||
|
```java |
||||
|
public void generateAllCharts(List<Movie> movies) { |
||||
|
Movie[] movieArray = movies.toArray(new Movie[0]); |
||||
|
for (ChartGenerator generator : chartGenerators) { |
||||
|
System.out.println("生成图表: " + generator.getChartName()); |
||||
|
generator.generateChart(movieArray); // 运行时根据实际类型调用对应方法 |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
#### 2.4 统一调用接口 |
||||
|
|
||||
|
**文件**: `src/main/java/com/crawler/MovieMain.java` (第46-51行) |
||||
|
|
||||
|
```java |
||||
|
chartManager.addChartGenerator(ratingChart); |
||||
|
chartManager.addChartGenerator(yearChart); |
||||
|
chartManager.addChartGenerator(genreChart); |
||||
|
chartManager.addChartGenerator(yearRatingChart); |
||||
|
|
||||
|
chartManager.generateAllCharts(cleanedMovies); |
||||
|
``` |
||||
|
|
||||
|
## 继承与多态的优势 |
||||
|
|
||||
|
1. **代码复用**:所有图表生成器共享相同的接口方法 |
||||
|
2. **可扩展性**:新增图表类型只需实现接口,无需修改现有代码 |
||||
|
3. **统一管理**:`ChartManager` 可以统一管理不同类型的图表生成器 |
||||
|
4. **灵活性**:通过接口引用可以操作不同的实现类对象 |
||||
|
5. **可维护性**:代码结构清晰,职责分明 |
||||
|
|
||||
|
## 项目结构 |
||||
|
|
||||
|
``` |
||||
|
src/ |
||||
|
└── main/ |
||||
|
└── java/ |
||||
|
└── com/ |
||||
|
└── crawler/ |
||||
|
├── MovieMain.java # 主入口文件 |
||||
|
├── model/ |
||||
|
│ └── Movie.java # 电影数据模型 |
||||
|
├── spider/ |
||||
|
│ └── DoubanSpider.java # 豆瓣爬虫实现 |
||||
|
├── analysis/ |
||||
|
│ └── MovieAnalyzer.java # 数据分析工具 |
||||
|
├── ui/ |
||||
|
│ └── MovieResultDisplay.java # 结果显示和图表生成 |
||||
|
├── utils/ |
||||
|
│ └── DataUtils.java # 数据工具类 |
||||
|
└── chart/ |
||||
|
├── ChartGenerator.java # 图表生成器接口 |
||||
|
├── ChartManager.java # 图表管理器 |
||||
|
└── impl/ |
||||
|
├── RatingDistributionChartGenerator.java # 评分分布图表 |
||||
|
├── YearDistributionChartGenerator.java # 年份分布图表 |
||||
|
├── GenreDistributionChartGenerator.java # 类型分布图表 |
||||
|
└── YearRatingChartGenerator.java # 年份评分相关性图表 |
||||
|
``` |
||||
|
|
||||
|
## 运行说明 |
||||
|
|
||||
|
1. **直接运行**:在IDE中直接运行 `MovieMain.java` |
||||
|
2. **依赖要求**:需要Jsoup和JFreeChart库 |
||||
|
3. **运行结果**: |
||||
|
- 控制台输出爬取进度和图表生成信息 |
||||
|
- 生成的CSV数据文件保存在项目目录 |
||||
|
- 生成的图表以PNG格式保存在项目目录 |
||||
|
|
||||
|
## 技术栈 |
||||
|
|
||||
|
- Java 8+ |
||||
|
- Jsoup (网页解析) |
||||
|
- JFreeChart (图表生成) |
||||
|
- Maven (依赖管理) |
||||
|
|
||||
|
## 总结 |
||||
|
|
||||
|
本项目通过图表生成器接口及其实现类,充分展示了面向对象编程中**继承**和**多态**的核心概念。接口定义了统一的方法规范,实现类提供了具体的实现逻辑,通过接口引用和运行时动态绑定,实现了代码的灵活性和可扩展性。 |
||||
@ -0,0 +1,119 @@ |
|||||
|
package com.crawler.analysis; |
||||
|
|
||||
|
import com.crawler.model.Movie; |
||||
|
|
||||
|
import java.util.*; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
public class MovieAnalyzer { |
||||
|
// 统计电影评分分布
|
||||
|
public static Map<Double, Integer> analyzeRatingDistribution(List<Movie> movieList) { |
||||
|
Map<Double, Integer> ratingMap = new TreeMap<>(); |
||||
|
|
||||
|
for (Movie movie : movieList) { |
||||
|
if (movie != null) { |
||||
|
double rating = movie.getRating(); |
||||
|
ratingMap.put(rating, ratingMap.getOrDefault(rating, 0) + 1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return ratingMap; |
||||
|
} |
||||
|
|
||||
|
// 统计电影年份分布
|
||||
|
public static Map<String, Integer> analyzeYearDistribution(List<Movie> movieList) { |
||||
|
Map<String, Integer> yearMap = new TreeMap<>(); |
||||
|
|
||||
|
for (Movie movie : movieList) { |
||||
|
if (movie != null && movie.getYear() != null) { |
||||
|
String year = movie.getYear(); |
||||
|
yearMap.put(year, yearMap.getOrDefault(year, 0) + 1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return yearMap; |
||||
|
} |
||||
|
|
||||
|
// 统计电影类型分布
|
||||
|
public static Map<String, Integer> analyzeGenreDistribution(List<Movie> movieList) { |
||||
|
Map<String, Integer> genreMap = new HashMap<>(); |
||||
|
|
||||
|
for (Movie movie : movieList) { |
||||
|
if (movie != null && movie.getGenre() != null) { |
||||
|
String genre = movie.getGenre(); |
||||
|
genreMap.put(genre, genreMap.getOrDefault(genre, 0) + 1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return genreMap; |
||||
|
} |
||||
|
|
||||
|
// 统计电影国家/地区分布
|
||||
|
public static Map<String, Integer> analyzeCountryDistribution(List<Movie> movieList) { |
||||
|
Map<String, Integer> countryMap = new HashMap<>(); |
||||
|
|
||||
|
for (Movie movie : movieList) { |
||||
|
if (movie != null && movie.getCountry() != null) { |
||||
|
String country = movie.getCountry(); |
||||
|
countryMap.put(country, countryMap.getOrDefault(country, 0) + 1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return countryMap; |
||||
|
} |
||||
|
|
||||
|
// 分析导演作品数量排行
|
||||
|
public static Map<String, Integer> analyzeDirectorWorks(List<Movie> movieList) { |
||||
|
Map<String, Integer> directorMap = new HashMap<>(); |
||||
|
|
||||
|
for (Movie movie : movieList) { |
||||
|
if (movie != null && movie.getDirector() != null) { |
||||
|
String director = movie.getDirector(); |
||||
|
directorMap.put(director, directorMap.getOrDefault(director, 0) + 1); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 按作品数量排序
|
||||
|
return directorMap.entrySet().stream() |
||||
|
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed()) |
||||
|
.collect(Collectors.toMap( |
||||
|
Map.Entry::getKey, |
||||
|
Map.Entry::getValue, |
||||
|
(e1, e2) -> e1, |
||||
|
LinkedHashMap::new |
||||
|
)); |
||||
|
} |
||||
|
|
||||
|
// 计算平均评分
|
||||
|
public static double calculateAverageRating(List<Movie> movieList) { |
||||
|
return movieList.stream() |
||||
|
.filter(Objects::nonNull) |
||||
|
.mapToDouble(Movie::getRating) |
||||
|
.average() |
||||
|
.orElse(0.0); |
||||
|
} |
||||
|
|
||||
|
// 计算评分与年份的相关性(简单计算)
|
||||
|
public static Map<String, Double> analyzeYearRatingCorrelation(List<Movie> movieList) { |
||||
|
Map<String, List<Double>> yearRatingsMap = new TreeMap<>(); |
||||
|
|
||||
|
for (Movie movie : movieList) { |
||||
|
if (movie != null && movie.getYear() != null) { |
||||
|
String year = movie.getYear(); |
||||
|
double rating = movie.getRating(); |
||||
|
yearRatingsMap.computeIfAbsent(year, k -> new ArrayList<>()).add(rating); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 计算每年的平均评分
|
||||
|
Map<String, Double> yearAverageRatingMap = new TreeMap<>(); |
||||
|
for (Map.Entry<String, List<Double>> entry : yearRatingsMap.entrySet()) { |
||||
|
String year = entry.getKey(); |
||||
|
List<Double> ratings = entry.getValue(); |
||||
|
double average = ratings.stream().mapToDouble(Double::doubleValue).average().orElse(0.0); |
||||
|
yearAverageRatingMap.put(year, average); |
||||
|
} |
||||
|
|
||||
|
return yearAverageRatingMap; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,8 @@ |
|||||
|
package com.crawler.chart; |
||||
|
|
||||
|
import com.crawler.model.Movie; |
||||
|
|
||||
|
public interface ChartGenerator { |
||||
|
void generateChart(Movie[] movies); |
||||
|
String getChartName(); |
||||
|
} |
||||
@ -0,0 +1,30 @@ |
|||||
|
package com.crawler.chart; |
||||
|
|
||||
|
import com.crawler.chart.impl.GenreDistributionChartGenerator; |
||||
|
import com.crawler.chart.impl.RatingDistributionChartGenerator; |
||||
|
import com.crawler.chart.impl.YearDistributionChartGenerator; |
||||
|
import com.crawler.chart.impl.YearRatingChartGenerator; |
||||
|
import com.crawler.model.Movie; |
||||
|
|
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
|
||||
|
public class ChartManager { |
||||
|
private List<ChartGenerator> chartGenerators; |
||||
|
|
||||
|
public ChartManager() { |
||||
|
chartGenerators = new ArrayList<>(); |
||||
|
} |
||||
|
|
||||
|
public void addChartGenerator(ChartGenerator generator) { |
||||
|
chartGenerators.add(generator); |
||||
|
} |
||||
|
|
||||
|
public void generateAllCharts(List<Movie> movies) { |
||||
|
Movie[] movieArray = movies.toArray(new Movie[0]); |
||||
|
for (ChartGenerator generator : chartGenerators) { |
||||
|
System.out.println("生成图表: " + generator.getChartName()); |
||||
|
generator.generateChart(movieArray); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
package com.crawler.chart.impl; |
||||
|
|
||||
|
import com.crawler.chart.ChartGenerator; |
||||
|
import com.crawler.model.Movie; |
||||
|
import com.crawler.ui.MovieResultDisplay; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.util.List; |
||||
|
|
||||
|
public class GenreDistributionChartGenerator implements ChartGenerator { |
||||
|
@Override |
||||
|
public void generateChart(Movie[] movies) { |
||||
|
List<Movie> movieList = List.of(movies); |
||||
|
try { |
||||
|
MovieResultDisplay.generateGenreDistributionChart(movieList); |
||||
|
} catch (IOException e) { |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getChartName() { |
||||
|
return "Genre Distribution Chart"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
package com.crawler.chart.impl; |
||||
|
|
||||
|
import com.crawler.chart.ChartGenerator; |
||||
|
import com.crawler.model.Movie; |
||||
|
import com.crawler.ui.MovieResultDisplay; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
import java.util.stream.Collectors; |
||||
|
|
||||
|
public class RatingDistributionChartGenerator implements ChartGenerator { |
||||
|
@Override |
||||
|
public void generateChart(Movie[] movies) { |
||||
|
List<Movie> movieList = List.of(movies); |
||||
|
try { |
||||
|
MovieResultDisplay.generateRatingDistributionChart(movieList); |
||||
|
} catch (IOException e) { |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getChartName() { |
||||
|
return "Rating Distribution Chart"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
package com.crawler.chart.impl; |
||||
|
|
||||
|
import com.crawler.chart.ChartGenerator; |
||||
|
import com.crawler.model.Movie; |
||||
|
import com.crawler.ui.MovieResultDisplay; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.util.List; |
||||
|
|
||||
|
public class YearDistributionChartGenerator implements ChartGenerator { |
||||
|
@Override |
||||
|
public void generateChart(Movie[] movies) { |
||||
|
List<Movie> movieList = List.of(movies); |
||||
|
try { |
||||
|
MovieResultDisplay.generateYearDistributionChart(movieList); |
||||
|
} catch (IOException e) { |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getChartName() { |
||||
|
return "Year Distribution Chart"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
package com.crawler.chart.impl; |
||||
|
|
||||
|
import com.crawler.chart.ChartGenerator; |
||||
|
import com.crawler.model.Movie; |
||||
|
import com.crawler.ui.MovieResultDisplay; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.util.List; |
||||
|
|
||||
|
public class YearRatingChartGenerator implements ChartGenerator { |
||||
|
@Override |
||||
|
public void generateChart(Movie[] movies) { |
||||
|
List<Movie> movieList = List.of(movies); |
||||
|
try { |
||||
|
MovieResultDisplay.generateYearRatingChart(movieList); |
||||
|
} catch (IOException e) { |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String getChartName() { |
||||
|
return "Year Rating Correlation Chart"; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,108 @@ |
|||||
|
package com.crawler.model; |
||||
|
|
||||
|
public class Movie { |
||||
|
private int rank; |
||||
|
private String title; |
||||
|
private double rating; |
||||
|
private int ratingPeople; |
||||
|
private String director; |
||||
|
private String actors; |
||||
|
private String year; |
||||
|
private String country; |
||||
|
private String genre; |
||||
|
private String quote; |
||||
|
|
||||
|
// Getters and Setters
|
||||
|
public int getRank() { |
||||
|
return rank; |
||||
|
} |
||||
|
|
||||
|
public void setRank(int rank) { |
||||
|
this.rank = rank; |
||||
|
} |
||||
|
|
||||
|
public String getTitle() { |
||||
|
return title; |
||||
|
} |
||||
|
|
||||
|
public void setTitle(String title) { |
||||
|
this.title = title; |
||||
|
} |
||||
|
|
||||
|
public double getRating() { |
||||
|
return rating; |
||||
|
} |
||||
|
|
||||
|
public void setRating(double rating) { |
||||
|
this.rating = rating; |
||||
|
} |
||||
|
|
||||
|
public int getRatingPeople() { |
||||
|
return ratingPeople; |
||||
|
} |
||||
|
|
||||
|
public void setRatingPeople(int ratingPeople) { |
||||
|
this.ratingPeople = ratingPeople; |
||||
|
} |
||||
|
|
||||
|
public String getDirector() { |
||||
|
return director; |
||||
|
} |
||||
|
|
||||
|
public void setDirector(String director) { |
||||
|
this.director = director; |
||||
|
} |
||||
|
|
||||
|
public String getActors() { |
||||
|
return actors; |
||||
|
} |
||||
|
|
||||
|
public void setActors(String actors) { |
||||
|
this.actors = actors; |
||||
|
} |
||||
|
|
||||
|
public String getYear() { |
||||
|
return year; |
||||
|
} |
||||
|
|
||||
|
public void setYear(String year) { |
||||
|
this.year = year; |
||||
|
} |
||||
|
|
||||
|
public String getCountry() { |
||||
|
return country; |
||||
|
} |
||||
|
|
||||
|
public void setCountry(String country) { |
||||
|
this.country = country; |
||||
|
} |
||||
|
|
||||
|
public String getGenre() { |
||||
|
return genre; |
||||
|
} |
||||
|
|
||||
|
public void setGenre(String genre) { |
||||
|
this.genre = genre; |
||||
|
} |
||||
|
|
||||
|
public String getQuote() { |
||||
|
return quote; |
||||
|
} |
||||
|
|
||||
|
public void setQuote(String quote) { |
||||
|
this.quote = quote; |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public String toString() { |
||||
|
return "Movie{" + |
||||
|
"rank=" + rank + |
||||
|
", title='" + title + '\'' + |
||||
|
", rating=" + rating + |
||||
|
", ratingPeople=" + ratingPeople + |
||||
|
", director='" + director + '\'' + |
||||
|
", year='" + year + '\'' + |
||||
|
", genre='" + genre + '\'' + |
||||
|
'}'; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" |
||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
|
<modelVersion>4.0.0</modelVersion> |
||||
|
|
||||
|
<groupId>com.crawler</groupId> |
||||
|
<artifactId>job-crawler</artifactId> |
||||
|
<version>1.0-SNAPSHOT</version> |
||||
|
|
||||
|
<properties> |
||||
|
<maven.compiler.source>1.8</maven.compiler.source> |
||||
|
<maven.compiler.target>1.8</maven.compiler.target> |
||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
||||
|
</properties> |
||||
|
|
||||
|
<dependencies> |
||||
|
<!-- Jsoup - HTML解析库 --> |
||||
|
<dependency> |
||||
|
<groupId>org.jsoup</groupId> |
||||
|
<artifactId>jsoup</artifactId> |
||||
|
<version>1.17.2</version> |
||||
|
</dependency> |
||||
|
|
||||
|
<!-- JFreeChart - 图表生成库 --> |
||||
|
<dependency> |
||||
|
<groupId>org.jfree</groupId> |
||||
|
<artifactId>jfreechart</artifactId> |
||||
|
<version>1.5.4</version> |
||||
|
</dependency> |
||||
|
|
||||
|
<!-- JCommon - JFreeChart依赖 --> |
||||
|
<dependency> |
||||
|
<groupId>org.jfree</groupId> |
||||
|
<artifactId>jcommon</artifactId> |
||||
|
<version>1.0.24</version> |
||||
|
</dependency> |
||||
|
</dependencies> |
||||
|
|
||||
|
<build> |
||||
|
<sourceDirectory>src/main/java</sourceDirectory> |
||||
|
<outputDirectory>target/classes</outputDirectory> |
||||
|
<plugins> |
||||
|
<plugin> |
||||
|
<groupId>org.apache.maven.plugins</groupId> |
||||
|
<artifactId>maven-compiler-plugin</artifactId> |
||||
|
<version>3.8.1</version> |
||||
|
<configuration> |
||||
|
<source>${maven.compiler.source}</source> |
||||
|
<target>${maven.compiler.target}</target> |
||||
|
</configuration> |
||||
|
</plugin> |
||||
|
</plugins> |
||||
|
</build> |
||||
|
</project> |
||||
@ -0,0 +1,206 @@ |
|||||
|
package com.crawler.spider; |
||||
|
|
||||
|
import com.crawler.model.Movie; |
||||
|
import org.jsoup.Jsoup; |
||||
|
import org.jsoup.nodes.Document; |
||||
|
import org.jsoup.nodes.Element; |
||||
|
import org.jsoup.select.Elements; |
||||
|
|
||||
|
import java.io.IOException; |
||||
|
import java.util.ArrayList; |
||||
|
import java.util.List; |
||||
|
import java.util.concurrent.*; |
||||
|
|
||||
|
public class DoubanSpider { |
||||
|
private static final String BASE_URL = "https://movie.douban.com/top250"; |
||||
|
private static final int MAX_PAGES = 10; |
||||
|
private static final int THREAD_POOL_SIZE = 3; |
||||
|
private static final int REQUEST_DELAY = 1000; |
||||
|
|
||||
|
public List<Movie> crawlMovies() { |
||||
|
List<Movie> movieList = new ArrayList<>(); |
||||
|
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE); |
||||
|
List<Future<List<Movie>>> futures = new ArrayList<>(); |
||||
|
|
||||
|
try { |
||||
|
for (int page = 0; page < MAX_PAGES; page++) { |
||||
|
final int currentPage = page; |
||||
|
futures.add(executorService.submit(() -> { |
||||
|
try { |
||||
|
Thread.sleep(REQUEST_DELAY); |
||||
|
return crawlPage(currentPage); |
||||
|
} catch (Exception e) { |
||||
|
e.printStackTrace(); |
||||
|
return new ArrayList<>(); |
||||
|
} |
||||
|
})); |
||||
|
} |
||||
|
|
||||
|
for (Future<List<Movie>> future : futures) { |
||||
|
try { |
||||
|
movieList.addAll(future.get()); |
||||
|
} catch (Exception e) { |
||||
|
e.printStackTrace(); |
||||
|
} |
||||
|
} |
||||
|
} finally { |
||||
|
executorService.shutdown(); |
||||
|
} |
||||
|
|
||||
|
return movieList; |
||||
|
} |
||||
|
|
||||
|
private List<Movie> crawlPage(int page) throws IOException { |
||||
|
List<Movie> movieList = new ArrayList<>(); |
||||
|
String url = BASE_URL + "?start=" + (page * 25); |
||||
|
System.out.println("爬取页面: " + url); |
||||
|
|
||||
|
Document document = Jsoup.connect(url) |
||||
|
.userAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36") |
||||
|
.timeout(10000) |
||||
|
.get(); |
||||
|
|
||||
|
System.out.println("页面标题: " + document.title()); |
||||
|
|
||||
|
// 选择电影条目
|
||||
|
Elements movieItems = document.select(".grid_view li"); |
||||
|
System.out.println("找到电影条目数: " + movieItems.size()); |
||||
|
|
||||
|
for (Element item : movieItems) { |
||||
|
Movie movie = parseMovie(item); |
||||
|
if (movie != null) { |
||||
|
movieList.add(movie); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
System.out.println("页面" + (page + 1) + "爬取成功,获取电影数: " + movieList.size()); |
||||
|
return movieList; |
||||
|
} |
||||
|
|
||||
|
private Movie parseMovie(Element item) { |
||||
|
Movie movie = new Movie(); |
||||
|
|
||||
|
try { |
||||
|
// 排名
|
||||
|
Element rankElement = item.selectFirst(".pic em"); |
||||
|
if (rankElement != null) { |
||||
|
movie.setRank(Integer.parseInt(rankElement.text().trim())); |
||||
|
} |
||||
|
|
||||
|
// 标题
|
||||
|
Element titleElement = item.selectFirst(".title"); |
||||
|
if (titleElement != null) { |
||||
|
movie.setTitle(titleElement.text().trim()); |
||||
|
} |
||||
|
|
||||
|
// 评分
|
||||
|
Element ratingElement = item.selectFirst(".rating_num"); |
||||
|
if (ratingElement != null) { |
||||
|
movie.setRating(Double.parseDouble(ratingElement.text().trim())); |
||||
|
} |
||||
|
|
||||
|
// 评价人数
|
||||
|
Element ratingPeopleElement = item.selectFirst(".star span:nth-child(4)"); |
||||
|
if (ratingPeopleElement != null) { |
||||
|
String ratingPeople = ratingPeopleElement.text().trim(); |
||||
|
movie.setRatingPeople(Integer.parseInt(ratingPeople.replaceAll("[^0-9]", ""))); |
||||
|
} |
||||
|
|
||||
|
// 导演和演员
|
||||
|
Element infoElement = item.selectFirst(".bd p:first-child"); |
||||
|
if (infoElement != null) { |
||||
|
String info = infoElement.text().trim(); |
||||
|
|
||||
|
// 提取导演
|
||||
|
if (info.contains("导演:")) { |
||||
|
int directorStart = info.indexOf("导演:") + 3; |
||||
|
int directorEnd = info.indexOf("主演:"); |
||||
|
if (directorEnd == -1) { |
||||
|
directorEnd = info.indexOf(" "); |
||||
|
// 找到第一个数字年份的位置
|
||||
|
for (int i = 0; i < info.length(); i++) { |
||||
|
if (Character.isDigit(info.charAt(i))) { |
||||
|
directorEnd = i; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
if (directorEnd != -1) { |
||||
|
movie.setDirector(info.substring(directorStart, directorEnd).trim()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 提取主演
|
||||
|
if (info.contains("主演:")) { |
||||
|
int actorsStart = info.indexOf("主演:") + 3; |
||||
|
int actorsEnd = info.length(); |
||||
|
// 找到第一个数字年份的位置
|
||||
|
for (int i = actorsStart; i < info.length(); i++) { |
||||
|
if (Character.isDigit(info.charAt(i))) { |
||||
|
actorsEnd = i; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
movie.setActors(info.substring(actorsStart, actorsEnd).trim()); |
||||
|
} |
||||
|
|
||||
|
// 提取年份、国家/地区和类型
|
||||
|
// 找到年份的开始位置(第一个数字)
|
||||
|
int yearStart = -1; |
||||
|
for (int i = 0; i < info.length(); i++) { |
||||
|
if (Character.isDigit(info.charAt(i))) { |
||||
|
yearStart = i; |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (yearStart != -1) { |
||||
|
// 提取年份(4位数字)
|
||||
|
if (yearStart + 4 <= info.length()) { |
||||
|
String year = info.substring(yearStart, yearStart + 4); |
||||
|
if (year.matches("\\d{4}")) { |
||||
|
movie.setYear(year); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 提取国家/地区和类型
|
||||
|
int slashIndex = info.indexOf("/", yearStart); |
||||
|
if (slashIndex != -1) { |
||||
|
// 提取国家/地区
|
||||
|
int nextSlashIndex = info.indexOf("/", slashIndex + 1); |
||||
|
if (nextSlashIndex != -1) { |
||||
|
String country = info.substring(slashIndex + 1, nextSlashIndex).trim(); |
||||
|
movie.setCountry(country); |
||||
|
|
||||
|
// 提取类型
|
||||
|
String genre = info.substring(nextSlashIndex + 1).trim(); |
||||
|
// 取第一个类型
|
||||
|
if (!genre.isEmpty()) { |
||||
|
String[] genres = genre.split(" "); |
||||
|
if (genres.length > 0) { |
||||
|
movie.setGenre(genres[0]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 简介
|
||||
|
Element quoteElement = item.selectFirst(".inq"); |
||||
|
if (quoteElement != null) { |
||||
|
movie.setQuote(quoteElement.text().trim()); |
||||
|
} |
||||
|
|
||||
|
// 过滤无效电影
|
||||
|
if (movie.getTitle() == null || movie.getTitle().isEmpty()) { |
||||
|
return null; |
||||
|
} |
||||
|
|
||||
|
return movie; |
||||
|
} catch (Exception e) { |
||||
|
e.printStackTrace(); |
||||
|
return null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,216 @@ |
|||||
|
package com.crawler.ui; |
||||
|
|
||||
|
import com.crawler.analysis.MovieAnalyzer; |
||||
|
import com.crawler.model.Movie; |
||||
|
import org.jfree.chart.ChartFactory; |
||||
|
import org.jfree.chart.ChartUtils; |
||||
|
import org.jfree.chart.JFreeChart; |
||||
|
import org.jfree.chart.plot.PlotOrientation; |
||||
|
import org.jfree.data.category.DefaultCategoryDataset; |
||||
|
import org.jfree.data.general.DefaultPieDataset; |
||||
|
import org.jfree.data.statistics.HistogramDataset; |
||||
|
import org.jfree.chart.plot.PiePlot; |
||||
|
import org.jfree.chart.labels.StandardPieSectionLabelGenerator; |
||||
|
import java.text.DecimalFormat; |
||||
|
import java.text.NumberFormat; |
||||
|
|
||||
|
import java.io.File; |
||||
|
import java.io.IOException; |
||||
|
import java.util.List; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
public class MovieResultDisplay { |
||||
|
// 控制台输出统计结果
|
||||
|
public static void displayResults(List<Movie> movieList) { |
||||
|
System.out.println("\n=== 电影数据统计结果 ==="); |
||||
|
System.out.println("爬取电影总数: " + movieList.size()); |
||||
|
|
||||
|
// 平均评分
|
||||
|
double averageRating = MovieAnalyzer.calculateAverageRating(movieList); |
||||
|
System.out.printf("平均评分: %.2f\n", averageRating); |
||||
|
|
||||
|
// 电影评分分布
|
||||
|
System.out.println("\n=== 电影评分分布 ==="); |
||||
|
Map<Double, Integer> ratingDistribution = MovieAnalyzer.analyzeRatingDistribution(movieList); |
||||
|
for (Map.Entry<Double, Integer> entry : ratingDistribution.entrySet()) { |
||||
|
System.out.printf("评分 %.1f: %d部\n", entry.getKey(), entry.getValue()); |
||||
|
} |
||||
|
|
||||
|
// 电影年份分布(最近20年)
|
||||
|
System.out.println("\n=== 电影年份分布(最近20年)==="); |
||||
|
Map<String, Integer> yearDistribution = MovieAnalyzer.analyzeYearDistribution(movieList); |
||||
|
int count = 0; |
||||
|
for (Map.Entry<String, Integer> entry : yearDistribution.entrySet()) { |
||||
|
if (count >= yearDistribution.size() - 20) { // 只显示最近20年
|
||||
|
System.out.printf("%s年: %d部\n", entry.getKey(), entry.getValue()); |
||||
|
} |
||||
|
count++; |
||||
|
} |
||||
|
|
||||
|
// 电影类型分布
|
||||
|
System.out.println("\n=== 电影类型分布 ==="); |
||||
|
Map<String, Integer> genreDistribution = MovieAnalyzer.analyzeGenreDistribution(movieList); |
||||
|
genreDistribution.entrySet().stream() |
||||
|
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed()) |
||||
|
.limit(10) // 只显示前10种类型
|
||||
|
.forEach(entry -> System.out.printf("%-10s: %d部\n", entry.getKey(), entry.getValue())); |
||||
|
|
||||
|
// 导演作品数量排行
|
||||
|
System.out.println("\n=== 导演作品数量排行 ==="); |
||||
|
Map<String, Integer> directorWorks = MovieAnalyzer.analyzeDirectorWorks(movieList); |
||||
|
count = 0; |
||||
|
for (Map.Entry<String, Integer> entry : directorWorks.entrySet()) { |
||||
|
if (count < 10) { // 只显示前10位导演
|
||||
|
System.out.printf("%-20s: %d部\n", entry.getKey(), entry.getValue()); |
||||
|
count++; |
||||
|
} else { |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 评分与年份相关性
|
||||
|
System.out.println("\n=== 评分与年份相关性 ==="); |
||||
|
Map<String, Double> yearRatingCorrelation = MovieAnalyzer.analyzeYearRatingCorrelation(movieList); |
||||
|
for (Map.Entry<String, Double> entry : yearRatingCorrelation.entrySet()) { |
||||
|
System.out.printf("%s年: 平均评分 %.2f\n", entry.getKey(), entry.getValue()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 生成电影评分分布直方图
|
||||
|
public static void generateRatingDistributionChart(List<Movie> movieList) throws IOException { |
||||
|
Map<Double, Integer> ratingDistribution = MovieAnalyzer.analyzeRatingDistribution(movieList); |
||||
|
DefaultCategoryDataset dataset = new DefaultCategoryDataset(); |
||||
|
|
||||
|
for (Map.Entry<Double, Integer> entry : ratingDistribution.entrySet()) { |
||||
|
dataset.addValue(entry.getValue(), "Count", entry.getKey().toString()); |
||||
|
} |
||||
|
|
||||
|
JFreeChart chart = ChartFactory.createBarChart( |
||||
|
"Movie Rating Distribution", |
||||
|
"Rating", |
||||
|
"Count", |
||||
|
dataset, |
||||
|
PlotOrientation.VERTICAL, |
||||
|
true, |
||||
|
true, |
||||
|
false |
||||
|
); |
||||
|
|
||||
|
ChartUtils.saveChartAsPNG(new File("movie_rating_distribution.png"), chart, 800, 600); |
||||
|
System.out.println("电影评分分布图表已保存为 movie_rating_distribution.png"); |
||||
|
} |
||||
|
|
||||
|
// 生成电影年份分布折线图
|
||||
|
public static void generateYearDistributionChart(List<Movie> movieList) throws IOException { |
||||
|
Map<String, Integer> yearDistribution = MovieAnalyzer.analyzeYearDistribution(movieList); |
||||
|
DefaultCategoryDataset dataset = new DefaultCategoryDataset(); |
||||
|
|
||||
|
System.out.println("年份分布数据:"); |
||||
|
for (Map.Entry<String, Integer> entry : yearDistribution.entrySet()) { |
||||
|
System.out.println("年份: '" + entry.getKey() + "', 数量: " + entry.getValue()); |
||||
|
// 尝试提取年份数字
|
||||
|
String year = entry.getKey(); |
||||
|
// 提取4位数字作为年份
|
||||
|
String yearMatch = year.replaceAll("[^0-9]", ""); |
||||
|
if (yearMatch.length() >= 4) { |
||||
|
yearMatch = yearMatch.substring(0, 4); |
||||
|
dataset.addValue(entry.getValue(), "Count", yearMatch); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
JFreeChart chart = ChartFactory.createLineChart( |
||||
|
"Movie Year Distribution", |
||||
|
"Year", |
||||
|
"Count", |
||||
|
dataset, |
||||
|
PlotOrientation.VERTICAL, |
||||
|
true, |
||||
|
true, |
||||
|
false |
||||
|
); |
||||
|
|
||||
|
ChartUtils.saveChartAsPNG(new File("movie_year_distribution.png"), chart, 800, 600); |
||||
|
System.out.println("电影年份分布图表已保存为 movie_year_distribution.png"); |
||||
|
} |
||||
|
|
||||
|
// 生成电影类型分布饼图
|
||||
|
public static void generateGenreDistributionChart(List<Movie> movieList) throws IOException { |
||||
|
Map<String, Integer> genreDistribution = MovieAnalyzer.analyzeGenreDistribution(movieList); |
||||
|
DefaultPieDataset dataset = new DefaultPieDataset(); |
||||
|
|
||||
|
// 只显示前10种类型
|
||||
|
genreDistribution.entrySet().stream() |
||||
|
.sorted(Map.Entry.<String, Integer>comparingByValue().reversed()) |
||||
|
.limit(10) |
||||
|
.forEach(entry -> { |
||||
|
// 使用英文标签避免中文显示问题
|
||||
|
String englishLabel = getEnglishGenre(entry.getKey()) + " (" + entry.getValue() + ")"; |
||||
|
dataset.setValue(englishLabel, entry.getValue()); |
||||
|
}); |
||||
|
|
||||
|
JFreeChart chart = ChartFactory.createPieChart( |
||||
|
"Movie Genre Distribution", // 使用英文标题
|
||||
|
dataset, |
||||
|
true, // 显示图例
|
||||
|
true, // 显示工具提示
|
||||
|
false // 不显示URL
|
||||
|
); |
||||
|
|
||||
|
ChartUtils.saveChartAsPNG(new File("movie_genre_distribution.png"), chart, 800, 600); |
||||
|
System.out.println("电影类型分布图表已保存为 movie_genre_distribution.png"); |
||||
|
} |
||||
|
|
||||
|
// 将中文类型转换为英文
|
||||
|
private static String getEnglishGenre(String chineseGenre) { |
||||
|
switch (chineseGenre) { |
||||
|
case "冒险": return "Adventure"; |
||||
|
case "奇幻": return "Fantasy"; |
||||
|
case "爱情": return "Romance"; |
||||
|
case "惊悚": return "Thriller"; |
||||
|
case "动画": return "Animation"; |
||||
|
case "悬疑": return "Mystery"; |
||||
|
case "家庭": return "Family"; |
||||
|
case "犯罪": return "Crime"; |
||||
|
case "同性": return "LGBTQ+"; |
||||
|
case "历史": return "History"; |
||||
|
case "剧情": return "Drama"; |
||||
|
case "动作": return "Action"; |
||||
|
case "喜剧": return "Comedy"; |
||||
|
case "科幻": return "Sci-Fi"; |
||||
|
default: return chineseGenre; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 生成评分与年份相关性图表
|
||||
|
public static void generateYearRatingChart(List<Movie> movieList) throws IOException { |
||||
|
Map<String, Double> yearRatingCorrelation = MovieAnalyzer.analyzeYearRatingCorrelation(movieList); |
||||
|
DefaultCategoryDataset dataset = new DefaultCategoryDataset(); |
||||
|
|
||||
|
System.out.println("评分与年份相关性数据:"); |
||||
|
for (Map.Entry<String, Double> entry : yearRatingCorrelation.entrySet()) { |
||||
|
System.out.println("年份: '" + entry.getKey() + "', 平均评分: " + entry.getValue()); |
||||
|
// 尝试提取年份数字
|
||||
|
String year = entry.getKey(); |
||||
|
// 提取4位数字作为年份
|
||||
|
String yearMatch = year.replaceAll("[^0-9]", ""); |
||||
|
if (yearMatch.length() >= 4) { |
||||
|
yearMatch = yearMatch.substring(0, 4); |
||||
|
dataset.addValue(entry.getValue(), "Avg Rating", yearMatch); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
JFreeChart chart = ChartFactory.createLineChart( |
||||
|
"Year vs Rating Correlation", |
||||
|
"Year", |
||||
|
"Average Rating", |
||||
|
dataset, |
||||
|
PlotOrientation.VERTICAL, |
||||
|
true, |
||||
|
true, |
||||
|
false |
||||
|
); |
||||
|
|
||||
|
ChartUtils.saveChartAsPNG(new File("movie_year_rating.png"), chart, 800, 600); |
||||
|
System.out.println("评分与年份相关性图表已保存为 movie_year_rating.png"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,91 @@ |
|||||
|
package com.crawler.utils; |
||||
|
|
||||
|
import com.crawler.model.Movie; |
||||
|
|
||||
|
import java.io.FileWriter; |
||||
|
import java.io.IOException; |
||||
|
import java.util.List; |
||||
|
|
||||
|
public class DataUtils { |
||||
|
// 清洗电影数据
|
||||
|
public static Movie cleanMovie(Movie movie) { |
||||
|
if (movie == null) return null; |
||||
|
|
||||
|
// 清洗标题
|
||||
|
if (movie.getTitle() != null) { |
||||
|
movie.setTitle(movie.getTitle().trim().replaceAll("\\s+", " ")); |
||||
|
} |
||||
|
|
||||
|
// 清洗导演
|
||||
|
if (movie.getDirector() != null) { |
||||
|
movie.setDirector(movie.getDirector().trim()); |
||||
|
} |
||||
|
|
||||
|
// 清洗演员
|
||||
|
if (movie.getActors() != null) { |
||||
|
movie.setActors(movie.getActors().trim()); |
||||
|
} |
||||
|
|
||||
|
// 清洗年份
|
||||
|
if (movie.getYear() != null) { |
||||
|
movie.setYear(movie.getYear().trim()); |
||||
|
} |
||||
|
|
||||
|
// 清洗国家/地区
|
||||
|
if (movie.getCountry() != null) { |
||||
|
movie.setCountry(movie.getCountry().trim()); |
||||
|
} |
||||
|
|
||||
|
// 清洗类型
|
||||
|
if (movie.getGenre() != null) { |
||||
|
movie.setGenre(movie.getGenre().trim()); |
||||
|
} |
||||
|
|
||||
|
// 清洗简介
|
||||
|
if (movie.getQuote() != null) { |
||||
|
movie.setQuote(movie.getQuote().trim().replaceAll("\\s+", " ")); |
||||
|
} |
||||
|
|
||||
|
return movie; |
||||
|
} |
||||
|
|
||||
|
// 写入电影数据到CSV文件
|
||||
|
public static void writeMovieToCSV(List<Movie> movieList, String filePath) throws IOException { |
||||
|
// 添加时间戳避免文件冲突
|
||||
|
String timestamp = String.valueOf(System.currentTimeMillis()); |
||||
|
String actualFilePath = filePath.replace(".csv", "_" + timestamp + ".csv"); |
||||
|
|
||||
|
FileWriter writer = new FileWriter(actualFilePath); |
||||
|
// 写入表头
|
||||
|
writer.write("排名,标题,评分,评价人数,导演,演员,年份,国家/地区,类型,简介\n"); |
||||
|
|
||||
|
// 写入数据
|
||||
|
for (Movie movie : movieList) { |
||||
|
if (movie != null) { |
||||
|
writer.write(movie.getRank() + ","); |
||||
|
writer.write(escapeCsv(movie.getTitle()) + ","); |
||||
|
writer.write(movie.getRating() + ","); |
||||
|
writer.write(movie.getRatingPeople() + ","); |
||||
|
writer.write(escapeCsv(movie.getDirector()) + ","); |
||||
|
writer.write(escapeCsv(movie.getActors()) + ","); |
||||
|
writer.write(escapeCsv(movie.getYear()) + ","); |
||||
|
writer.write(escapeCsv(movie.getCountry()) + ","); |
||||
|
writer.write(escapeCsv(movie.getGenre()) + ","); |
||||
|
writer.write(escapeCsv(movie.getQuote()) + "\n"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
writer.close(); |
||||
|
System.out.println("数据已保存到 " + actualFilePath); |
||||
|
} |
||||
|
|
||||
|
// 转义CSV特殊字符
|
||||
|
private static String escapeCsv(String value) { |
||||
|
if (value == null) return ""; |
||||
|
if (value.contains(",") || value.contains("\"")) { |
||||
|
value = value.replaceAll("\"", "\"\""); |
||||
|
return "\"" + value + "\""; |
||||
|
} |
||||
|
return value; |
||||
|
} |
||||
|
} |
||||