day26--apiday09(聊天室续、集合框架)

day26--apiday09(聊天室续、集合框架)

1.聊天室(续)

1.实现客户端发消息给服务端

在服务端通过socket获取输出流,客户端获取输入流,实现将服务端消息发送给客户端

这里的是服务端将客户端发过来的消息,服务端再发送给客户端来进行测试

1.服务端代码:

package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
* 聊天室服务端
*/
public class Server {
   /**
    * 运行在服务端的ServerSocket主要完成两个工作:
    * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
    * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
    *   就可以和该客户端交互了
    *
    * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
    * 电话使得服务端与你沟通。
    */
   private ServerSocket serverSocket;

   /**
    * 服务端构造方法,用来初始化
    */
   public Server(){
       try {
           System.out.println("正在启动服务端...");
           /*
               实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
               应用程序占用的端口相同,否则会抛出异常:
               java.net.BindException:address already in use

               端口是一个数字,取值范围:0-65535之间。
               6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
            */
           serverSocket = new ServerSocket(8088);
           System.out.println("服务端启动完毕!");
      } catch (IOException e) {
           e.printStackTrace();
      }
  }

   /**
    * 服务端开始工作的方法
    */
   public void start(){
       try {
           while(true) {
               System.out.println("等待客户端链接...");
               /*
                   ServerSocket提供了接受客户端链接的方法:
                   Socket accept()
                   这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
                   的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
                   通过这个Socket就可以与客户端进行交互了。

                   可以理解为此操作是接电话,电话没响时就一直等。
                */
               Socket socket = serverSocket.accept();
               System.out.println("一个客户端链接了!");
               //启动一个线程与该客户端交互
               ClientHandler clientHandler = new ClientHandler(socket);
               Thread t = new Thread(clientHandler);
               t.start();

          }
      } catch (IOException e) {
           e.printStackTrace();
      }
  }

   public static void main(String[] args) {
       Server server = new Server();
       server.start();
  }

   /**
    * 定义线程任务
    * 目的是让一个线程完成与特定客户端的交互工作
    */
   private class ClientHandler implements Runnable{
       private Socket socket;
       private String host;//记录客户端的IP地址信息

       public ClientHandler(Socket socket){
           this.socket = socket;
           //通过socket获取远端计算机地址信息
           host = socket.getInetAddress().getHostAddress();
      }
       public void run(){
           try{
                /*
                   Socket提供的方法:
                   InputStream getInputStream()
                   获取的字节输入流读取的是对方计算机发送过来的字节
                */
               InputStream in = socket.getInputStream();
               InputStreamReader isr = new InputStreamReader(in, "UTF-8");
               BufferedReader br = new BufferedReader(isr);

               OutputStream out = socket.getOutputStream();
               OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
               BufferedWriter bw = new BufferedWriter(osw);
               PrintWriter pw = new PrintWriter(bw,true);


               String message = null;
               while ((message = br.readLine()) != null) {
                   System.out.println(host + "说:" + message);
                   //将消息回复给客户端
                   pw.println(host + "说:" + message);
              }
          }catch(IOException e){
               e.printStackTrace();
          }
      }
  }


}

 

2.客户端代码:

package socket;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

/**
* 聊天室客户端
*/
public class Client {
   /*
       java.net.Socket 套接字
       Socket封装了TCP协议的通讯细节,我们通过它可以与远端计算机建立链接,
       并通过它获取两个流(一个输入,一个输出),然后对两个流的数据读写完成
       与远端计算机的数据交互工作。
       我们可以把Socket想象成是一个电话,电话有一个听筒(输入流),一个麦克
       风(输出流),通过它们就可以与对方交流了。
    */
   private Socket socket;

