java基础之Map集合

2023-11-15


java Map集合又很多种,很多人java开发几年,却不知道这几种常见的Map有什么区别 。大部分人开发存放key-value键值对数据类型集合容器, 第一想到HashMap集合, 其他Map到没有怎么使用过,甚者可能连HashMap的实现原理也都不清楚。
发现问题查找博客,发现大部分的博客知识点错误百出 简直误人子弟,当然 我早些年写的博客 甚至现在可能写的博客知识也有这种情况,如果有读者指出错误,我很乐意和读者探讨。
本篇文章Map基于openj9.1.8

HashMap

数据结构

JDK1.8之前HashMap由数组+链表组成 ,JDK1.8以在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转 化为红黑树,以减少搜索时间

链表长度不超过8
在这里插入图片描述
链表长度超出8 且HashMap数组长度超出64(默认是64)
在这里插入图片描述

HashMap数据存放过程

HashMap put的流程 map.put(key,value);

  1. 先根据key 找到key的Node hash 值 int hashCode = hash(key); (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
  2. 根据hash和hashMap数组长度计算对应的数组下标 (n - 1) & hash n为数组长度
    所以 当hashMap数组扩容后 key的数组下标值会变动
  3. 该数组索引对应的桶有没有数据,没数据 new Node 放入数组下标内。 到这里跳到第6步。
  4. 数组索引对应的桶有数据,判断数据存放类型 如果是自平衡的排序二叉树 new TreeNode insert数据到红黑树。到这里跳到第6步。
  5. 数据索引对应的桶 数据类型为Node ,遍历链表节点 如果与key hash值相等 覆盖该节点 ,否则 在链表最后面添加 新节点 Node ,链表长度+1 判断修改后的链表长度 如果超出8 链表改为红黑树。到这里跳到第6步。
  6. 判断hashMap 散列表的装填因子 ,如果装填因子超出0.75 (默认值0.75),给数组扩容 把HashMap集合数据重新组装存放到新的数组内。

在这里插入图片描述

HashMap 线程安全问题

与大多数集合类一样,此类不同步。可以使用 Collections.synchronizedMap 方法构造同步的HashMap。Map m = Collections.synchronizedMap(new HashMap(...)); ,为什么说HashMap是线程不安全的呢?

线程不安全的情况,多个线程给map put数据

  1. 两个线程(线程A 和线程B)key 计算的数组索引下标相同 都是2
  2. . 线程A 优先获取时间调度 判断出 数组索引2 的位置没有数据 new 出一个Node 名叫nodeA ,
  3. 紧接着线程B 获取了资源, 线程A挂起 。 判断数组索引2位置没有数据也 new 一个Node 叫nodeB, 线程B准备存放数据时 ,线程A 获取资源 把nodeA 放入 索引2 内, 结束put工作 ,接着线程B获取资源 把nodeB放入数组内 。这样线程A存放的数据被覆盖get线程A的key 返回为null。

图文表示:

线程A 找到下标2的位置,看没有值 创建一个nodeA
在这里插入图片描述
线程A被挂起,线程B 查询 下标2 没有数据,创建节点 NodeB
在这里插入图片描述
线程A获取资源,直接把nodeA放入桶内
在这里插入图片描述
线程B ,一直认为索引2的桶没有数据 ,忽略已有的链表数据 直接把nodeB节点覆盖放入桶内
在这里插入图片描述

多线程不安全案例

案例测试代码

public class HashMapTest {

    static HashMap map = new HashMap();
    public static void main(String[] args) {

        new Thread(() -> {
            System.out.println("线程1 执行");
            for (int i = 0; i < 8000; i++) {
                map.put(i,i);
            }
            System.out.println("线程1---------"+map.get(500));
        }).start();

        new Thread(() -> {
            System.out.println("线程2 执行");
            for (int i = 8000; i < 16000; i++) {
                map.put(i,i);
            }
            System.out.println("线程2---------"+map.get(1500));
        }).start();

    }


}

多次执行结果

线程1 执行
线程2 执行
线程2---------null
线程1---------null



线程1 执行
线程2 执行
线程1---------500
线程2---------1500



线程1 执行
线程2 执行
线程1---------null
线程2---------1500


线程1 执行
线程2 执行
线程2---------1500
线程1---------null

线程1 执行
线程2 执行
线程1---------500
线程2---------null

Collections.synchronizedMap(new HashMap(…));保证Map安全

public class HashMapTest {

    public static void main(String[] args) {

        Map map = Collections.synchronizedMap(new HashMap());
        new Thread(() -> {
            System.out.println("线程1 执行");
            for (int i = 0; i < 80000; i++) {
                map.put(i,i);
            }
            System.out.println("线程1---------"+ map.get(500));
        }).start();

        new Thread(() -> {
            System.out.println("线程2 执行");
            for (int i = 80000; i < 160000; i++) {
                map.put(i,i);
            }
            System.out.println("线程2---------"+ map.get(80020));
        }).start();

    }


}

HashTable

和hashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射。

数据结构

在这里插入图片描述

数据存放过程

和hashMap 差不多相同 但是没有红黑树 ,具体的看代码。

线程安全问题

HashTable 内的方法是同步的 synchronized ,当多个线程同时put 或remove 时
多线程案例,把创建的HashTable对象 加锁,缺点:没加锁之前多个线程 put key的数组下标索引不一样时,同时往数组添加数据没有影响 ,整个对象加锁后 要等上个线程释放锁后才能操作put

hashTable锁加的只加


public class HashTableTest {

    static  Hashtable<Integer,Integer> map = new Hashtable();

    public static void main(String[] args) {



        new Thread(() -> {
            System.out.println("线程1 执行");
            for (int i = 0; i < 8000; i++) {
                map.put(i,i);
            }
            System.out.println("线程1---------"+map.get(500));
        }).start();

        new Thread(() -> {
            System.out.println("线程2 执行");
            for (int i = 8000; i < 16000; i++) {
                map.put(i,i);
            }
            System.out.println("线程2---------"+map.get(1500));
        }).start();


    }


}

执行结果
线程相对安全的

线程1 执行
线程2 执行
线程2---------1500
线程1---------500

ConcurrentHashMap

jdk1.6版本:

1.默认情况下会有16个区段 Segment数组 Segment[16]

2.每次每个区段Segment中会保存若干个散列桶,每次散列桶长度扩容成2^n次方的长度。 多个散列桶相连就构成了散列表。

3.存入元素: key带入到hashcode方法众获得hashcode值,然后把hashcode值带入到散列算法中获取segment的下标(区段编号),再根据key带入到定义好的函数中获取Segment对象中散列桶的下标。

如果此位置有元素就构成链表(JDK1.8及以后会形成红黑树),如果没有元素就存入

3.存取的线程安全问题: 如果多个线程操作同一个Segment,则某个线程给此Segment加锁,另一个线程只能阻塞。

同时解决了HashTable的问题,HashTable只能由一个线程操作。 ConcurrentHashMap可以让一个线程操作第一个Segment,另一个线程操作另一个Segment。

4.小矩形块表示散列桶

绿色的Segment表示ConcurrentHashMap集合众 Segment[16]数组里的一个对象。

5.并发问题:

两个线程给不同的区段Segment中添加元素,这种情况可以并发。 所以ConcurrentHashMap可以保证线程安全(多个线程操作同一个Segment,则某个线程给此Segment加锁,另一个线程只能阻塞)并且在一定程度上提交线程并发执行效率。

两个线程给同一个区段Segment中添加元素,这种情况不可以并发, 这样JDK1.8进行了改进:

没有区段了,和HashMap一致了,数组+链表+红黑树 +乐观锁 + synchronized

8及以后

省略 下次补上

WeakHashMap

与大多数集合类一样,此类不同步。可以使用 Collections.synchronizedMap 方法构造同步的 WeakHashMap。

WeakHashMap.Entry 和 HashMap.Node 的不同点在于,WeakHashMap.Entry 继承了WeakReference。
弱引用的生存期特别短。https://blog.csdn.net/ChineseSoftware/article/details/119212399的时候,一旦发现弱引用对象,无论当前内存空间是否充足,都会将弱引用回收。
想象一下如下场景:

  1. 调用两次 size():第一次为 10,第二次就为 8 了。
  2. 两次调用 isEmpty():第一次返回 false,第二次返回 true。
  3. 两次调用 containsKey():第一次返回 true,第二次返回 false。
  4. 两次调用 get():第一次返回一个 value,第二次返回 null。
    在如今的并发泛滥的大环境下,大家应该都用过缓存,缓存都是放在内存中的,而内存几乎是计算机中最宝贵也是最稀缺的资源,所以需要谨慎的使用,不然很容易就出现 https://blog.csdn.net/ChineseSoftware/article/details/119212481。缓存的主要作用是为了更快的处理业务、降低服务器的压力,那么就要保证缓存命中率,这里假设整个缓存是一个 key-value 结构的(以键值对缓存为例),HashMap 作为强引用对象在没有主动将 key 删除时是不会被 JVM 回收的,这样 HashMap 中的对象就会越积越多直到 OOM 错误;那么如何做到既让缓存的命中率高又不占用那么多的内存,这里就可以采用 WeakHashMap,当然不会有 HashMap 100% 的命中率(假设内存足够),但是在保证程序正常的前提下更好的实现了缓存这套解决方案。

WeakHashMap使用了软引用结构,它的对象在垃圾回收时会被删除
注:垃圾回收是优先级非常低的线程,不能被显示调用,当内存不足的时候会启用
下面是 WeakHashMap 的实现原理拆分:

public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> {
    ... ...
    // 用于存储需要清理的引用对象
    private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
    ... ...
    // 内部Entry继承自WeakReference,从而有弱引用特性
    private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        ... ...
    }
    ... ...
    // 用于移除内部不用的Entry来释放内存
    private void expungeStaleEntries() { ... ... }
    ... ...
}

