知识点范围:多线程和网络编程
一、选择题
- 以下选项中可以填写到横线处,让代码正确编译和运行的是( A )。(选择1项)
public class Test implements Runnable {
public static void main(String[] args) {
___________________________________
t.start();
System.out.println("main");
}
public void run() {
System.out.println("thread1!");
}
}
- A:
Thread t = new Thread(new Test());
- A:
- B:
Test t = new Test();
- B:
- C:
Thread t = new Test();
- C:
- D:
Thread t = new Thread();
- D:
解析:
- 线程创建的方式之一就是实现Runnable接口,此接口只有一个抽象方法run(),启动线程又需要start()方法,所以还需要借助Thread类,根据Thread类的构造方法Thread(Runnable run),需要传入Runnable接口对象。
- 如下代码创建一个新线程并启动线程,问:四个选项中可以保证正确代码创建target对象,并能编译正确的是( C )。(选择1项)
public static void main(String[] args) {
Runnable target=new MyRunnable( );
Thread myThread=new Thread(target);
}
- A:
public class MyRunnable extends Runnable {
public void run() {}
}
- B:
public class MyRunnable extends Runnable {
void run() {}
}
- C:
public class MyRunnable implements Runnable {
public void run() {}
}
- D:
public class MyRunnable implements Runnable {
void run() {}
}
解析:
线程创建的方式之一就是实现Runnable接口,此接口只有一个抽象方法run()。子类重写的方法的权限必须大于等于父类方法权限(除去private外)。
- 当线程调用start( )后,其所处状态为( C )。(选择1项)
- A:阻塞状态
- B:运行状态
- C:就绪状态
- D:新建状态
解析:
线程的五个状态也就是它的生命周期:
- 新建状态:通过new关键字来创建线程对象
- 就绪状态:调用start()方法
- 运行状态:调用run()方法
- 阻塞状态:调用sleep()、wait()、join() 、yield()、interrupt ()等方法
- 消亡状态:调用stop()方法,但是此方法已经过时
- 下列关于Thread类提供的线程控制方法的说法中,错误的是( C )。(选择1项)
- A:线程A中执行线程B的join()方法,则线程A等待直到B执行完成
- B:线程A通过调用interrupt()方法来中断其阻塞状态
- C:若线程A调用方法isAlive()返回值为false,则说明A正在执行中,也可能是可运行状态
- D:currentThread()方法返回当前线程的引用
解析:
isAlive()方法判断一个线程是否在活动,如果在活动则返回true,未活动则返回false。
- 下列关于线程的优先级说法中,正确的是( CD )。(选择2项)
- A:线程的优先级是不能改变的
- B:线程的优先级是在创建线程时设置的
- C:在创建线程后的任何时候都可以重新设置
- D:线程的优先级的范围在1-10之间
解析:
线程的优先级可以在创建线程后的任何时候通过setPriority(int newPriority)方法进行设置,线程的优先级的范围在1-10之间,最低优先级为1,最高优先级为10,但是有一点,即使设置为最高优先级也不一定先执行,只是被选中执行的概率比较高
- 以下协议中属于TCP/IP协议栈中应用层协议的是( A )。(选择1项)
- A:HTTP
- B:TCP
- C:UDP
- D:IP
解析:
- 应用层:FTP、 SMTP、HTTP
- 传输层:TCP、UDP
- IP网络层:IP、ICMP、IGMP
- 网络接口层:ARP、RARP、FDDI
- 以下说法中关于UDP协议的说法正确的是( AD )。(选择2项)
- A:发送不管对方是否准备好,接收方收到也不确认
- B:面向连接
- C:占用系统资源多、效率低
- D:非常简单的协议,可以广播发送
解析:
image.png
- 在基于TCP网络通信模式中,客户与服务器程序的主要任务是( BC )。(选择2项)
- A:客户程序在网络上找到一条到达服务器的路由
- B:客户程序发送请求,并接收服务器的响应
- C:服务器程序接收并处理客户请求,然后向客户发送响应结果
- D:客户程序和服务器都会保证发送的数据不会在传输途中丢失
解析:
客户程序负责发送请求并接收服务器的响应,服务器程序负责接收处理客户请求并向客户发送响应结果。
- 在Java网络编程中,使用客户端套接字Socket创建对象时,需要指定( A )。(选择1项)
- A:服务器主机名称和端口
- B:服务器端口和文件
- C:服务器名称和文件
- D:服务器地址和文件
解析:
public Socket(String host, int port) throws UnknownHostException, IOException
:创建一个流套接字并将其连接到指定主机上的指定端口号。
- ServerSocket的监听方法accept( )方法的返回值类型是( A )。(选择1项)
- A:工厂模式
- B:装饰模式
- C:适配器模式
- D:代理模式
解析:
public Socket accept() throws IOException
:侦听并接受到此套接字的连接。
二、简答题
-
简述程序、进程和线程的联系和区别。
image.png -
创建线程的方式有哪些?各有什么优缺点。
- 继承Thread类,覆写run()方法
- 实现Runnable接口,覆写run()方法,没有返回值,无法上抛异常
- 实现Callable接口,覆写call()方法,有返回值,可以上抛异常
- 使用Executor框架创建线程池
一般情况下,常见的是第二种和第三种,原因如下:
- 避免单继承带来的局限性
- 适合于资源共享
-
线程同步的三种方式。
- 使用同步代码块:
synchronized (对象) {}
- 使用同步方法:
public synchronized void function() {}
- 使用对象锁:
Lock lock = new ReentrantLock();lock.lock();lock.unlock();
- 使用同步代码块:
-
说明sleep、yield、join方法的区别。
- Thread.sleep():在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。sleep()方法使当前线程进入阻塞状态,所以执行sleep()方法的线程在指定的时间内肯定不会被执行。
- Thread.yield():让当前运行线程回到就绪状态,以允许其他线程获得运行机会。yield()方法只是让当前线程重新回到就绪状态,所以执行yield()方法的线程有可能在进入到就绪状态后马上又被执行。
- Thread对象的join():当前线程等待加入的线程终止后继续运行。在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。
-
说明线程池的原理和线程池的拒绝策略。
线程池其实就是一组线程实时处于休眠状态,等待唤醒执行。
使用线程池的目的:- 减少在创建和销毁线程上所花费的时间及系统资源开销。
- 将当前任务与主线程隔离,实现和主线程的异步执行,特别是那些可以分开重复执行的任务。
但是,一味的开线程不一定能带来性能上的优化,线池休眠也需要占用一定的内存空间,所以需要合理的选择线程池的大小。
在线程池中执行任务比为每个任务单独分配一个线程要好得多,通过重用现有的线程而不是创建新线程,可以在处理多个请求时分摊线程创建和销毁所产生的巨大开销。当请求到达时,通常工作线程已经存在,提高了响应性。通过配置线程池的大小,可以创建足够多的线程使CPU达到忙碌状态,还可以防止线程太多而耗尽计算机的资源。
创建ExecutorService线程池:ExecutorService executorService = Executors.newCachedThreadPool();
ThreadPoolExecutor中包含了一个任务缓存队列和若干个执行线程,任务缓存队列是一个大小固定的缓冲区队列,用来缓存待执行的任务,执行线程用来处理待执行的任务。每个待执行的任务都必须实现Runnable接口,执行线程通过调用其run()方法来完成相应任务。
ThreadPoolExecutor对象初始化时,不会创建任何执行线程,只有当有新任务进来时,才会创建执行线程。
创建ThreadPoolExecutor对象时,需要配置该对象的核心线程池大小和最大线程池大小。
当目前执行线程的总数小于核心线程池大小时,所有新加入的任务,都在新线程中处理。
当目前执行线程的总数大于或等于核心线程池大小时,若任务缓存队列未满,则所有新加入的任务都放入任务缓存队列中,若任务缓存队列已满,同时目前执行线程的总数小于最大线程池大小,那么就会创建新线程加入线程池中,协助处理新加入的任务。
当目前执行线程的总数等于最大线程池大小,并且缓存队列已满时,拒绝策略RejectedExecutionHandler就会拒绝新的任务。 -
简述TCP和UDP协议的异同。
image.png
三、编码题
- 设计一个火车售票模拟程序。假如火车站要有100张火车票要卖出,现在有5个售票点同时售票,用5个线程模拟这5个售票点的售票情况。
- 代码:
package sxt;
import java.io.IOException;
public class Test {
public static void main(String args[]) throws IOException {
TicketThread ticketThread = new TicketThread();
for (int i = 1; i <= 5; i++) {
new Thread(ticketThread, "售票点" + i).start();
}
}
}
class TicketThread implements Runnable {
private Integer ticket = 1;
@Override
public void run() {
while (ticket <= 100) {
synchronized (ticket) {
if (ticket <= 100) {
System.out.println(Thread.currentThread().getName() + "卖出了第" + ticket++ + "张票");
}
}
}
System.out.println("已售罄");
}
}
-
结果截图:
image.png
image.png
image.png
image.png
image.png
- 编写两个线程,一个线程打印152的整数,另一个线程打印字母AZ,打印顺序为12A34B56C……5152Z,即按照整数和字母的顺序从小到大打印,并且每打印两个整数后,打印一个字母,交替循环打印,直到打印完整数52和字母Z后结束。
- 代码:
package sxt;
public class Test {
public static void main(String args[]) {
Object lock = new Object();
new Thread(new DigitThread(lock)).start();
new Thread(new LetterThread(lock)).start();
}
}
class DigitThread implements Runnable {
private Object lock;
private int digit = 1;
public DigitThread(Object lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized (lock) {
while (digit <= 52) {
System.out.print(digit++);
System.out.print(digit++);
lock.notifyAll();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class LetterThread implements Runnable {
private Object lock;
private char letter = 'A';
public LetterThread(Object lock) {
this.lock = lock;
}
@Override
public void run() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock) {
while (letter <= 'Z') {
System.out.print(letter++);
lock.notifyAll();
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
-
结果截图:
image.png
- 使用基于TCP的Java Socket编程,实现上传本地文件到服务器端。
- 代码:
服务器端
package sxt;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main(String args[]) {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream inputStream = null;
DataInputStream dataInputStream = null;
BufferedInputStream bufferedInputStream = null;
FileOutputStream fileOutputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
serverSocket = new ServerSocket(8888);
socket = serverSocket.accept();
inputStream = socket.getInputStream();
dataInputStream = new DataInputStream(inputStream);
bufferedInputStream = new BufferedInputStream(inputStream);
fileOutputStream = new FileOutputStream(new File("D:\\Server" + File.separator + dataInputStream.readUTF()));
bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
byte[] bs = new byte[1024];
int length = -1;
while ((length = bufferedInputStream.read(bs)) != -1) {
bufferedOutputStream.write(bs, 0, length);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bufferedOutputStream != null) {
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedInputStream != null) {
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedInputStream != null) {
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (dataInputStream != null) {
try {
dataInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
客户端:
package sxt;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
public static void main(String[] args) {
Socket socket = null;
FileInputStream fileInputStream = null;
BufferedInputStream bufferedInputStream = null;
OutputStream outputStream = null;
DataOutputStream dataOutputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
socket = new Socket("127.0.0.1", 8888);
File file = new File("D:\\Client\\Thinking_in_Patterns中文版.pdf");
fileInputStream = new FileInputStream(file);
bufferedInputStream = new BufferedInputStream(fileInputStream);
outputStream = socket.getOutputStream();
dataOutputStream = new DataOutputStream(outputStream);
bufferedOutputStream = new BufferedOutputStream(outputStream);
dataOutputStream.writeUTF(file.getName());
byte[] bs = new byte[1024];
int length = -1;
while ((length = bufferedInputStream.read(bs)) != -1) {
bufferedOutputStream.write(bs, 0, length);
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bufferedOutputStream != null) {
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (dataOutputStream != null) {
try {
dataOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedInputStream != null) {
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
-
结果截图:
image.png
image.png
四、程序题
-
项目结构+README:
image.png
image.png - 代码:
package entity;
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = -2270550255047294276L;
private String username;
private String password;
public User() {}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [username=" + username + ", password=" + password + "]";
}
}
package service.impl;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import service.MusicService;
public class MusicServiceImpl implements MusicService {
private MusicServiceImpl() {}
private static volatile MusicServiceImpl musicServiceImpl = null;
public static MusicServiceImpl getInstance() {
if (musicServiceImpl == null) {
synchronized (MusicServiceImpl.class) {
if (musicServiceImpl == null) {
musicServiceImpl = new MusicServiceImpl();
}
}
}
return musicServiceImpl;
}
@Override
public List<File> findAll() {
return getMusicFileList("D:\\MyJava\\Workspace\\STS\\League-of-Legends\\src\\main\\resources\\static");
}
public List<File> getMusicFileList(String filePath) {
List<File> musicFileList = new ArrayList<File>();
for (File musicFile : new File(filePath).listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
if (file.isDirectory()) {
musicFileList.addAll(getMusicFileList(file.getPath()));
} else {
return file.getName().toLowerCase().endsWith(".mp3");
}
return false;
}
})) {
musicFileList.add(musicFile);
}
return musicFileList;
}
@Override
public void upload(File file, String path) {
Thread thread = new Thread(new IOThread(file, path));
thread.start();
}
}
class IOThread implements Runnable {
private File file;
private String path;
public IOThread(File file, String path) {
this.file = file;
this.path = path;
}
@Override
public void run() {
FileInputStream fileInputStream = null;
BufferedInputStream bufferedInputStream = null;
FileOutputStream fileOutputStream = null;
BufferedOutputStream bufferedOutputStream = null;
try {
fileInputStream = new FileInputStream(file);
bufferedInputStream = new BufferedInputStream(fileInputStream);
fileOutputStream = new FileOutputStream(new File(path + File.separator + file.getName()));
bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
byte[] bs = new byte[1024];
int length = -1;
while ((length = bufferedInputStream.read(bs)) != -1) {
bufferedOutputStream.write(bs, 0, length);
}
System.out.println("\n" + path + File.separator + file.getName() + "上传成功!");
} catch (IOException e) {
e.printStackTrace();
} finally {
if (bufferedOutputStream != null) {
try {
bufferedOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileOutputStream != null) {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (bufferedInputStream != null) {
try {
bufferedInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fileInputStream != null) {
try {
fileInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
package service.impl;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import entity.User;
import service.UserService;
public class UserServiceImpl implements UserService {
private UserServiceImpl() {}
private static volatile UserServiceImpl userServiceImpl = null;
public static UserServiceImpl getInstance() {
if (userServiceImpl == null) {
synchronized (UserServiceImpl.class) {
if (userServiceImpl == null) {
userServiceImpl = new UserServiceImpl();
}
}
}
return userServiceImpl;
}
@Override
public boolean login(User user) {
Socket socket = null;
InputStream inputStream = null;
DataInputStream dataInputStream = null;
OutputStream outputStream = null;
ObjectOutputStream objectOutputStream = null;
try {
socket = new Socket("127.0.0.1", 8888);
inputStream = socket.getInputStream();
dataInputStream = new DataInputStream(inputStream);
outputStream = socket.getOutputStream();
objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(user);
if (dataInputStream.readBoolean()) {
return true;
} else {
return false;
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (objectOutputStream != null) {
try {
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (dataInputStream != null) {
try {
dataInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
}
package service;
import java.io.File;
import java.util.List;
public interface MusicService {
public abstract List<File> findAll();
public abstract void upload(File file, String path);
}
package service;
import entity.User;
public interface UserService {
public abstract boolean login(User user);
}
package socket;
import java.io.File;
import java.util.List;
import java.util.Scanner;
import entity.User;
import service.MusicService;
import service.UserService;
import service.impl.MusicServiceImpl;
import service.impl.UserServiceImpl;
public class Client {
private static boolean isLogin;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
UserService userService = UserServiceImpl.getInstance();
MusicService musicService = MusicServiceImpl.getInstance();
while (true) {
System.out.println("----------欢迎来到音乐上传网站----------");
System.out.println("1. 登录");
System.out.println("2. 上传音乐");
System.out.println("3. 退出");
System.out.println("----------------------------------------");
System.out.print("请选择菜单:");
int flag = scanner.nextInt();
switch (flag) {
case 1:
System.out.println("请输入用户名");
String username = scanner.next();
System.out.println("请输入密码");
String password = scanner.next();
isLogin = userService.login(new User(username, password));
if (isLogin) {
System.out.println("登陆成功!");
} else {
System.out.println("用户名或密码错误!");
}
break;
case 2:
if (isLogin) {
System.out.println("这是您的歌单请查收");
System.out.println("编号\t名称");
List<File> musicFileList = musicService.findAll();
for (int i = 0; i < musicFileList.size(); i++) {
System.out.println((i + 1) + "\t" + musicFileList.get(i).getName());
}
System.out.println("请输入要上传的歌曲");
String fileName = scanner.next();
File file = null;
for (File musicFile : musicFileList) {
if (fileName.equals(musicFile.getName())) {
file = musicFile;
break;
}
}
if (file != null) {
System.out.println("请输入服务器地址");
String path = scanner.next();
musicService.upload(file, path);
} else {
System.out.println("输入错误!");
}
} else {
System.out.println("您还没有登陆,请先登录后再上传音乐");
}
break;
case 7:
scanner.close();
System.exit(0);
default:
System.out.println("输入错误!");
break;
}
}
}
}
package socket;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import entity.User;
public class Server {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
try {
serverSocket = new ServerSocket(8888);
System.out.println("服务器准备就绪,等待客户端连接");
while (true) {
socket = serverSocket.accept();
Thread thread = new Thread(new SocketThread(socket));
thread.start();
thread.join();
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
class SocketThread implements Runnable {
private Socket socket;
public SocketThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "正在处理" + socket.getInetAddress() + "的登录验证");
InputStream inputStream = null;
ObjectInputStream objectInputStream = null;
OutputStream outputStream = null;
DataOutputStream dataOutputStream = null;
try {
inputStream = socket.getInputStream();
objectInputStream = new ObjectInputStream(inputStream);
outputStream = socket.getOutputStream();
dataOutputStream = new DataOutputStream(outputStream);
User user = (User) objectInputStream.readObject();
if ("lbc".equals(user.getUsername()) && "123".equals(user.getPassword())) {
dataOutputStream.writeBoolean(true);
} else {
dataOutputStream.writeBoolean(false);
}
System.out.println("验证完成");
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} finally {
if (dataOutputStream != null) {
try {
dataOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (objectInputStream != null) {
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
-
结果截图:
image.png
image.png
image.png
image.png
image.png
网友评论