   /**
    * 构造方法,用来初始化客户端
    */
   public Client(){
       try {
           System.out.println("正在链接服务端...");
           /*
               实例化Socket时要传入两个参数
               参数1:服务端的地址信息
                    可以是IP地址,如果链接本机可以写"localhost"
               参数2:服务端开启的服务端口
               我们通过IP找到网络上的服务端计算机,通过端口链接运行在该机器上
               的服务端应用程序。
               实例化的过程就是链接的过程,如果链接失败会抛出异常:
               java.net.ConnectException: Connection refused: connect
            */
           socket = new Socket("localhost",8088);
           System.out.println("与服务端建立链接!");
      } catch (IOException e) {
           e.printStackTrace();
      }
  }

   /**
    * 客户端开始工作的方法
    */
   public void start(){
       try {
           /*
               Socket提供了一个方法:
               OutputStream getOutputStream()
               该方法获取的字节输出流写出的字节会通过网络发送给对方计算机。
            */
           //低级流,将字节通过网络发送给对方
           OutputStream out = socket.getOutputStream();
           //高级流,负责衔接字节流与字符流,并将写出的字符按指定字符集转字节
           OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
           //高级流,负责块写文本数据加速
           BufferedWriter bw = new BufferedWriter(osw);
           //高级流,负责按行写出字符串,自动行刷新
           PrintWriter pw = new PrintWriter(bw,true);

           //通过socket获取输入流读取服务端发送过来的消息
           InputStream in = socket.getInputStream();
           InputStreamReader isr = new InputStreamReader(in,"UTF-8");
           BufferedReader br = new BufferedReader(isr);


           Scanner scanner = new Scanner(System.in);
           while(true) {
               String line = scanner.nextLine();
               if("exit".equalsIgnoreCase(line)){
                   break;
              }
               pw.println(line);

               line = br.readLine();
               System.out.println(line);
          }

      } catch (IOException e) {
           e.printStackTrace();
      } finally {
           try {
               /*
                   通讯完毕后调用socket的close方法。
                   该方法会给对方发送断开信号。
                */
               socket.close();
          } catch (IOException e) {
               e.printStackTrace();
          }
      }
  }

   public static void main(String[] args) {
       Client client = new Client();
       client.start();
  }
}

2.服务端转发所有消息给客户端

1.问题即:一个客户端发消息给服务端,如何所有客户端都能收到信息

2.解决:

内部类可以访问外部类的成员,因此在Server类上定义一个allOut数组可以被所有内部类ClientHandler实例访问。从而将这些ClientHandler实例之间像相互访问的数组存放在这个数组中达到共享数据的目的。对此子需要将所有ClientHandler中的输出流都存入allOut这个数组即可。

 

 

1.服务端代码:
package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;

/**
* 聊天室服务端
*/
public class Server {
   /**
    * 运行在服务端的ServerSocket主要完成两个工作:
    * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
    * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
    *   就可以和该客户端交互了
    *
    * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
    * 电话使得服务端与你沟通。
    */
   private ServerSocket serverSocket;
   /*
       存放所有客户端输出流,用于广播消息
    */
   private PrintWriter[] allOut = {};

   /**
    * 服务端构造方法,用来初始化
    */
   public Server(){
       try {
           System.out.println("正在启动服务端...");
           /*
               实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
               应用程序占用的端口相同,否则会抛出异常:
               java.net.BindException:address already in use

               端口是一个数字,取值范围:0-65535之间。
               6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
            */
           serverSocket = new ServerSocket(8088);
           System.out.println("服务端启动完毕!");
      } catch (IOException e) {
           e.printStackTrace();
      }
  }

   /**
    * 服务端开始工作的方法
    */
   public void start(){
       try {
           while(true) {
               System.out.println("等待客户端链接...");
               /*
                   ServerSocket提供了接受客户端链接的方法:
                   Socket accept()
                   这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
                   的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
                   通过这个Socket就可以与客户端进行交互了。

                   可以理解为此操作是接电话,电话没响时就一直等。
                */
               Socket socket = serverSocket.accept();
               System.out.println("一个客户端链接了!");
               //启动一个线程与该客户端交互
               ClientHandler clientHandler = new ClientHandler(socket);
               Thread t = new Thread(clientHandler);
               t.start();

          }
      } catch (IOException e) {
           e.printStackTrace();
      }
  }