WeakHashMap 原理说明
1.每次GC清理对象后,引用对象被放置到 ReferenceQueue 之中
2.每次访问 WeakHashMap 都会调用 expungeStaleEntries 遍历删除 ReferenceQueue 中引用对象

1、缓存中使用
由于 WeakHashMap 是弱引用,因此适合在缓存中使用,当内存不足GC的时候,会清理不用的引用达到释放内存的目的

2、不要使用基础类型作为WeakHashMap的key

我大概理解的是,基础类型的一定范围不会被回收
原文:objectMap.put方法执行的时候i会被封装为Integer类型的,Integer保留了-128到127的缓存。但是对于int来说范围大很多,因此那些Key <= 127的Entry将不会进行自动回收,但是那些大于127的将会被回收,因此最后的尺寸总是会稳定在128左右

key 设置从0 开始 ,一直循环发现 0至128 的key value 没有被清理

    public static void main(String[] args) {
        WeakHashMap map = new WeakHashMap();
        map.put(0,0);
        for (int i = 0; map.size() == 0; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(i< 2000){
                map.put(i,i);
            }
            System.out.println("i 值为:  "+i);
            System.out.println("map size 为:  "+map.size());
            if(map.size() == 0){
                System.out.println("map size 为  " + 0);
                break;
            }
        }
    }

