JavaSE02_Day03(下)-WebServer项目(一、二、三)、Map接口、HashMap

一、WebServer项目

1.1 项目描述

  该项目是手写版的Web服务器项目,市面中常用的Web服务器有Tomcat,在第三阶段也会学习使用Tomcat服务器,在面试的过程中经常会被问,是否阅读过源码,以及是否手写过相关框架,当前通过第二阶段的API学习,可以进行手写一个简易版本的Web服务器项目。

1.2 版本一

实现功能:实现了一个Client向Server发送数据,Server接受到数据(客户端向服务端发送数据,服务端接收数据)

WebServer.java

 package cn.tedu.core;
 
 import java.io.IOException;
 import java.net.ServerSocket;
 import java.net.Socket;
 
 /**
  * webserver主类
  * @author cjn
  *
  */
 public class WebServer {
     //声明服务器端socket属性
     private ServerSocket serverSocket;
     
     /**
      * 构造器
      * 用于进行对属性的初始化操作,
      * 也就意味着进行启动服务器端
      */
     public WebServer() {
         try {
             System.out.println("正在启动服务器端......");
             serverSocket = new ServerSocket(8888);
             System.out.println("服务器端已经启动成功!!!");
        } catch (IOException e) {
             e.printStackTrace();
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
     
     /**
      * 服务器端处理客户端请求的方法
      */
     public void start() {
         try {
             System.out.println("正在等待客户端连接......");
             Socket socket = serverSocket.accept();
             System.out.println("一个客户端已经连接完毕!!!");
             //启动线程进行处理客户端的请求
             ClientHandler clientHandler = new ClientHandler(socket);
             Thread thread = new Thread(clientHandler);
             thread.start();  
        } catch (IOException e) {
             e.printStackTrace();
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
     
     public static void main(String[] args) {
         WebServer server = new WebServer();
         server.start();
    }
 
 }

ClientHandler.java

 package cn.tedu.core;
 /**
  * 处理客户端请求的任务序列类
  * @author cjn
  *
  */
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.Socket;
 
 public class ClientHandler implements Runnable{
     //声明获取连接客户端以后,获取的socket对象
     private Socket socket;
     
     public ClientHandler(Socket socket) {
         this.socket = socket;
    }
     /**
      * 线程所执行的任务序列逻辑
      */
     public void run() {
         
         try {
             //获取输入流对象
             InputStream is = socket.getInputStream();
             //不能使用缓冲字符输入流BufferReader进行读取,使用字节方案进行读取
             int d = -1;
             while ((d = is.read()) != -1) {
                 //按照字节读取以后,再转换为字符
                  char c = (char)d;
                  System.out.print(c);
            }
        } catch (IOException e) {
             e.printStackTrace();
        } catch (Exception e) {
             e.printStackTrace();
        }
         
    }
 
 }

测试:启动WebServer主类,然后打开浏览器,在浏览器的地址栏中输入http://localhost:8888

控制台结果如下

 正在启动服务器端......
 服务器端已经启动成功!!!
 正在等待客户端连接......
 一个客户端已经连接完毕!!!
 GET / HTTP/1.1
 Host: localhost:8888
 Connection: keep-alive
 Cache-Control: max-age=0
 sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"
 sec-ch-ua-mobile: ?0
 Upgrade-Insecure-Requests: 1
 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.114 Safari/537.36
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
 Sec-Fetch-Site: none
 Sec-Fetch-Mode: navigate
 Sec-Fetch-User: ?1
 Sec-Fetch-Dest: document
 Accept-Encoding: gzip, deflate, br
 Accept-Language: zh-CN,zh;q=0.9

同学常见的错误:

  1. 在浏览器的地址栏输入localhost:8888浏览器前面没有自动补填http://,那么自己手动加

  1. 未在WebServer主类中的start方法进行开启线程

1.3 版本二

实现功能:在ClientHandler类中自定义一个readLine方法,并测试按行读取功能

提示: 该方法可以读取一行的字符串,因为一个请求中的请求行和消息头都是一行一行的字符串,并且也CRLF结尾。

关于如何判断读到CRLF的示意图:

测试:启动WebServer主类,然后打开浏览器,在浏览器的地址栏中输入http://localhost:8888

控制台结果如下

 正在启动服务器端......
 服务器端已经启动成功!!!
 正在等待客户端连接......
 一个客户端已经连接完毕!!!
 GET / HTTP/1.1

1.4 版本三(上)

实现功能:解析请求(请求行,消息头,消息正文),将客户端发过来的请求以一个HttpRequest对象的形式保存,以便于后期处理时可以方便的获取请求中的各个信息。

提示:

  1. 新建一个包cn.tedu.http

  2. 在该包中定义一个类:HttpRequest

  3. 在该类中定义请求中各部分信息对应的属性

  4. 定义构造方法来进行初始化(解析)请求

HttpRequest.java

 package cn.tedu.http;
 /**
  * 解析客户端发送的请求
  * @author cjn
  *
  */
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.Socket;
 
 public class HttpRequest {
     
     /*
      * 定义请求行相关的属性信息
      */
     private String method;//请求方式
     private String url;//请求路径
     private String protocol;
     
     
     /*
      * 定义消息头相关的属性信息
      */
     
     /*
      * 定义消息正文相关的属性信息
      */
     
     /*
      * 定义客户端连接的相关属性信息
      */
     private Socket socket;//客户端返回的socket
     private InputStream is;//通过socket获取的输入流
     
     /**
      * 构造方法,用于初始化解析请求
      * @param socket 需要外界传入客户端socket对象
      */
     public HttpRequest(Socket socket) {
         try {
             this.socket = socket;
             this.is = socket.getInputStream();
             System.out.println("HttpRequest开始解析请求......");
             /*
              * 解析请求:
              * 1、解析请求行
              * 2、解析消息头
              * 3、解析消息正文
              */
             parseRequestLine();
             parseHeader();
             parseContext();
             System.out.println("HttpRequest解析请求完毕......");
        } catch (IOException e) {
             e.printStackTrace();
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
     
     /**
      * 解析请求行:
      * 解析请求行,也就是对于当前请求行中的信息内容
      * 进行拆分,将请求方式,请求路径和协议版本
      * 存储到上方定义的属性中
      * 例如:GET / HTTP/1.1
      */
     public void parseRequestLine() {
         try {
             System.out.println("开始解析请求行");
             //获取请求行操作
             String line = readLine();
             System.out.println("请求行:" + line);
             //解析请求行操作(按照空格进行拆分),并且完成对相关的属性的赋值
             String[] data = line.split("\\s");
             method = data[0];
             url = data[1];
             protocol = data[2];
             System.err.println("method:" + method);
             System.err.println("url:" + url);
             System.err.println("protocal:" + protocol);
             
             System.out.println("解析请求行结束");
        } catch (IOException e) {
             e.printStackTrace();
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
     
     /**
      * 解析消息头
      * 例如其中一行消息头:sec-ch-ua-mobile: ?0
      */
     public void parseHeader() {
         try {
             System.out.println("开始解析消息头");
             /*
              * 解析消息头操作:
              *     因为消息头不同于请求行,请求行只有一行,
              * 而消息头有很多行,并且不知道具体有多少行,
              * 所以可以使用不计次循环while进行多次调用readLine方法
              * 进行读取消息头。
              *     当读取消息头内容以后,当读取到空串""的时候,
              * 证明读取消息头结束。
              *     读取消息头结束以后,可以根据消息头的格式分析,
              * 按照:冒号进行拆分,需要用到Map这个知识,将冒号左侧的内容
              * 存储到Map中的key,将冒号右侧的内容存储到Map的value。
              */
             while (true) {
                 String line = readLine();
                 //判断当读取到空串的时候,意味这读取到了消息头最后的CRLF
                 if ("".equals(line)) {
                     break;
                }
                 System.out.println(line);
            }
             System.out.println("解析消息头结束");
        } catch (IOException e) {
             e.printStackTrace();
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
     
     /**
      * 解析消息正文
      */
     public void parseContext() {
         System.out.println("开始解析消息正文");
         //解析消息正文操作
         System.out.println("解析消息正文结束");
    }
     
     /**
      * 按行进行读取信息,当读取到CR回车LF换行符的时候停止,
      * 将以上读取的内容以一行字符串的形式进行返回
      * @throws IOException
      */
     public String readLine() throws IOException {
         //创建StringBuilder对象,为拼接字符使用
         StringBuilder strBuilder = new StringBuilder();
         //用于存储读取的字节
         int d = -1;
         //表示c1上次读取的字符和c2本次读取的字符
         char c1 = 'a',c2 = 'a';
         while ((d = is.read()) != -1) {
             //读取的字节转换为字符
             c2 = (char)d;
             //判断是否读取到CRLF
             if (c1 == 13 && c2 == 10) {
                 //终止循环,不再读取
                 break;
            }
             //拼接字符
             strBuilder.append(c2);
             //将本次读取的字符c2作为上次读取的字符
             c1 = c2;
        }
         
         return strBuilder.toString().trim();
    }
     /**
      * 生成请求行信息存储的属性的get方法
      * 因为有关于请求行中的信息是从客户端中获取而来,
      * 所以在这里不需要生成set方法
      */
     public String getMethod() {
         return method;
    }
 
     public String getUrl() {
         return url;
    }
 
     public String getProtocol() { 
       return protocol; 
  } 
    
}

ClientHandler.java

 package cn.tedu.core;
 /**
  * 处理客户端请求的任务序列类
  * @author cjn
  *
  */
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.Socket;
 
 import cn.tedu.http.HttpRequest;
 
 public class ClientHandler implements Runnable{
     //声明获取连接客户端以后,获取的socket对象
     private Socket socket;
     
     public ClientHandler(Socket socket) {
         this.socket = socket;
    }
     /**
      * 线程所执行的任务序列逻辑
      */
     public void run() {
         
         try {
             //创建HttpRequest对象
             HttpRequest request = new HttpRequest(socket);
        } catch (Exception e) {
             e.printStackTrace();
        }
         
    }
     
 }

测试:启动WebServer主类,然后打开浏览器,在浏览器的地址栏中输入http://localhost:8888

控制台结果如下:

 正在启动服务器端......
 服务器端已经启动成功!!!
 正在等待客户端连接......
 一个客户端已经连接完毕!!!
 HttpRequest开始解析请求......
 开始解析请求行
 请求行:GET / HTTP/1.1
 解析请求行结束
 开始解析消息头
 Host: localhost:8888
 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0
 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
 Accept-Encoding: gzip, deflate
 Connection: keep-alive
 Upgrade-Insecure-Requests: 1
 解析消息头结束
 开始解析消息正文
 解析消息正文结束
 HttpRequest解析请求完毕......
 method:GET
 url:/
 protocal:HTTP/1.1

1.5 版本三(下)

实现功能:将消息头信息进行解析存储到Map中

HttpRequest.java

 package cn.tedu.http;
 /**
  * 解析客户端发送的请求
  * @author cjn
  *
  */
 
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.Socket;
 import java.util.HashMap;
 import java.util.Map;
 
 public class HttpRequest {
     
     /*
      * 定义请求行相关的属性信息
      */
     private String method;//请求方式
     private String url;//请求路径
     private String protocol;
     
     
     /*
      * 定义消息头相关的属性信息
      */
     private Map<String, String> headers = new HashMap<String, String>();
     
     /*
      * 定义消息正文相关的属性信息
      */
     
     /*
      * 定义客户端连接的相关属性信息
      */
     private Socket socket;//客户端返回的socket
     private InputStream is;//通过socket获取的输入流
     
     /**
      * 构造方法,用于初始化解析请求
      * @param socket 需要外界传入客户端socket对象
      */
     public HttpRequest(Socket socket) {
         try {
             this.socket = socket;
             this.is = socket.getInputStream();
             System.out.println("HttpRequest开始解析请求......");
             /*
              * 解析请求:
              * 1、解析请求行
              * 2、解析消息头
              * 3、解析消息正文
              */
             parseRequestLine();
             parseHeader();
             parseContext();
             System.out.println("HttpRequest解析请求完毕......");
        } catch (IOException e) {
             e.printStackTrace();
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
     
     /**
      * 解析请求行:
      * 解析请求行,也就是对于当前请求行中的信息内容
      * 进行拆分,将请求方式,请求路径和协议版本
      * 存储到上方定义的属性中
      * 例如:GET / HTTP/1.1
      */
     public void parseRequestLine() {
         try {
             System.out.println("开始解析请求行");
             //获取请求行操作
             String line = readLine();
             System.out.println("请求行:" + line);
             //解析请求行操作(按照空格进行拆分),并且完成对相关的属性的赋值
             String[] data = line.split("\\s");
             method = data[0];
             url = data[1];
             protocol = data[2];
             System.err.println("method:" + method);
             System.err.println("url:" + url);
             System.err.println("protocal:" + protocol);
             
             System.out.println("解析请求行结束");
        } catch (IOException e) {
             e.printStackTrace();
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
     
     /**
      * 解析消息头
      * 例如其中一行消息头:sec-ch-ua-mobile: ?0
      */
     public void parseHeader() {
         try {
             System.out.println("开始解析消息头");
             /*
              * 解析消息头操作:
              *     因为消息头不同于请求行,请求行只有一行,
              * 而消息头有很多行,并且不知道具体有多少行,
              * 所以可以使用不计次循环while进行多次调用readLine方法
              * 进行读取消息头。
              *     当读取消息头内容以后,当读取到空串""的时候,
              * 证明读取消息头结束。
              *     读取消息头结束以后,可以根据消息头的格式分析,
              * 按照:冒号进行拆分,需要用到Map这个知识,将冒号左侧的内容
              * 存储到Map中的key,将冒号右侧的内容存储到Map的value。
              */
             while (true) {
                 String line = readLine();
                 //判断当读取到空串的时候,意味这读取到了消息头最后的CRLF
                 if ("".equals(line)) {
                     break;
                }
                 String[] data = line.split(":\\s");
                 headers.put(data[0], data[1]);
                 System.out.println(line);
            }
             System.out.println("解析消息头结束");
        } catch (IOException e) {
             e.printStackTrace();
        } catch (Exception e) {
             e.printStackTrace();
        }
    }
     
     /**
      * 解析消息正文
      */
     public void parseContext() {
         System.out.println("开始解析消息正文");
         //解析消息正文操作
         System.out.println("解析消息正文结束");
    }
     
     /**
      * 按行进行读取信息,当读取到CR回车LF换行符的时候停止,
      * 将以上读取的内容以一行字符串的形式进行返回
      * @throws IOException
      */
     public String readLine() throws IOException {
         //创建StringBuilder对象,为拼接字符使用
         StringBuilder strBuilder = new StringBuilder();
         //用于存储读取的字节
         int d = -1;
         //表示c1上次读取的字符和c2本次读取的字符
         char c1 = 'a',c2 = 'a';
         while ((d = is.read()) != -1) {
             //读取的字节转换为字符
             c2 = (char)d;
             //判断是否读取到CRLF
             if (c1 == 13 && c2 == 10) {
                 //终止循环,不再读取
                 break;
            }
             //拼接字符
             strBuilder.append(c2);
             //将本次读取的字符c2作为上次读取的字符
             c1 = c2;
        }
         
         return strBuilder.toString().trim(); 
  } 
   /** 
    * 生成请求行信息存储的属性的get方法 
    * 因为有关于请求行中的信息是从客户端中获取而来, 
    * 所以在这里不需要生成set方法 
    */ 
   public String getMethod() { 
       return method; 
  } 
​ 
   public String getUrl() { 
       return url; 
  } 
​ 
   public String getProtocol() { 
       return protocol; 
  } 
    
   /** 
    * 提供可以进行对外访问的获取Map中value值的方法 
    */ 
   public String getHeaders(String key) { 
       return headers.get(key); 
  } 
​ 
}

ClientHandler.java

 package cn.tedu.core;
 /**
  * 处理客户端请求的任务序列类
  * @author cjn
  *
  */
 ​
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.Socket;
 ​
 import cn.tedu.http.HttpRequest;
 ​
 public class ClientHandler implements Runnable{
     //声明获取连接客户端以后,获取的socket对象
     private Socket socket;
     
     public ClientHandler(Socket socket) {
         this.socket = socket;
    }
     /**
      * 线程所执行的任务序列逻辑
      */
     public void run() {
         
         try {
             //创建HttpRequest对象
             HttpRequest request = new HttpRequest(socket);
        System.out.println(request.getHeaders("Connection"));
        } catch (Exception e) {
             e.printStackTrace();
        }
         
    }
     
 }

测试:启动WebServer主类,然后打开浏览器,在浏览器的地址栏中输入http://localhost:8888

控制台结果如下:

正在启动服务器端......
服务器端已经启动成功!
正在等待客户端连接......
一个客户端已经连接完毕!
HttpRequest开始解析请求......
开始解析请求行......
请求行:GET / HTTP/1.1
method:GET
url:/
protocal:HTTP/1.1
解析请求行完毕!
开始解析消息头......
Host: localhost:8888
Connection: keep-alive
Cache-Control: max-age=0
sec-ch-ua: " Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"
sec-ch-ua-mobile: ?0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
解析消息头完毕!
开始解析消息正文......
解析消息正文完毕!
HttpRequest解析请求完毕!
keep-alive

 

二、Map接口

2.1 定义

  Map是一个以键值对(key-value)形式进行存储的数据结构,Map是来自于java.util包下的一个接口,有两个实现类,分别为HashMap(散列表,基于散列算法的Map)和TreeMap(基于二叉树实现的Map),这个Map并不是继承自Collection。

2.2 存储方式

  可以把Map看成是一个多行两列的表格,其中一列存放key,另一列存放value,总体来看,Map就是一个以key-value形式进行存储数据的容器。

2.3 注意事项

  • Map中的key是不可以重复的,调用equals方法不能为true;

  • 使用Map在存储数据的过程中,会使用泛型进行约束key和value存储数据类型;

  • Map中对于键值对中元素的类型是没有要求,只要是引用数据类型均可。

2.4 优势

  Map中对应的实现类有HashMap,使用HashMap进行查询数据,是目前查询数据最快的数据结构,没有之一,可以通过key来进行查找对应的value值。

2.5 Map的相关API方法

 package cn.tedu.map;
 
 import java.util.HashMap;
 import java.util.Map;
 
 /**
  * Map的相关API方法使用案例
  * @author cjn
  *
  */
 public class MapDemo {
 
     public static void main(String[] args) {
         /*
          * 1.V put(K key,V value)
          * 该方法可以将一组给定的键值对存储在Map中,
          * 需要注意,Map中存储的元素时无序的,如果向Map
          * 中添加一个已经存在key值,那么就会修改之前的
          * key所对应的value值.
          *
          * 返回值说明:
          * 1.如果是在Map中添加不存在的key和对应的value值,会返回null。
          * 2.如果是在Map中添加已经存在的key和对应的value值,会返回被
          * 替换掉之前的value值。
          */
         Map<String, Integer> map = new HashMap<String, Integer>();
         Integer i = map.put("数学", 145);
         System.out.println(i);
         map.put("语文", 100);
         map.put("英语", 110);
         map.put("物理", 78);
         map.put("化学", 88);
         map.put("生物", 98);
         i = map.put("数学", 130);
         System.out.println(i);
         System.out.println("Map对象:" + map);
         
         /*  
          * 2.V get(Object key)
          * 该方法可以根据Map中的key取出对应的value值
          * 如果get方法中的key值并不是Map中存储的,返回值为null
          */
         Integer score = map.get("生物");
         System.out.println("生物学科的成绩为:" + score);
         score = map.get("计算机");
         System.out.println("计算机学科的成绩为:" + score);
         
         /*
          * 3.V remove(Object key)
          * 该方法可以将给定的Map对应key和它的value值一组都删除,
          * 返回值为删除的key所对应的value
          */
         score = map.remove("英语");
         System.out.println(score);
         System.out.println("Map对象:" + map);

      
    }
 
 }

测试结果:

 null
 145
 Map对象:{物理=78, 生物=98, 数学=130, 化学=88, 语文=100, 英语=110}
 生物学科的成绩为:98
 计算机学科的成绩为:null
 110
 Map对象:{物理=78, 生物=98, 数学=130, 化学=88, 语文=100}

补充

  /*
    int size():返回键值对儿的个数
  */
  int size = map.size();
  System.out.println("size:"+size);

  /*
    boolean containsKey(Object key):判断当前Map是否包含给定的key
    boolean containsValue(Object value):判断当前Map是否包含给定的value
  */
  boolean a = map.containsKey("英语");//key存在,返回true
  System.out.println("包含key:"+a);
  boolean b = map.containsKey("刘壮实");//key不存在,返回false
  System.out.println("包含key:"+b);
  boolean c = map.containsValue(98);//value存在,返回true
  System.out.println("包含value:"+c);
  boolean d = map.containsValue(250);//value不存在,返回false
  System.out.println("包含value:"+d);

2.6 Map的遍历

  • 遍历键key(应用适中)

  • 遍历值value(基本不用)

  • 遍历键值key-value(应用广泛)

 package cn.tedu.map;
 
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 
 /**
  * Map遍历案例
  * @author cjn
  *
  */
 public class MapDemo02 {
 
     public static void main(String[] args) {
         Map<String, Integer> map = new HashMap<String, Integer>();
         map.put("数学", 145);
         map.put("语文", 100);
         map.put("英语", 110);
         map.put("物理", 78);
         map.put("化学", 88);
         map.put("生物", 98);
         System.out.println("Map对象:" + map);
         
         /*
          * 1.遍历Map中的key
          * Set<K> keySet()
          */
         Set<String> set = map.keySet();
         for (String str : set) {
             System.out.println("key:" + str + ",value:" + map.get(str));
        }
         
         /*
          * 2.遍历Map中的value
          * Collection<V> values();
          * 通过值找到key这个不允许,所以基本不用
          */
         Collection<Integer> c = map.values();
         for (Integer integer : c) {
             System.out.println("Map中的value值为:" + integer);
        }
         
         /*
          * 3.遍历Map中的key-value键值对
          * Set<Map.Entry<K, V>> entrySet()
          * 一组数据key-value称之为是一个Entry实例,
          * Map中会有若干的Entry实例
          */
         Set<Entry<String,Integer>> set2 = map.entrySet();
         for (Entry<String, Integer> entry : set2) {
             System.out.println(entry);
             System.out.println("键:" + entry.getKey() + ",值:" + entry.getValue());
        }
         
    }
 
 }

测试结果:

 Map对象:{物理=78, 生物=98, 数学=145, 化学=88, 语文=100, 英语=110}
 key:物理,value:78
 key:生物,value:98
 key:数学,value:145
 key:化学,value:88
 key:语文,value:100
 key:英语,value:110
 Map中的value值为:78
 Map中的value值为:98
 Map中的value值为:145
 Map中的value值为:88
 Map中的value值为:100
 Map中的value值为:110
 物理=78
 键:物理,值:78
 生物=98
 键:生物,值:98
 数学=145
 键:数学,值:145
 化学=88
 键:化学,值:88
 语文=100
 键:语文,值:100
 英语=110
 键:英语,值:110

补充:forEach新循环

  /*
    JDK8之后集合框架支持了使用lambda表达式遍历。
    因此Map和Collection都提供了forEach方法,
    通过lambda表达式遍历元素。
  */
  map.forEach(
    (k,v)->System.out.println(k+","+v)
  );
  /*
    集合在使用forEach遍历时并不要求过程中不能通过集合的方法增删元素。
    而之前的迭代器则有此要求,否则可能在遍历过程中抛出异常。
  */
  values.forEach( //注意:values是Collection<V> values();获取到的集合
    v->System.out.println("value:"+v)
  );
  key.forEach( //注意:key是Set<K> keySet();获取到的集合
    k->System.out.println("key:"+k)
  );

2.7 HashMap存储数据原理(非常重要)

  • capacity:桶容量 / 散列数组的大小,当进行创建hash表的时候,初始的容量是16;

  • size:存储数据的数量。

  • Load factor加载因子:75%,公式:size/capacity 的比值大于加载因子则发生扩容并且重新散列

  • 加载因子数值较小的时候,散列查询性能会更高,当前也会浪费散列桶的容量。75%其实是性能和空间相对比较平衡的一个比值。

2.8 hashCode方法

  hashCode方法是Object类所提供的方法,重写该方法时的注意:

  • 如果一个类中重写了equals方法,那么应当重写hashCode方法。

  • 如果对于两个对象进行equals比较,返回值为true,那么他们的hashCode值是一定相同的。

  • 如果一个对象,在内容上没有发生变化的情况,如果多次调用hashCode(),最终计算的返回值是相同的。

  • 如果对于两个对象进行equals比较为false,那在这里其实不会要求这两个对象的hashCode值一定不一样,但是尽量保证一样,这样可以提高散列表的性能。

2.9 Map的应用

  在框架中很多的数据存储操作都是使用Map存储的,例如Spring、SpringMvc、redis、Structs2等框架中都是有使用Map进行存储数据的。

 

posted @ 2021-07-11 21:39  Coder_Cui  阅读(148)  评论(0编辑  收藏  举报