   public static void main(String[] args) {
       Server server = new Server();
       server.start();
  }

   /**
    * 定义线程任务
    * 目的是让一个线程完成与特定客户端的交互工作
    */
   private class ClientHandler implements Runnable{
       private Socket socket;
       private String host;//记录客户端的IP地址信息

       public ClientHandler(Socket socket){
           this.socket = socket;
           //通过socket获取远端计算机地址信息
           host = socket.getInetAddress().getHostAddress();
      }
       public void run(){
           try{
                /*
                   Socket提供的方法:
                   InputStream getInputStream()
                   获取的字节输入流读取的是对方计算机发送过来的字节
                */
               InputStream in = socket.getInputStream();
               InputStreamReader isr = new InputStreamReader(in, "UTF-8");
               BufferedReader br = new BufferedReader(isr);

               OutputStream out = socket.getOutputStream();
               OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
               BufferedWriter bw = new BufferedWriter(osw);
               PrintWriter pw = new PrintWriter(bw,true);

               //将该输出流存入共享数组allOut中
               //1对allOut数组扩容
               allOut = Arrays.copyOf(allOut,allOut.length+1);
               //2将输出流存入数组最后一个位置
               allOut[allOut.length-1] = pw;


               String message = null;
               while ((message = br.readLine()) != null) {
                   System.out.println(host + "说:" + message);
                   //将消息回复给所有客户端
                   for(int i=0;i<allOut.length;i++) {
                       allOut[i].println(host + "说:" + message);
                  }
              }
          }catch(IOException e){
               e.printStackTrace();
          }
      }
  }


}

3.客户端解决收发消息的冲突问题

由于客户端start方法中循环进行的操作顺序是先通过控制台输入一句话将其发送给服务端,然后再读取服务端发送回来的一句话。这导致如果客户端不输入内容就无法收到服务端发送过来的其他信息(其他客户端的聊天内容)。因此客户端中接受消息的工作移动到一个单独的线程上执行,才能保证收发信息互不打扰

1.客户端代码

package socket;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

/**
* 聊天室客户端
*/
public class Client {
   /*
       java.net.Socket 套接字
       Socket封装了TCP协议的通讯细节,我们通过它可以与远端计算机建立链接,
       并通过它获取两个流(一个输入,一个输出),然后对两个流的数据读写完成
       与远端计算机的数据交互工作。
       我们可以把Socket想象成是一个电话,电话有一个听筒(输入流),一个麦克
       风(输出流),通过它们就可以与对方交流了。
    */
   private Socket socket;

   /**
    * 构造方法,用来初始化客户端
    */
   public Client(){
       try {
           System.out.println("正在链接服务端...");
           /*
               实例化Socket时要传入两个参数
               参数1:服务端的地址信息
                    可以是IP地址,如果链接本机可以写"localhost"
               参数2:服务端开启的服务端口
               我们通过IP找到网络上的服务端计算机,通过端口链接运行在该机器上
               的服务端应用程序。
               实例化的过程就是链接的过程,如果链接失败会抛出异常:
               java.net.ConnectException: Connection refused: connect
            */
           socket = new Socket("localhost",8088);
           System.out.println("与服务端建立链接!");
      } catch (IOException e) {
           e.printStackTrace();
      }
  }

