面向对象程序设计实践(Java)书面作业2

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。当程序成功执行完毕时,将设置 restartfalse,退出循环。(面对真实工作场景下的异常应当这样设计)

try 块中,你可以编写你的程序逻辑代码。如果在执行过程中发生了异常,将被 catch 块捕获,并执行重新启动的逻辑。在本例中,重新启动逻辑简单地打印一条消息,并在捕获异常后添加了1秒的延迟时间。

在上述示例中,我们将重新启动的逻辑放在 catch 块中。当捕获到异常时,我们打印出异常消息,并调用 main(args) 方法来重新启动程序。这里使用了递归调用,使程序能够无限重新启动。

为了使程序能够重新启动,确保在重新启动时传递正确的命令行参数(如果有)。在上述示例中,我们将原始的命令行参数 args 传递给重新启动的 main 方法。

这种方法可能会导致无限循环的重新启动,因此实际使用时,确保程序逻辑在某些条件下能够终止循环,否则可能会导致程序无限重新启动。

3. 编写10个线程,第一个线程从1加到10,第二个线程从11加20…第十个线程从91加到100,最后再把10个线程结果相加。

线程(Thread)是操作系统能够进行运算调度的最小单位,也是程序执行的最小单元。简单来说,线程是一条独立的执行路径,可以并发执行多条线程,从而实现程序的并发性。

线程的出现主要是为了解决多任务并发执行的需求。在单核处理器的时代,多线程可以通过在同一个处理器上快速切换执行,实现多个任务的同时执行,提高程序的效率。而在多核处理器的时代,多线程可以充分利用多核处理器的计算能力,实现并行计算,进一步提高程序的性能。

  1. 线程的创建和启动:可以通过继承Thread类或实现Runnable接口来创建线程,并使用start()方法启动线程。
  2. 线程的执行:在线程的run()方法中定义线程的具体执行逻辑。
  3. 线程同步:在多线程环境下,可能会出现资源竞争的问题,需要使用同步机制来保证线程安全。
  4. 线程间的通信:可以使用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 方法中,你可以指定源文件夹路径和目标文件夹路径,并创建复制线程进行文件的同步复制。每个文件对应一个复制线程,可以并发进行复制操作。

  1. FileCopyThread 类继承自 Thread 类,用于实现文件的复制。
  2. run 方法是线程的主要执行逻辑。在该方法中,通过输入流和输出流复制文件。使用一个缓冲区 buffer 来读取和写入文件内容。
  3. 在复制的过程中,使用 totalBytesRead 记录已复制的字节数,并通过计算已复制的字节数与文件总大小的比例,得到复制的进度。
  4. 进度以百分比的形式打印在控制台上,使用 String.format 方法保留两位小数。
  5. 复制完成后,关闭输入流和输出流,并在控制台上显示文件复制完成的消息。
  6. main 方法是程序的入口。在该方法中,指定了源文件夹路径和目标文件夹路径,并创建了对应的文件对象。
  7. 如果源文件夹存在,并且其中有文件,就遍历每个文件,构造目标文件的路径,并创建并启动对应的复制线程。
  8. 每个复制线程都负责复制一个文件,实现多个文件的同步复制。
  9. 程序结束后,所有文件都会被复制到指定的目标文件夹中,并在控制台上显示复制的进度信息和完成消息。

注意:代码中使用的是绝对路径。

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();
        }
    }
}
  1. 服务器端:

    • 服务器端首先需要创建一个服务器套接字,它将监听指定的端口,等待客户端的连接请求。
    • 一旦服务器套接字接收到连接请求,它将接受连接并创建一个新的套接字,与客户端建立通信。
    • 服务器端通过该套接字进行数据的发送和接收。
    • 服务器端可以使用多线程或异步编程的方式,以支持同时处理多个客户端连接。
  2. 客户端:

    • 客户端首先创建一个套接字,并指定服务器的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);
    }
}
  1. listDirectory 方法用于列出指定目录及其子目录和文件。它接受一个目录路径作为参数。
  2. 首先检查目录是否存在,如果不存在则输出相应的消息并返回。
  3. 然后输出当前目录的路径。
  4. 接着调用 recursiveList 方法来递归遍历目录。
  5. recursiveList 方法用于递归地列出目录下的子目录和文件。它接受一个目录对象和当前的深度作为参数。
  6. 首先获取当前目录下的子目录和文件。
  7. 遍历每个子目录和文件,根据深度输出相应的缩进。
  8. 输出子目录和文件的名称。
  9. 如果是目录,递归调用 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的文件:使用文本编辑器(如vinano)创建一个名为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` 等)用于控制框架的外观和行为。

特别指出,这段代码调用了天气预报,天气预报查询一周,天气预报15天查询,今天,明天,7天,10天,15天,30天,今天,明天,40天,未来一周天气预报查询_天气网天气网提供全国及世界各大城市天气预报查询以及历史天气查询,实时天气查询,准确提供一周天气预报查询及未来天气预报15天,30天,40天,7天,10天,未来十五天天气查询,并且为用户提供生活指数、健康指数、旅游攻略、交通出行、空气质量指数,及各类天气预报和生活资讯。icon-default.png?t=N4P3https://www.tianqi.com/

提供的接口,该网站提供了众多样式供学习测试。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值