int 类型改为大于 128 gc自动清除

public class WeakHashMapTest {
    public static void main(String[] args) {
        WeakHashMap map = new WeakHashMap();
        map.put(3000,3000);
        for (int i = 129; map.size() != 0; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if(i< 2000){
                map.put(i,i);
            }
            System.out.println("i 值为:  "+i);
            System.out.println("map size 为:  "+map.size());
            if(map.size() == 0){
                System.out.println("map size 为  " + 0);
                break;
            }
        }
    }
}

在检测到适当的可达性更改后,垃圾收集器会将注册的引用对象附加到该队列中。

byte[] key = new byte[1024*10];
WeakReference<byte[]> reference = new WeakReference<>(key,queue);

//当垃圾回收之后实际上会将reference对象放进引用队列中。
//而key就是也就是byte数组对象回收掉。通常引用队列就是作为一个通知的信号,表明这个对象被回收掉了。

验证代码 这里设置堆的大小为-Xmx20m (20m)

@Test
public void test() throws InterruptedException {
ReferenceQueue queue = new ReferenceQueue<>();

new Thread(()->{
    HashMap<Object,Object> map = new HashMap<>();
    for(int i=0;i<100;i++){
        WeakReference<byte[]> reference = new WeakReference<>(new byte[1024*100],queue);
        map.put(reference,"a");
    }
    System.out.println(map.size());
}).start();

new Thread(()->{
    int cnt = 0;
    WeakReference<byte[]> k;
    try {
    	//ReferenceQueue.remove是阻塞的。poll()方法是不阻塞的。
        while((k = (WeakReference) queue.remove()) != null) {
            System.out.println("byte对象地址" + k.get());
            System.out.println("WeakReference的地址" + k);
        }
    }catch (Exception e){

    }
}).start();
Thread.sleep(2000);

}