   /**
    * 客户端开始工作的方法
    */
   public void start(){
       try {
           //启动读取服务端发送过来消息的线程
           ServerHandler handler = new ServerHandler();
           Thread t = new Thread(handler);
           t.setDaemon(true);
           t.start();


           /*
               Socket提供了一个方法:
               OutputStream getOutputStream()
               该方法获取的字节输出流写出的字节会通过网络发送给对方计算机。
            */
           //低级流,将字节通过网络发送给对方
           OutputStream out = socket.getOutputStream();
           //高级流,负责衔接字节流与字符流,并将写出的字符按指定字符集转字节
           OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
           //高级流,负责块写文本数据加速
           BufferedWriter bw = new BufferedWriter(osw);
           //高级流,负责按行写出字符串,自动行刷新
           PrintWriter pw = new PrintWriter(bw,true);


           Scanner scanner = new Scanner(System.in);
           while(true) {
               String line = scanner.nextLine();
               if("exit".equalsIgnoreCase(line)){
                   break;
              }
               pw.println(line);
          }

      } catch (IOException e) {
           e.printStackTrace();
      } finally {
           try {
               /*
                   通讯完毕后调用socket的close方法。
                   该方法会给对方发送断开信号。
                */
               socket.close();
          } catch (IOException e) {
               e.printStackTrace();
          }
      }
  }

   public static void main(String[] args) {
       Client client = new Client();
       client.start();
  }

   /**
    * 该线程负责接收服务端发送过来的消息
    */
   private class ServerHandler implements Runnable{
       public void run(){
           //通过socket获取输入流读取服务端发送过来的消息
           try {
               InputStream in = socket.getInputStream();
               InputStreamReader isr = new InputStreamReader(in,"UTF-8");
               BufferedReader br = new BufferedReader(isr);

               String line;
               //循环读取服务端发送过来的每一行字符串
               while((line = br.readLine())!=null){
                   System.out.println(line);
              }
          } catch (IOException e) {
               e.printStackTrace();
          }
      }
  }
}

 

4.服务端完成处理客户端断开连接后的操作

当一个客户端断开连接后,服务端处理该客户端交互线程ClientHandler应当将通过socket获取的输出流从共享数组allOut中删除,防止其他的ClientHandler再将消息通过这个输出流发送给当前客户端

1.服务端代码

package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;

/**
* 聊天室服务端
*/
public class Server {
   /**
    * 运行在服务端的ServerSocket主要完成两个工作:
    * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
    * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
    *   就可以和该客户端交互了
    *
    * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
    * 电话使得服务端与你沟通。
    */
   private ServerSocket serverSocket;
   /*
       存放所有客户端输出流,用于广播消息
    */
   private PrintWriter[] allOut = {};

   /**
    * 服务端构造方法,用来初始化
    */
   public Server(){
       try {
           System.out.println("正在启动服务端...");
           /*
               实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
               应用程序占用的端口相同,否则会抛出异常:
               java.net.BindException:address already in use

               端口是一个数字,取值范围:0-65535之间。
               6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
            */
           serverSocket = new ServerSocket(8088);
           System.out.println("服务端启动完毕!");
      } catch (IOException e) {
           e.printStackTrace();
      }
  }

   /**
    * 服务端开始工作的方法
    */
   public void start(){
       try {
           while(true) {
               System.out.println("等待客户端链接...");
               /*
                   ServerSocket提供了接受客户端链接的方法:
                   Socket accept()
                   这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
                   的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
                   通过这个Socket就可以与客户端进行交互了。

                   可以理解为此操作是接电话,电话没响时就一直等。
                */
               Socket socket = serverSocket.accept();
               System.out.println("一个客户端链接了!");
               //启动一个线程与该客户端交互
               ClientHandler clientHandler = new ClientHandler(socket);
               Thread t = new Thread(clientHandler);
               t.start();

          }
      } catch (IOException e) {
           e.printStackTrace();
      }
  }

   public static void main(String[] args) {
       Server server = new Server();
       server.start();
  }

   /**
    * 定义线程任务
    * 目的是让一个线程完成与特定客户端的交互工作
    */
   private class ClientHandler implements Runnable{
       private Socket socket;
       private String host;//记录客户端的IP地址信息

