1. 用户如何自定义异常?编程实现一个用户自定义异常。
以下是一个示例代码,展示了如何创建一个自定义异常类:
class CustomException extends Exception {
public CustomException(String message) {
super(message);
}
}
public class homework2_1_V1 {
public static void main(String[] args) {
try {
throw new CustomException("This is a custom exception.");
} catch (CustomException e) {
System.out.println("Caught custom exception: " + e.getMessage());
}
}
}
在Java中,用户可以通过扩展`Exception`类或其子类来创建自定义异常。
在上述示例中,我们创建了一个名为 `CustomException` 的自定义异常类,它扩展了`Exception`类。我们在自定义异常类中添加了一个构造函数,用于传递异常消息。
在 `Main` 类的 `main()` 方法中,我们使用 `throw` 关键字抛出一个 `CustomException` 异常对象,并在 `catch` 块中捕获并处理该异常。最后,我们打印出异常的消息。
可以根据自己的需求来定义自定义异常类,并在适当的地方使用 `throw` 语句抛出异常。
2. 设计一个异常处理,如果程序出错(或者说捕获到任何异常)就重新启动整个程序。
public class Main {
static int restartTime = 0; // 避免无尽循环重启,重启一次后不再继续重启
public static void main(String[] args) {
boolean restart = true;
while (restart) {
try {
// 运行你的程序代码
yourProgramLogic();
// 如果程序执行成功,退出循环
restart = false;
} catch (Exception e) {
System.out.println("Exception caught: " + e.getMessage());
// 清理资源
// 如果程序使用了文件、数据库连接或其他资源,应该在捕获异常后关闭这些资源,以确保不会出现资源泄漏或错误状态。
// 写在此处
// 重新启动程序的逻辑
System.out.println("Restarting the program...");
// 可以选择添加延迟时间或其他重启逻辑
try {
Thread.sleep(1000); // 休眠1秒
} catch (InterruptedException ex) {
ex.printStackTrace();
}
// 重新调用main方法,实现程序重新启动
restartProgram();
}
}
}
// 定义重新启动程序的方法
public static void restartProgram() {
// 调用 main 方法重新启动程序
main(new String[]{});
}
// 定义你的程序逻辑方法
public static void yourProgramLogic() throws Exception {
System.out.println("Program start!");
if (restartTime == 0) {
restartTime++;
throw new CustomException("This is a custom exception.");
}
// 在这里编写你的程序逻辑
// 如果发生异常,将被捕获并重新启动程序
}
}
使用一个 while
循环来包装主程序代码。循环的条件是 restart
变量的值,初始设为 true
。当程序成功执行完毕时,将设置 restart
为 false
,退出循环。(面对真实工作场景下的异常应当这样设计)
在 try
块中,你可以编写你的程序逻辑代码。如果在执行过程中发生了异常,将被 catch
块捕获,并执行重新启动的逻辑。在本例中,重新启动逻辑简单地打印一条消息,并在捕获异常后添加了1秒的延迟时间。
在上述示例中,我们将重新启动的逻辑放在 catch
块中。当捕获到异常时,我们打印出异常消息,并调用 main(args)
方法来重新启动程序。这里使用了递归调用,使程序能够无限重新启动。
为了使程序能够重新启动,确保在重新启动时传递正确的命令行参数(如果有)。在上述示例中,我们将原始的命令行参数 args
传递给重新启动的 main
方法。
这种方法可能会导致无限循环的重新启动,因此实际使用时,确保程序逻辑在某些条件下能够终止循环,否则可能会导致程序无限重新启动。
3. 编写10个线程,第一个线程从1加到10,第二个线程从11加20…第十个线程从91加到100,最后再把10个线程结果相加。
线程(Thread)是操作系统能够进行运算调度的最小单位,也是程序执行的最小单元。简单来说,线程是一条独立的执行路径,可以并发执行多条线程,从而实现程序的并发性。
线程的出现主要是为了解决多任务并发执行的需求。在单核处理器的时代,多线程可以通过在同一个处理器上快速切换执行,实现多个任务的同时执行,提高程序的效率。而在多核处理器的时代,多线程可以充分利用多核处理器的计算能力,实现并行计算,进一步提高程序的性能。
- 线程的创建和启动:可以通过继承Thread类或实现Runnable接口来创建线程,并使用start()方法启动线程。
- 线程的执行:在线程的run()方法中定义线程的具体执行逻辑。
- 线程同步:在多线程环境下,可能会出现资源竞争的问题,需要使用同步机制来保证线程安全。
- 线程间的通信:可以使用wait()和notify()方法实现线程之间的通信,以便协调它们的执行顺序或共享数据。
public class Main {
public static void main(String[] args) {
// 创建一个共享的结果变量
Result result = new Result();
// 创建10个线程,并启动它们
for (int i = 0; i < 10; i++) {
// 计算每个线程要处理的数值范围
int start = i * 10 + 1;
int end = (i + 1) * 10;
// 创建线程,并传入结果变量和数值范围
Thread thread = new AddThread(result, start, end);
// 启动线程
thread.start();
}
// 等待所有线程执行完毕
try {
for (int i = 0; i < 10; i++) {
Thread thread = AddThread.threads[i];
thread.join();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印最终的结果
System.out.println("Sum: " + result.getSum());
}
// 定义共享的结果类
// 创建了一个Result类来保存最终的结果
static class Result {
private int sum;
public synchronized void addToSum(int value) {
sum += value;
}
public int getSum() {
return sum;
}
}
// 定义每个线程的逻辑
// AddThread类继承自Thread,并在run()方法中实现了每个线程的加法逻辑。
static class AddThread extends Thread {
private Result result;
private int start;
private int end;
public static Thread[] threads = new Thread[10]; // threads数组用于保存每个线程的引用,以便在main方法中等待所有线程执行完毕。
public AddThread(Result result, int start, int end) {
this.result = result;
this.start = start;
this.end = end;
threads[start/10] = this;
}
@Override
public void run() {
int sum = 0;
for (int i = start; i <= end; i++) {
sum += i;
}
result.addToSum(sum);
}
}
}
创建了一个Result
类来保存最终的结果。AddThread
类继承自Thread
,并在run()
方法中实现了每个线程的加法逻辑。threads
数组用于保存每个线程的引用,以便在main
方法中等待所有线程执行完毕。
4. 选做:模拟三个窗口同时卖电影票的程序,电影票有第几排第几号两个参数,接受单张和联票(就是2-10张连续座位的票)两种订单。自行设计订单来验证你的程序的正确性。
import java.util.concurrent.locks.ReentrantLock;
public class Main {
private static final int TOTAL_SEATS = 100;
private static final int WINDOW_COUNT = 3;
private static final int MAX_TICKETS_PER_ORDER = 10;
private static int[] seats = new int[TOTAL_SEATS];
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
// 初始化座位
for (int i = 0; i < TOTAL_SEATS; i++) {
seats[i] = i + 1;
}
// 创建三个窗口线程
for (int i = 0; i < WINDOW_COUNT; i++) {
int windowId = i + 1;
Thread windowThread = new Thread(() -> {
while (true) {
int ticketCount = getRandomTicketCount();
try {
sellTickets(windowId, ticketCount);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
windowThread.start();
}
}
private static void sellTickets(int windowId, int ticketCount) throws InterruptedException {
// 锁(lock)是用于实现线程同步的机制,它可以确保多个线程在访问共享资源(如 seats 数组)时的互斥性。
lock.lock(); // 获取锁
try {
if (ticketCount < MAX_TICKETS_PER_ORDER && ticketCount > 1) {
// 处理联票订单
int consecutiveSeats = findConsecutiveSeats(ticketCount);
if (consecutiveSeats != -1) {
for (int i = consecutiveSeats; i < consecutiveSeats + ticketCount; i++) {
seats[i] = 0; // 表示座位已售出
}
System.out.println("Window " + windowId + " sold " + ticketCount + " consecutive seats starting from Seat " + (consecutiveSeats + 1));
} else {
System.out.println("Window " + windowId + " failed to sell " + ticketCount + " consecutive seats");
}
} else {
// 处理单张票订单
int availableSeat = findAvailableSeat();
if (availableSeat != -1) {
seats[availableSeat] = 0; // 表示座位已售出
System.out.println("Window " + windowId + " sold single ticket for Seat " + (availableSeat + 1));
} else {
System.out.println("Window " + windowId + " failed to sell single ticket");
// 程序停止后的清理工作
System.out.println("All tickets have been sold. Program stopped.");
System.exit(0);
}
}
// 无论是否发生异常,finally 块中的 lock.unlock() 语句都会被执行。防止出现死锁问题
} finally {
// 无论是否发生异常,都会通过 lock.unlock() 方法释放锁,使其他等待的线程有机会获取锁并执行相应的操作。
lock.unlock(); // 释放锁
}
if (ticketCount > 1) {
Thread.sleep(5000); // 模拟卖联票过程耗时
} else {
Thread.sleep(3000); // 模拟卖独票过程耗时
}
}
private static int findAvailableSeat() {
for (int i = 0; i < TOTAL_SEATS; i++) {
if (seats[i] != 0) {
return i;
}
}
return -1; // 没有可用座位
}
private static int findConsecutiveSeats(int ticketCount) {
int consecutiveCount = 0;
for (int i = 0; i < TOTAL_SEATS; i++) {
if (seats[i] != 0) {
consecutiveCount++;
if (consecutiveCount == ticketCount) {
return i - ticketCount + 1;
}
} else {
consecutiveCount = 0;
}
}
return -1; // 没有足够连续的座位
}
private static int getRandomTicketCount() {
return (int) (Math.random() * MAX_TICKETS_PER_ORDER) + 1; // 得到1~10之间的一个数
}
}
这个程序使用一个整型数组 seats
来表示座位状态,初始时每个座位的值为座位号。当座位被售出时,将其值设为0。我们使用 ReentrantLock
锁来确保多个线程操作座位数据的互斥性。
程序中创建了三个窗口线程,每个窗口线程不断尝试售票,直到程序终止。当窗口线程售出票时,根据订单的要求选择单张票或联票,并通过 findAvailableSeat()
和 findConsecutiveSeats()
方法找到可用的座位。如果找到座位,则将其标记为已售出,否则标记为售票失败。
实现2:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class homework2_4_V2 {
public static void main(String[] args) {
// 创建电影票对象
Ticket ticket = new Ticket();
// 创建三个线程模拟三个售票窗口
TicketWindow window1 = new TicketWindow(ticket, "窗口1");
TicketWindow window2 = new TicketWindow(ticket, "窗口2");
TicketWindow window3 = new TicketWindow(ticket, "窗口3");
// 启动线程
window1.start();
window2.start();
window3.start();
}
}
class Ticket {
// 电影票总数
private int total = 100;
// 记录已售出的座位
private Map<String, Boolean> seats = new HashMap<>();
public Ticket() {
// 初始化座位情况
for (int i = 1; i <= 10; i++) {
for (int j = 1; j <= 10; j++) {
String seat = i + "-" + j;
seats.put(seat, false);
}
}
}
// 售票单张方法
public void sell(String seat) throws InterruptedException {
synchronized (this) {
if (seats.containsKey(seat) && !seats.get(seat)) {
seats.put(seat, true);
System.out.println(Thread.currentThread().getName() + " 售出电影票:" + seat);
total--;
Thread.sleep(1000);
} else {
System.out.println(Thread.currentThread().getName() + " 该座位已售出或不存在:" + seat);
}
}
}
// 批量售票方法
public void sellBatch(List<String> seats) throws InterruptedException {
synchronized (this) {
boolean canSell = true;
for (String seat : seats) {
if (!this.seats.containsKey(seat) || this.seats.get(seat)) {
canSell = false;
break;
}
}
if (canSell) {
for (String seat : seats) {
this.seats.put(seat, true);
System.out.println(Thread.currentThread().getName() + " 售出电影票:" + seat);
total--;
Thread.sleep(1000);
}
} else {
System.out.println(Thread.currentThread().getName() + " 连续座位不可售卖或不存在:" + seats);
}
}
}
public int getTotal() {
return total;
}
}
class TicketWindow extends Thread {
private Ticket ticket;
public TicketWindow(Ticket ticket, String name) {
super(name);
this.ticket = ticket;
}
@Override
public void run() {
while (true) {
// 随机生成一个订单类型(单张票或连续座位票)
boolean isSingle = Math.random() >= 0.5;
if (isSingle) {
// 单张票订单
int row = (int) (Math.random() * 10) + 1;
int col = (int) (Math.random() * 10) + 1;
String seat = row + "-" + col;
try {
ticket.sell(seat);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
// 连续座位票订单
int row = (int) (Math.random() * 10) + 1;
int startCol = (int) (Math.random() * 6) + 1;
List<String> seats = new ArrayList<>();
for (int i = startCol; i < startCol + 5; i++) {
seats.add(row + "-" + i);
}
try {
ticket.sellBatch(seats);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 如果票已经卖完,则结束售票
if (ticket.getTotal() == 0) {
System.out.println("电影票已售空," + Thread.currentThread().getName() + " 停止售票。");
break;
}
}
}
}
key1:强制类型转换
在Java中,对浮点数进行强制类型转换时,使用的是向下取整的规则。这意味着小数部分会被舍弃,只保留整数部分。
例如,对于表达式 (int) 3.9
,结果将是3,而不是4。同样地,(int) -2.7
的结果将是-2。
需要注意的是,强制类型转换只会舍弃小数部分,并不会进行四舍五入。如果想要进行四舍五入,可以使用 Math.round()
方法或其他舍入方法来实现。
key2:锁
锁(lock
)是用于实现线程同步的机制,它可以确保多个线程在访问共享资源(如 seats
数组)时的互斥性。
在多线程环境下,如果多个线程同时访问共享资源,可能会导致数据不一致或者竞态条件的问题。为了避免这些问题,需要使用锁来保证在任意时刻只有一个线程可以访问共享资源。
在给定的代码中,lock
对象是一个互斥锁(Mutex Lock),它使用了Java中的Lock接口的实现类,如 ReentrantLock
。通过调用 lock.lock()
方法获取锁,可以确保只有一个线程可以进入临界区(即 try
代码块)。
当一个线程成功获取到锁后,它会执行相应的票务销售操作,包括处理联票订单和单张票订单。在操作完成后,无论是否发生异常,都会通过 lock.unlock()
方法释放锁,使其他等待的线程有机会获取锁并执行相应的操作。
使用锁的目的是保证在同一时间只有一个线程能够修改共享资源,避免了竞态条件和数据不一致的问题。通过锁的机制,可以确保多线程环境下的票务销售操作是线程安全的。
`ReentrantLock`是Java中的一个线程同步机制,它提供了更灵活和功能强大的锁定操作,相比于传统的`synchronized`关键字,它具有更多的特性和可扩展性。
`ReentrantLock`实现了`Lock`接口,它允许线程以互斥的方式访问共享资源。与`synchronized`关键字不同,`ReentrantLock`提供了显式的锁定和解锁操作,需要手动地获取和释放锁。
以下是`ReentrantLock`的一些重要特性:
1. **重入性**:与`synchronized`类似,`ReentrantLock`允许线程多次获得同一个锁,这称为重入性。线程每次获得锁时,必须相应地释放相同次数的锁,否则其他线程将无法获得该锁。
2. **公平性**:`ReentrantLock`可以以公平或非公平的方式进行锁的获取。在公平模式下,锁将按照请求的顺序分配给等待的线程。而在非公平模式下,锁的获取是无序的,可能会导致某些线程长时间等待,从而引发饥饿问题。默认情况下,`ReentrantLock`是非公平的,但可以通过构造函数参数来设置为公平模式。
3. **条件变量**:`ReentrantLock`提供了与锁关联的条件变量,即`Condition`。条件变量可以让线程在某个特定条件下等待或被唤醒。通过调用`newCondition()`方法可以创建与锁相关联的条件变量。
4. **可中断的获取锁**:`ReentrantLock`支持可中断的获取锁操作。在某个线程等待锁的过程中,如果其他线程中断了等待线程,等待线程将会被唤醒,并且可以通过捕获`InterruptedException`来响应中断。
5. **性能优化**:相比于`synchronized`,`ReentrantLock`提供了更高级别的性能优化。例如,它支持可重入性、非阻塞的尝试获取锁操作、可轮询的尝试获取锁操作等。
使用`ReentrantLock`时,通常的模式是在`try...finally`块中获取和释放锁,以确保锁始终被正确地释放。它提供了更多的灵活性和功能,可以更细粒度地控制线程的同步和互斥访问。
key3:结束程序运行
在Java中,要结束程序的运行,可以使用以下方法之一:
1. 使用 System.exit(int status)`方法:该方法立即终止正在运行的Java虚拟机。参数 status是程序的退出状态码,通常使用0表示正常退出,非零值表示异常终止。例如,调用 System.exit(0)将正常结束程序的运行。
// 终止程序的运行并返回退出状态码0
System.exit(0);
2. 使用 `return` 语句:如果你的程序位于 `main` 方法中,你可以直接使用 `return` 语句来结束程序的运行。程序执行到 `return` 语句时,将立即返回到调用 `main` 方法的地方,并结束程序的执行。
// 终止程序的运行
return;
无论是使用 `System.exit()` 方法还是 `return` 语句,都可以用来终止程序的运行。选择哪种方式取决于你的具体需求和程序的结构。一般来说,如果你只是想结束当前方法的执行,可以使用 `return` 语句;如果你想立即终止整个程序的运行,可以使用 `System.exit()` 方法。
5. 使用多线程实现多个文件同步复制功能,并在控制台显示复制的进度,进度以百分比表示。例如:把文件A复制到E盘某文件夹下,在控制台上显示“XXX文件已复制10%”,“XXX文件已复制20%”……“XXX文件已复制100%”,“XXX复制完成!”
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class homework2_5_V1 extends Thread {
private File sourceFile;
private File destinationFile;
public homework2_5_V1(File sourceFile, File destinationFile) {
this.sourceFile = sourceFile;
this.destinationFile = destinationFile;
}
@Override
public void run() {
try {
FileInputStream fis = new FileInputStream(sourceFile);
FileOutputStream fos = new FileOutputStream(destinationFile);
byte[] buffer = new byte[1024];
int bytesRead;
long totalBytesRead = 0;
long fileSize = sourceFile.length();
while ((bytesRead = fis.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
totalBytesRead += bytesRead;
double progress = (double) totalBytesRead / fileSize * 100;
System.out.println(sourceFile.getName() + " 已复制 " + String.format("%.2f", progress) + "%");
}
fis.close();
fos.close();
System.out.println(sourceFile.getName() + " 复制完成!");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 源文件路径
String sourceFolderPath = "/Users/xiangri/Desktop/source";
// 目标文件夹路径
String destinationFolderPath = "/Users/xiangri/Desktop/destination";
// 创建源文件夹和目标文件夹
File sourceFolder = new File(sourceFolderPath);
File destinationFolder = new File(destinationFolderPath);
destinationFolder.mkdirs();
// 获取源文件夹中的所有文件
File[] files = sourceFolder.listFiles();
if (files != null) {
for (File file : files) {
// 构造目标文件路径
String destinationFilePath = destinationFolderPath + "/" + file.getName();
// 创建并启动复制线程
homework2_5_V1 copyThread = new homework2_5_V1(file, new File(destinationFilePath));
copyThread.start();
}
}
}
}
上述代码创建了一个 FileCopyThread
类,继承自 Thread
类,用于实现文件的复制。在 run
方法中,通过输入流和输出流复制文件,并计算复制的进度,并在控制台打印进度信息。
在 main
方法中,你可以指定源文件夹路径和目标文件夹路径,并创建复制线程进行文件的同步复制。每个文件对应一个复制线程,可以并发进行复制操作。
FileCopyThread
类继承自Thread
类,用于实现文件的复制。run
方法是线程的主要执行逻辑。在该方法中,通过输入流和输出流复制文件。使用一个缓冲区buffer
来读取和写入文件内容。- 在复制的过程中,使用
totalBytesRead
记录已复制的字节数,并通过计算已复制的字节数与文件总大小的比例,得到复制的进度。 - 进度以百分比的形式打印在控制台上,使用
String.format
方法保留两位小数。 - 复制完成后,关闭输入流和输出流,并在控制台上显示文件复制完成的消息。
main
方法是程序的入口。在该方法中,指定了源文件夹路径和目标文件夹路径,并创建了对应的文件对象。- 如果源文件夹存在,并且其中有文件,就遍历每个文件,构造目标文件的路径,并创建并启动对应的复制线程。
- 每个复制线程都负责复制一个文件,实现多个文件的同步复制。
- 程序结束后,所有文件都会被复制到指定的目标文件夹中,并在控制台上显示复制的进度信息和完成消息。
注意:代码中使用的是绝对路径。
6. 选做:编写一个使用TCP协议进行网络通信的程序,双方建立连接后,各自将一个本地文件传送到对方并各自保存到本地。
服务器端代码:
import java.io.*;
import java.net.*;
public class homework2_6_Server {
public static void main(String[] args) {
try {
// 创建服务器套接字,监听指定端口
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服务器已启动,等待客户端连接...");
// 接受客户端连接
Socket socket = serverSocket.accept();
System.out.println("客户端已连接");
// 接收客户端发送的文件
InputStream inputStream = socket.getInputStream();
FileOutputStream fileOutputStream = new FileOutputStream("server_received_file.txt");
// 读取客户端发送的数据
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
fileOutputStream.write(buffer, 0, bytesRead);
}
// 关闭连接
fileOutputStream.close();
inputStream.close();
socket.close();
System.out.println("文件接收完成");
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端代码:
import java.io.*;
import java.net.*;
public class homework2_6_Client {
public static void main(String[] args) {
try {
// 创建套接字,连接服务器
Socket socket = new Socket("127.0.0.1", 9999);
System.out.println("已连接到服务器");
// 发送本地文件到服务器
// 获取输入流和输出流
File file = new File("/Users/xiangri/Documents/A-Personal/A学习/北邮课程/2023SS/OO-Java/作业/OJ1,2,3/JavaOJ/src/JavaHomework/Homework02/toBeSent.txt");
FileInputStream fileInputStream = new FileInputStream(file);
OutputStream outputStream = socket.getOutputStream();
// 向服务器发送数据
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fileInputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
// 读取服务器响应数据
// 写在这里
// 关闭连接
outputStream.close();
fileInputStream.close();
socket.close();
System.out.println("文件发送完成");
} catch (IOException e) {
e.printStackTrace();
}
}
}
-
服务器端:
- 服务器端首先需要创建一个服务器套接字,它将监听指定的端口,等待客户端的连接请求。
- 一旦服务器套接字接收到连接请求,它将接受连接并创建一个新的套接字,与客户端建立通信。
- 服务器端通过该套接字进行数据的发送和接收。
- 服务器端可以使用多线程或异步编程的方式,以支持同时处理多个客户端连接。
-
客户端:
- 客户端首先创建一个套接字,并指定服务器的IP地址和端口号,以便与服务器建立连接。
- 客户端通过套接字连接到服务器,并建立一个通信通道。
- 客户端可以通过套接字发送数据给服务器,并接收服务器发送的数据。
7. 编写一个列出某目录下所有子目录及文件的java程序(参照dos命令dir /s的显示结果)。
import java.io.File;
public class homework2_7_V1 {
public static void listDirectory(String path) {
File directory = new File(path);
if (!directory.exists()) {
System.out.println("目录不存在:" + path);
return;
}
System.out.println("目录:" + path);
// 递归遍历目录
recursiveList(directory, 0);
}
private static void recursiveList(File directory, int depth) {
// 获取当前目录下的子目录和文件
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
// 输出文件或子目录的缩进
for (int i = 0; i < depth; i++) {
System.out.print(" ");
}
// 输出文件名或目录名
System.out.println(file.getName());
// 如果是目录,递归遍历子目录
if (file.isDirectory()) {
recursiveList(file, depth + 1);
}
}
}
}
public static void main(String[] args) {
// 指定目录路径
String directoryPath = "/Users/xiangri/Desktop";
// 列出目录及子目录和文件
listDirectory(directoryPath);
}
}
listDirectory
方法用于列出指定目录及其子目录和文件。它接受一个目录路径作为参数。- 首先检查目录是否存在,如果不存在则输出相应的消息并返回。
- 然后输出当前目录的路径。
- 接着调用
recursiveList
方法来递归遍历目录。 recursiveList
方法用于递归地列出目录下的子目录和文件。它接受一个目录对象和当前的深度作为参数。- 首先获取当前目录下的子目录和文件。
- 遍历每个子目录和文件,根据深度输出相应的缩进。
- 输出子目录和文件的名称。
- 如果是目录,递归调用
recursiveList
方法继续遍历子目录。
在 main
方法中,你可以修改 directoryPath
变量的值来指定要列出的目录路径。运行程序后,它将输出指定目录及其子目录和文件的列表。
8. 配置一个Web服务器并建立JSP网站并显示当前时间。
选做:连接到天气预报网站获取天气预报信息并显示在网站上。
配置一个Web服务器并建立JSP网站并显示当前时间涉及到以下几个步骤:
1. 安装并配置Web服务器:首先,你需要选择一个Java Web服务器,比如Apache Tomcat。下载并安装Tomcat,并根据官方文档进行配置。
1.1 下载Tomcat:访问Apache Tomcat官方网站(https://tomcat.apache.org/)并下载适合你操作系统的Tomcat版本。通常会有两个版本可供选择:一个是完整版(Full)包含所有的JAR文件,另一个是仅包含运行时必需的文件(Core)。
1.2 在Apache Tomcat官方网站的左侧边栏Download模块->选择想要下载的版本->Binary Distributions->Core->tar.gz(pgp,sha512) (Mac用户选此,Windows用户可以选择对应的zip格式)
1.3 安装Tomcat:下载完Tomcat后,解压缩压缩包到你想要安装Tomcat的目录。注意,你需要有足够的权限来在目标目录中创建文件和目录。
1.4 配置环境变量(可选):如果你希望在命令行中直接运行Tomcat命令,可以配置Tomcat的环境变量。将Tomcat的安装目录路径添加到系统的PATH环境变量中。
1.5 启动Tomcat:进入Tomcat的安装目录,找到`bin`目录,然后运行`startup.bat`(Windows)或`startup.sh`(Unix/Linux)文件来启动Tomcat服务器。等待片刻,你应该会在控制台上看到一些启动日志。
打开Terminal,输入下列代码以启动Tomcat
./startup.sh
1.6 访问Tomcat主页:打开你的Web浏览器,并在地址栏中输入`http://localhost:8080`。如果一切正常,你应该能够看到Tomcat的默认主页。
至此,你已经成功安装和启动了Apache Tomcat。
请注意,以上步骤仅提供了一个基本的安装和配置Tomcat的指南。对于更详细的配置和特定需求,你可以参考Apache Tomcat官方文档,其中包含了更多的详细信息和配置选项。
一旦你成功安装和启动了Apache Tomcat,它会一直运行在你的计算机上,直到你主动停止它或者重启你的计算机。在正常情况下,你不需要每次使用完Tomcat后手动关闭它。
2. 创建JSP文件:在Tomcat的Web应用程序目录下,创建一个新的文件夹,用于存放你的JSP文件。在该文件夹中创建一个名为`index.jsp`的文件。
2.1 导航到Tomcat的安装目录,进入Tomcat的webapps目录:在Tomcat目录中,有一个名为webapps的目录,这是用于存放Web应用程序的地方。
2.2 创建一个新的Web应用程序目录,使用mkdir命令创建一个新的目录。即打开webapps文件夹,在这里创建一个新的文件夹,用于存放你的Web应用程序。
2.3 创建一个名为index.jsp的文件:使用文本编辑器(如vi或nano)创建一个名为index.jsp的文件,并添加以下内容:
在`index.jsp`文件中使用JSP的内置对象和标签来获取和显示当前时间。例如,可以使用`<%= new java.util.Date() %>`来显示当前时间。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Current Time</title>
</head>
<body>
<h1>Current Time</h1>
<p>当前时间:<%= new java.util.Date() %></p>
</body>
</html>
特别指出,上述步骤使用控制台也可以完成。步骤如下:
cd /usr/local/apache-tomcat-<version>/
cd webapps
mkdir myapp
cd myapp
2.1 打开终端应用程序:你可以在“应用程序”文件夹的“实用工具”文件夹中找到终端应用程序。
2.2 导航到Tomcat的安装目录:在终端中,使用`cd`命令导航到你安装Tomcat的目录。默认情况下,Tomcat在`/usr/local/`目录下。如果你使用了不同的安装路径,请相应地导航到该路径。
2.3 进入Tomcat的`webapps`目录:在Tomcat目录中,有一个名为`webapps`的目录,这是用于存放Web应用程序的地方。
2.4 创建一个新的Web应用程序目录:使用`mkdir`命令创建一个新的目录,用于存放你的Web应用程序。例如创建一个名为myapp的目录。
2.5 创建一个名为index.jsp
的文件:使用文本编辑器(如vi
或nano
)创建一个名为index.jsp
的文件,并添加前述HTML内容。
现在,你已经在Tomcat中创建了一个简单的JSP网站。该网站将显示当前时间。
3. 启动Web服务器:启动Tomcat服务器,并确保服务器已成功启动。
3.1 启动Tomcat服务器:
在Tomcat目录下的bin文件中,打开Terminal,输入下列指令以启动Tomcat服务器:
./startup.sh
这将启动Tomcat服务器,并将其运行在本地主机的默认端口(通常为8080)上。
3.2 打开Web浏览器,并在地址栏中输入以下URL:
http://localhost:8080/myapp/
这将打开你的JSP网站,并在浏览器中显示当前时间。
如果一切正常,你将在网页上看到标题为"Current Time"的页面,以及显示当前时间的文本。
3.3 当你完成查看后,可以停止Tomcat服务器,以释放系统资源。在终端中,使用以下命令停止Tomcat服务器:
./shutdown.sh
这将停止Tomcat服务器的运行。
选做部分(连接到天气预报网站获取天气预报信息并显示在网站上)涉及到网络请求和解析数据的操作。步骤如下:
1. 确定天气预报网站:选择一个可提供天气预报信息的网站,并查找其提供的API文档或数据接口。
1. 阅读OpenWeatherMap的API文档:访问OpenWeatherMap的官方网站并找到他们的API文档。仔细阅读文档以了解他们的API的功能、使用方法和可用的数据。
2. 确定你需要获取的天气信息:根据你的需求,确定你想要从OpenWeatherMap获取的具体天气信息。例如,你可以选择获取当前天气状况、温度、湿度、风速等。
2. 进行HTTP请求:使用Java的网络编程库(如HttpClient或HttpURLConnection),向天气预报网站发送HTTP请求,获取天气数据。
构造API请求:根据OpenWeatherMap的API文档,构造适当的API请求以获取所需的天气数据。这通常包括指定API密钥、目标城市或地理位置以及你希望获取的天气信息类型。
发送API请求并获取响应:使用Java的网络请求库(如HttpClient或HttpURLConnection)来发送构造的API请求。确保将API密钥添加到请求中进行身份验证。发送请求后,你将收到一个API响应。
示例数据结构:
buildApiUrl()
方法用于根据提供的参数构造API请求URL。在该方法中,我们将提供的参数拼接到基本的API URL字符串中,以获得完整的API请求URL。
sendApiRequest()
方法用于发送API请求并获取响应。在该方法中,我们首先创建一个URL
对象,将API请求URL传递给它。然后,我们打开一个HttpURLConnection
连接,并设置请求方法为GET。我们检查响应代码,如果响应代码为HTTP_OK(即200),则读取API响应的内容并将其存储在StringBuilder
中。最后,我们返回API响应的字符串表示。
在上述代码中的主方法中,我们使用实际的API密钥、纬度、经度和排除参数来构建API请求URL。然后,我们通过调用sendApiRequest()
方法发送API请求并获取响应。你可以在获取响应后进行进一步的处理,如解析JSON响应并提取所需的天气信息。
3. 解析天气数据:根据天气预报网站的API文档,了解返回的数据格式,并使用相应的JSON或XML解析库解析数据。解析后的数据将被转换为Java对象,以便在后续的处理中使用。
3.1 导入包
在使用 IntelliJ IDEA 导入Google Gson的这些依赖时,您可以按照以下步骤进行操作:
1. 打开 IntelliJ IDEA,并打开您的 Java 项目。
2. 在项目文件结构中,找到项目根目录下的 "lib" 或 "libs" 文件夹(如果没有该文件夹,可以手动创建一个)。
3. 在 "lib" 或 "libs" 文件夹中,将包含所需库的 JAR 文件拷贝到该文件夹中。在您的情况下,您需要拷贝 `gson.jar` 文件。
4. 返回 IntelliJ IDEA,右键单击项目根目录,然后选择 "Open Module Settings"(也可以使用快捷键 F4)。
5. 在弹出的 "Project Structure" 窗口中,选择 "Libraries" 选项卡。
6. 点击左上角的 "+"(加号)按钮,然后选择 "Java"。
7. 在文件选择对话框中,浏览到 "lib" 或 "libs" 文件夹,并选择您复制的 `gson.jar` 文件。
8. 确认选择后,点击 "OK" 关闭对话框。
现在,您的项目中应该成功导入了 Gson 库。您可以在您的 Java 代码中使用 `import` 语句导入相关类,例如:
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
这样,您就可以在代码中使用 Gson 库提供的类和方法了。
请注意,如果您使用的是 Maven 或 Gradle 等构建工具,您可以通过在项目的构建配置文件中添加 Gson 的依赖来实现更便捷的导入。构建工具会自动下载和管理依赖项。
3.2 解析JSON响应
我们使用Gson库来解析JSON响应。我们首先创建一个Gson
对象,然后使用fromJson()
方法将API响应的JSON字符串转换为JsonObject
对象。
然后通过JsonObject
的方法和属性来获取和解析JSON中的各个字段和值。代码在循环中进行处理获取当前天气、每小时天气和每日天气的相关信息,亦可根据自己的需求进一步解析其他字段。
最后,你可以在processApiResponse()
方法中进行进一步的处理和显示解析后的天气信息。
4. 在JSP页面中显示天气信息:将解析后的天气数据整合到JSP页面中,以便在网页上显示。使用HTML和CSS来设计和格式化天气信息的展示。
提取所需的天气信息:从解析后的API响应数据中提取你所需的天气信息。根据在第二步中确定的天气信息类型,获取相应的字段或属性。
5. 部署和运行更新后的JSP网站
将更新后的JSP网站部署到Tomcat服务器上,并启动服务器。通过访问相应的URL,查看你的JSP网站,并确保天气信息能够正确地显示在网页上。
快捷解决方案:
将基础部分2.3节所写HTML代码修改如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Weather Forcast</title>
<style>
body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
}
.weather-widget {
width: 415px;
height: auto;
margin-top: 20px;
}
</style>
</head>
<body>
<h1>The current time is: <%= new java.util.Date() %></h1>
<div class="weather-widget">
<iframe width="100%" scrolling="no" height="250" frameborder="0" allowtransparency="true" src="https://i.tianqi.com?c=code&id=13&icon=2&num=5&site=12&lang=zh"></iframe>
</div>
</body>
</html>
1. `<!DOCTYPE html>`: 文档类型定义,告诉浏览器这是一个 HTML5 文档。
2. `<html lang="en">`: 整个 HTML 文档的根元素。`lang="en"` 属性表示这个页面的内容是英文。
3. `<head>`: 包含页面的元数据(如字符集、标题、样式等)。
- `<meta charset="UTF-8">`: 定义字符集为 UTF-8,以支持多语言字符。
- `<title>Weather Forecast</title>`: 定义页面标题,显示在浏览器标签上。
- `<style>`: 包含 CSS 样式,用于设置页面元素的外观。在这个例子中,它设置了页面布局和 `.weather-widget` 类的样式。
4. `<body>`: 包含页面的实际内容,如文本、图像、链接等。
- `<h1>`: 一级标题。在这个例子中,它显示当前时间。`<%= new java.util.Date() %>` 是服务器端代码,用于插入当前日期和时间。
- `<div class="weather-widget">`: 一个包含天气信息的 `<div>` 元素。`class="weather-widget"` 是一个自定义类,应用于此元素的 CSS 样式。
- `<iframe>`: 一个内嵌框架,用于在当前页面中加载其他页面。它的 `src` 属性指向一个提供天气信息的网址。其他属性(如 `width`、`height`、`frameborder` 等)用于控制框架的外观和行为。
提供的接口,该网站提供了众多样式供学习测试。