WeakReference的地址java.lang.ref.WeakReference@6930026c
byte对象地址null
WeakReference的地址java.lang.ref.WeakReference@561e343
byte对象地址null

map.size()=100

实际上只有map数组中key对象中的byte数组被回收掉了。

IdentityHashMap

IdentityHashMap数据结构

在这里插入图片描述

IdentityHashMap数据存放过程

在这里插入图片描述
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

java基础之Map集合 的相关文章

  • java.lang.NoClassDefFoundError:org.apache.batik.dom.svg.SVGDOMImplementation

    我在链接到我的 Android LibGDX 项目的 Apache Batik 库时遇到了奇怪的问题 但让我们从头开始 在 IntelliJ Idea 中我有一个项目 其中包含三个模块 Main Android 和 Desktop 我强调的
  • 如何为最终用户方便地启动Java GUI程序

    用户想要从以下位置启动 Java GUI 应用程序Windows 以及一些额外的 JVM 参数 例如 javaw Djava util logging config file logging properties jar MyGUI jar
  • Java中反射是如何实现的?

    Java 7 语言规范很早就指出 本规范没有详细描述反射 我只是想知道 反射在Java中是如何实现的 我不是问它是如何使用的 我知道可能没有我正在寻找的具体答案 但任何信息将不胜感激 我在 Stackoverflow 上发现了这个 关于 C
  • 在画布上绘图

    我正在编写一个 Android 应用程序 它可以在视图的 onDraw 事件上直接绘制到画布上 我正在绘制一些涉及单独绘制每个像素的东西 为此我使用类似的东西 for int x 0 x lt xMax x for int y 0 y lt
  • Java JDBC:更改表

    我希望对此表进行以下修改 添加 状态列 varchar 20 日期列 时间戳 我不确定该怎么做 String createTable Create table aircraft aircraftNumber int airLineCompa
  • 控制Android的前置LED灯

    我试图在用户按下某个按钮时在前面的 LED 上实现 1 秒红色闪烁 但我很难找到有关如何访问和使用前置 LED 的文档 教程甚至代码示例 我的意思是位于 自拍 相机和触摸屏附近的 LED 我已经看到了使用手电筒和相机类 已弃用 的示例 但我
  • JavaMail 只获取新邮件

    我想知道是否有一种方法可以在javamail中只获取新消息 例如 在初始加载时 获取收件箱中的所有消息并存储它们 然后 每当应用程序再次加载时 仅获取新消息 而不是再次重新加载它们 javamail 可以做到这一点吗 它是如何工作的 一些背
  • 磁模拟

    假设我在 n m 像素的 2D 表面上有 p 个节点 我希望这些节点相互吸引 使得它们相距越远吸引力就越强 但是 如果两个节点之间的距离 比如 d A B 小于某个阈值 比如 k 那么它们就会开始排斥 谁能让我开始编写一些关于如何随时间更新
  • 我可以使用 HSQLDB 进行 junit 测试克隆 mySQL 数据库吗

    我正在开发一个 spring webflow 项目 我想我可以使用 HSQLDB 而不是 mysql 进行 junit 测试吗 如何将我的 mysql 数据库克隆到 HSQLDB 如果您使用 spring 3 1 或更高版本 您可以使用 s
  • Mockito when().thenReturn 不必要地调用该方法

    我正在研究继承的代码 我编写了一个应该捕获 NullPointerException 的测试 因为它试图从 null 对象调用方法 Test expected NullPointerException class public void c
  • 十进制到八进制的转换[重复]

    这个问题在这里已经有答案了 可能的重复 十进制转换错误 https stackoverflow com questions 13142977 decimal conversion error 我正在为一个类编写一个程序 并且在计算如何将八进
  • 加密 JBoss 配置中的敏感信息

    JBoss 中的标准数据源配置要求数据库用户的用户名和密码位于 xxx ds xml 文件中 如果我将数据源定义为 c3p0 mbean 我会遇到同样的问题 是否有标准方法来加密用户和密码 保存密钥的好地方是什么 这当然也与 tomcat
  • 如何在 javadoc 中使用“<”和“>”而不进行格式化?

    如果我写
  • AWS 无法从 START_OBJECT 中反序列化 java.lang.String 实例

    我创建了一个 Lambda 函数 我想在 API 网关的帮助下通过 URL 访问它 我已经把一切都设置好了 我还创建了一个application jsonAPI Gateway 中的正文映射模板如下所示 input input params
  • 如何从终端运行处理应用程序

    我目前正在使用加工 http processing org对于一个小项目 但是我不喜欢它附带的文本编辑器 我使用 vim 编写所有代码 我找到了 pde 文件的位置 并且我一直在从 vim 中编辑它们 然后重新打开它们并运行它们 重新加载脚
  • Java列表的线程安全

    我有一个列表 它将在线程安全上下文或非线程安全上下文中使用 究竟会是哪一个 无法提前确定 在这种特殊情况下 每当列表进入非线程安全上下文时 我都会使用它来包装它 Collections synchronizedList 但如果不进入非线程安
  • 如何从泛型类调用静态方法?

    我有一个包含静态创建方法的类 public class TestClass public static
  • 声明的包“”与预期的包不匹配

    我可以编译并运行我的代码 但 VSCode 中始终显示错误 早些时候有一个弹出窗口 我不记得是什么了 我点击了 全局应用 从那以后一直是这样 Output is there but so is the error The declared
  • 当我从 Netbeans 创建 Derby 数据库时,它存储在哪里?

    当我从 netbeans 创建 Derby 数据库时 它存储在哪里 如何将它与项目的其余部分合并到一个文件夹中 右键单击Databases gt JavaDB in the Service查看并选择Properties This will
  • 如何实现仅当可用内存较低时才将数据交换到磁盘的写缓存

    我想将应用程序生成的数据缓存在内存中 但如果内存变得稀缺 我想将数据交换到磁盘 理想情况下 我希望虚拟机通知它需要内存并将我的数据写入磁盘并以这种方式释放一些内存 但我没有看到任何方法以通知我的方式将自己挂接到虚拟机中before an O