       public ClientHandler(Socket socket){
           this.socket = socket;
           //通过socket获取远端计算机地址信息
           host = socket.getInetAddress().getHostAddress();
      }
       public void run(){
           PrintWriter pw = null;
           try{
                /*
                   Socket提供的方法:
                   InputStream getInputStream()
                   获取的字节输入流读取的是对方计算机发送过来的字节
                */
               InputStream in = socket.getInputStream();
               InputStreamReader isr = new InputStreamReader(in, "UTF-8");
               BufferedReader br = new BufferedReader(isr);

               OutputStream out = socket.getOutputStream();
               OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
               BufferedWriter bw = new BufferedWriter(osw);
               pw = new PrintWriter(bw,true);

               //将该输出流存入共享数组allOut中
               //1对allOut数组扩容
               allOut = Arrays.copyOf(allOut, allOut.length + 1);
               //2将输出流存入数组最后一个位置
               allOut[allOut.length - 1] = pw;
               //通知所有客户端该用户上线了
               sendMessage(host + "上线了,当前在线人数:"+allOut.length);


               String message = null;
               while ((message = br.readLine()) != null) {
                   System.out.println(host + "说:" + message);
                   //将消息回复给所有客户端
                   sendMessage(host + "说:" + message);
              }
          }catch(IOException e){
               e.printStackTrace();
          }finally{
               //处理客户端断开链接的操作
               //将当前客户端的输出流从allOut中删除(数组缩容)
               for(int i=0;i<allOut.length;i++){
                   if(allOut[i]==pw){
                       allOut[i] = allOut[allOut.length-1];
                       allOut = Arrays.copyOf(allOut,allOut.length-1);
                       break;
                  }
              }
               sendMessage(host+"下线了,当前在线人数:"+allOut.length);
               try {
                   socket.close();//与客户端断开链接
              } catch (IOException e) {
                   e.printStackTrace();
              }
          }
      }

       /**
        * 广播消息给所有客户端
        * @param message
        */
       private void sendMessage(String message){
           for(int i=0;i<allOut.length;i++) {
               allOut[i].println(message);
          }
      }
  }



}

5.服务端解决多线程并发安全问题

为了让能让消息转发给所有客户端,我们在Server上创建了一个数组类型的allOut,并且供所有ClientHandler使用,这时就需要考虑并发安全问题

1.问题原因

当两个客户端同时上线(橙、绿)

 

 

两个ClientHandler启动后都会对数组扩容,将自身的输出流存入数组

此时ClientHandler(橙)先拿到CPU时间,进行数组扩容

 

扩容后发生CPU切换,ClientHandler(绿)拿到时间

 

此时ClientHandler(绿)进行数组扩容

 

ClientHandler(绿)扩容后,将输出流存入数组最后一个位置

 

线程切换回ClientHandler(橙)

 

ClientHandler(橙)将输出流存入数组最后一个位置,此时会将ClientHandler(绿)存入的输入流覆盖。出现了并发安全问题!!

 

2.解决方案:选择合适的锁对象

1.this不可以

 

2.allOut不可以,大多数情况下可以选择临界资源作为锁对象,但是这里不行。
//将该输出流存入共享数组allOut中
synchronized(allOut){//不行,下面操作会扩容,allOut对象会变,每次扩容,都是创建一个新的对象
   //1对allOut数组扩容
   allOut=Arrays.copyOf(allOut,allOut.length+1);
   //2将输出流存入数组最后一个位置
   allOut[allOut.length-1]=pw;
   
}
3.外部类对象可以

因为这些内部类ClientHandler都属于这个外部类对象Server.this

 

4.注意

还要考虑对数组的不同操作之间的互斥问题,道理同上。因此,对allOut数组的扩容,缩容和遍历操作要进行互斥。

5.最终代码:

package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;

/**
* 聊天室服务端
*/
public class Server {
   /**
    * 运行在服务端的ServerSocket主要完成两个工作:
    * 1:向服务端操作系统申请服务端口,客户端就是通过这个端口与ServerSocket建立链接
    * 2:监听端口,一旦一个客户端建立链接,会立即返回一个Socket。通过这个Socket
    *   就可以和该客户端交互了
    *
    * 我们可以把ServerSocket想象成某客服的"总机"。用户打电话到总机,总机分配一个
    * 电话使得服务端与你沟通。
    */
   private ServerSocket serverSocket;
   /*
       存放所有客户端输出流,用于广播消息
    */
   private PrintWriter[] allOut = {};

