Compare commits
9 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
22bfc89b1c | 4 days ago |
|
|
84a1b42f7e | 1 week ago |
|
|
58b4a05a8b | 2 weeks ago |
|
|
9df77bfcb4 | 3 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; |
|||
} |
|||
} |
|||