随机推荐

  • 基础css-flex布局基础属性

    1 flex布局 弹性布局 伸缩布局 设置当前盒子为弹性盒子 display flex 设置主轴方向的对齐方式 justify content justify content center 设置侧轴方向的对齐方式 align items a
  • replace(),IndexOf(),substring() ,lastIndexOf() ,split() ,pollFirst() ,pollFirst()

    replace pattern replacement 使用replacement替换pattern 如果pattern是字符串只替换第一个匹配项 如果pattern是正则表达式则替换每次匹配都要调用的回调函数 实例 String a 1
  • win10 系统开启自带热点,手机无法连接(连接超时)

    win10开始自带热点 手机成功连接 颇费周折 所以在此记录一下 也给其他人一个参考 今天想在win10上安装个WIFI软件 好让手机连接 结果无意间发现win10自带了热点功能 于是赶紧打开 手机的WIFI列表也显示出来了 本以为就这样愉
  • 国际MES供应商与产品大全

    最近花了一点时间 将国际上知名的MES厂商和其产品整理了一下 如下 ABB ABB 苏黎世 瑞士 是一个240亿美元的自动化和电力技术的全球业务的供应商 它的制造软件利用其控制及自动化产品通用解决方案的一部分 其系统800xa控制体系结构
  • Android 自定义播放暂停按钮图片

    Android 自定义播放暂停按钮图片 在开发Android应用程序时 我们经常需要使用播放暂停按钮来控制媒体播放器的状态 虽然Android提供了默认的播放暂停按钮 但有时候我们希望根据设计需求自定义这些按钮的外观 在本文中 我将详细介绍
  • 嵌入式毕设分享 自动晾衣架设计与实现(源码+硬件+论文)

    文章目录 0 前言 1 主要功能 2 硬件设计 原理图 3 核心软件设计 4 实现效果 5 最后 0 前言 这两年开始毕业设计和毕业答辩的要求和难度不断提升 传统的毕设题目缺少创新和亮点 往往达不到毕业答辩的要求 这两年不断有学弟学妹告诉学
  • 解决tmux启动「can't create socket」的问题

    Tmux是终端重度用户的好帮手 你再也不用担心以下问题 任务运行一半要下班 发现忘记使用nohup 网络不稳定 终端会掉线 有人找我开会 切换有线 无线会掉线 我入职以后 一直在开发机里面使用tmux和screen 在我发现tmux之前 最
  • Java的循环

    目录 1 while循环 2 do while循环 3 for 循环 1 while循环 while循环结构 语法结构 初始值 while 条件 循环操作代码块 迭代代码 执行规律 1 首先执行一次初始值 2 然后判断条件 如果条件为tru
  • ESP32的WIFI的STA模式&调控ESP32蓝牙和WIFI发射功率

    以下相关API接口的定义可进入l乐鑫官方查看 Wi Fi 库 ESP32 ESP IDF 编程指南 v4 4 文档 STA模式配置过程 include
  • springboot 获取当前日期_Spring Boot获取时间

    运行环境新建测试类 package com wusiyao websockets service import org springframework stereotype Service import java text SimpleDa
  • 【Linux后端服务器开发】常用开发工具

    目录 一 apt yum 二 gcc g 三 make makefile 四 vi vim 五 gdb 一 apt yum apt 和 yum 都是在Linux环境下的软件包管理器 负责软件的查找 安装 更新与卸载 apt 是Ubuntu系
  • 小程序经典案例

    1 上拉触底事件 data colorList isloding false getColors this setData isloding true 需要展示 loading 效果 wx showLoading title 数据加载中 w
  • Java学习笔记 03

    相关文章 Java学习笔记 01 概论 Java学习笔记 02 快速之旅 Java环境配置及HelloWorld程序 引言 写这篇文章 主要是为了以后能快速复习Java的基础语法 同时 帮助有C 等语言基础的同学快速入门Java 目录 一
  • SmartFusion从FPGA到ARM系列教程

    前言 本系列教程 将会以Microsemi SmartFusion一代芯片A2F200M3F为例 简单介绍片上ARM Cortex M3 硬核 MCU 基本外设的使用 及其与FPGA逻辑模块进行交互的示例 在学习片上硬核ARM Cortex
  • 网上阅卷系统php源码,又开源了,网上阅卷系统自动识别功能代码

    想让自己轻松点就要让计算机多为你做点 前几天一个朋友找到我让我做一个网上阅卷系统 就是实现这么几个功能 高速扫描仪扫描试卷后得到一张一张的图片 软件的功能就是处理图片 计算成绩 再详细点就是自动识别考生涂的学号 自动识别考生的选择题答案并记
  • css设计引言,HTML5与CSS3设计模式 引言(3)

    引言 3 2 代码清单2 浮动下沉首字示例 HTML pclass hanging indent spanclass hanging dropcap H span anging Dropcap p CSS hanging indent pa
  • 2022-渗透测试-git提权(Linux)

    目录 1 什么是提权 2 git提权命令 3 git的使用 1 什么是提权 提权就是通过各种办法和漏洞 提高自己在服务器中的权限 以便控制全局 利用漏洞的最终目的是获取被测系统的最高权限 即Windows中管理员账户的权限 或Linux中r
  • C++自定义connect超时时间——非阻塞套接字法

    一 代码 include
  • 深圳大学数据库系统实验 Leasing Luxury Database system 基于PHP,MySQL,Web三件套

    本实验要求搭建一个手袋租聘的数据库系统 并实现以下要求 创建一个数据库 可以记录客户数据 手袋数据 租聘数据 设计者数据 用户可以提供自己的邮箱地址 邮寄地址 信用卡号码 来注册租聘网站 数据库要展示所有课租聘的手袋 已被租聘的手袋用户不能
  • java基础之Map集合

    Map集合 HashMap 数据结构 HashMap数据存放过程 HashMap 线程安全问题 多线程不安全案例 Collections synchronizedMap new HashMap 保证Map安全 HashTable 数据结构