   /**
    * 服务端构造方法,用来初始化
    */
   public Server(){
       try {
           System.out.println("正在启动服务端...");
           /*
               实例化ServerSocket时要指定服务端口,该端口不能与操作系统其他
               应用程序占用的端口相同,否则会抛出异常:
               java.net.BindException:address already in use

               端口是一个数字,取值范围:0-65535之间。
               6000之前的的端口不要使用,密集绑定系统应用和流行应用程序。
            */
           serverSocket = new ServerSocket(8088);
           System.out.println("服务端启动完毕!");
      } catch (IOException e) {
           e.printStackTrace();
      }
  }

   /**
    * 服务端开始工作的方法
    */
   public void start(){
       try {
           while(true) {
               System.out.println("等待客户端链接...");
               /*
                   ServerSocket提供了接受客户端链接的方法:
                   Socket accept()
                   这个方法是一个阻塞方法,调用后方法"卡住",此时开始等待客户端
                   的链接,直到一个客户端链接,此时该方法会立即返回一个Socket实例
                   通过这个Socket就可以与客户端进行交互了。

                   可以理解为此操作是接电话,电话没响时就一直等。
                */
               Socket socket = serverSocket.accept();
               System.out.println("一个客户端链接了!");
               //启动一个线程与该客户端交互
               ClientHandler clientHandler = new ClientHandler(socket);
               Thread t = new Thread(clientHandler);
               t.start();

          }
      } catch (IOException e) {
           e.printStackTrace();
      }
  }

   public static void main(String[] args) {
       Server server = new Server();
       server.start();
  }

   /**
    * 定义线程任务
    * 目的是让一个线程完成与特定客户端的交互工作
    */
   private class ClientHandler implements Runnable{
       private Socket socket;
       private String host;//记录客户端的IP地址信息

       public ClientHandler(Socket socket){
           this.socket = socket;
           //通过socket获取远端计算机地址信息
           host = socket.getInetAddress().getHostAddress();
      }
       public void run(){
           PrintWriter pw = null;
           try{
                /*
                   Socket提供的方法:
                   InputStream getInputStream()
                   获取的字节输入流读取的是对方计算机发送过来的字节
                */
               InputStream in = socket.getInputStream();
               InputStreamReader isr = new InputStreamReader(in, "UTF-8");
               BufferedReader br = new BufferedReader(isr);

               OutputStream out = socket.getOutputStream();
               OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
               BufferedWriter bw = new BufferedWriter(osw);
               pw = new PrintWriter(bw,true);

               //将该输出流存入共享数组allOut中
//               synchronized (this) {//不行,因为这个是ClientHandler实例
//               synchronized (allOut) {//不行,下面操作会扩容,allOut对象会变
               synchronized (Server.this) {//外部类对象可以
                   //1对allOut数组扩容
                   allOut = Arrays.copyOf(allOut, allOut.length + 1);
                   //2将输出流存入数组最后一个位置
                   allOut[allOut.length - 1] = pw;
              }
               //通知所有客户端该用户上线了
               sendMessage(host + "上线了,当前在线人数:"+allOut.length);


               String message = null;
               while ((message = br.readLine()) != null) {
                   System.out.println(host + "说:" + message);
                   //将消息回复给所有客户端
                   sendMessage(host + "说:" + message);
              }
          }catch(IOException e){
               e.printStackTrace();
          }finally{
               //处理客户端断开链接的操作
               //将当前客户端的输出流从allOut中删除(数组缩容)
               synchronized (Server.this) {
                   for (int i = 0; i < allOut.length; i++) {
                       if (allOut[i] == pw) {
                           allOut[i] = allOut[allOut.length - 1];
                           allOut = Arrays.copyOf(allOut, allOut.length - 1);
                           break;
                      }
                  }
              }
               sendMessage(host+"下线了,当前在线人数:"+allOut.length);
               try {
                   socket.close();//与客户端断开链接
              } catch (IOException e) {
                   e.printStackTrace();
              }
          }
      }

       /**
        * 广播消息给所有客户端
        * @param message
        */
       private void sendMessage(String message){
           synchronized (Server.this) {
               for (int i = 0; i < allOut.length; i++) {
                   allOut[i].println(message);
              }
          }
      }
  }
}

 

2.集合框架

1.什么是集合

集合与数组一样,可以保存一组元素,并且提供了操作元素的相关方法,使用更方便.

2.java集合框架中相关接口

1.java.util.Collection接口:

java.util.Collection是所有集合的顶级接口.Collection下面有多种实现类,因此我们有更多的数据结构可供选择.

2.Collection下面有两个常见的子接口:

  • java.util.List:线性表.是可重复集合,并且有序.

  • java.util.Set:不可重复的集合,大部分实现类是无序的.

这里可重复指的是集合中的元素是否可以重复,而判定重复元素的标准是依靠元素自身equals比较

的结果.为true就认为是重复元素.

package collection;

import java.util.ArrayList;
import java.util.Collection;

public class CollectionDemo {
   public static void main(String[] args) {
       Collection c = new ArrayList();
       /*
           boolean add(E e)
           向当前集合中添加一个元素.当元素成功添加后返回true
        */
       c.add("one");
       c.add("two");
       c.add("three");
       c.add("four");
       c.add("five");
       System.out.println(c);
       /*
           int size()
           返回当前集合的元素个数
        */
       int size = c.size();
       System.out.println("size:"+size);
       /*
           boolean isEmpty()
           判断当前集合是否为空集(不含有任何元素)
        */
       boolean isEmpty = c.isEmpty();
       System.out.println("是否为空集:"+isEmpty);
       /*
          清空集合
        */
       c.clear();
       System.out.println(c);
       System.out.println("size:"+c.size());//0
       System.out.println("是否为空集:"+c.isEmpty());


  }
}

 

3.集合与元素equals方法相关的方法

package collection;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;

/**
* 集合的很多操作有与元素的equals方法相关。
*/
public class CollectionDemo2 {
   public static void main(String[] args) {
//       Collection c = new ArrayList();
       Collection c = new HashSet();
       c.add(new Point(1,2));
       c.add(new Point(3,4));
       c.add(new Point(5,6));
       c.add(new Point(7,8));
       c.add(new Point(1,2));
       /*
           集合重写了Object的toString方法,输出的格式为:
           [元素1.toString(), 元素2.toString(), ....]
        */
       System.out.println(c);

       Point p = new Point(1,2);
       /*
           boolean contains(Object o)
           判断当前集合是否包含给定元素,这里判断的依据是给定元素是否与集合
           现有元素存在equals比较为true的情况。
        */
       boolean contains = c.contains(p);
       System.out.println("包含:"+contains);
       /*
           remove用来从集合中删除给定元素,删除的也是与集合中equals比较
           为true的元素。注意,对于可以存放重复元素的集合而言,只删除一次。
        */
       c.remove(p);
       System.out.println(c);
  }
}

4.集合存放的是元素的引用

集合只能存放引用类型元素,并且存放的是元素的引用

package collection;

import java.util.ArrayList;
import java.util.Collection;

/**
* 集合只能存放引用类型元素,并且存放的是元素的引用(地址)
*/
public class CollectionDemo3 {
   public static void main(String[] args) {
       Collection c = new ArrayList();
       Point p = new Point(1,2);
       c.add(p);
       System.out.println("p:"+p);//p:(1,2)
       System.out.println("c:"+c);//c:[(1,2)]

       p.setX(2);
       System.out.println("p:"+p);//p:(2,2)
       System.out.println("c:"+c);//c:[(2,2)]

  }
}

 

 

posted @   约拿小叶  阅读(18)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示