JDK核心API速查手册与实用指南

文摘   2024-01-29 00:17   广东  


欢迎访问作者的个人网站
www.yxclass.net

第 1 章

目录


第 1 章:目录

第 2 章:Object

第 3 章:基本数据类型的包装类

第 4 章:字符串的常用API

第 5 章:异常处理

第 6 章:系统类和日期类

第 7 章:Collection单列集合

第 8 章:Map双列集合

第 9 章:JDK8新特性

第 10 章:File文件操作

第 11 章:常用的算法

第 12 章:IO流

第 13 章:多线程

第 14 章:Java高级技术


第 2 章

Object


API,Application Program Interface 中文翻译过来叫“应用程序接口”,是别人写好的一些程序,给我们直接进行调用。


关注公众号,回复“jdk文档”即可获取JDK9汉化版下载链接。



2.2、Object常用API

Object类是Java中所有类的祖宗类,因此,Java中所有类的对象都可以直接使用Object类中提供的一些方法。

2.2.1、toString

toString方法是Java中所有对象都继承自Object类的一个方法。其主要用途是将对象以字符串的形式返回,通常用于将其他数据类型转为字符串类型,以及调试和日志记录。


当你调用一个对象的toString方法时,如果该对象没有重写(override)这个方法,那么就会调用Object类中的toString方法,返回一个表示该对象内存地址的字符串。


2.2.2、equals

equals方法在Java中通常用于比较两个对象的内容是否相等。默认情况下,equals方法会比较对象的引用是否相同,即判断两个引用是否指向内存中的同一个对象。

如果你想要比较自定义对象的内容是否相等,通常需要重写equals方法。




Lombok是一个Java库,通过注解的方式自动生成getter/setter、构造函数、equals、hashCode以及toString等方法,旨在减少JavaBean类中冗余代码的编写。



2.2.3、clone

clone()克隆方法,当某一个对象调用这个方法,这个方法会复制一个一模一样的新对象,并返回。

想要调用clone()方法,必须让被克隆的类实现Cloneable接口。



1.浅克隆:



浅克隆拷贝出来的对象封装的数据和原来对象封装的数据一模一样,只是在栈内存多拷贝一份原来对象变量的值,不会在堆内存中创建新的对象。





深克隆:


在进行对象克隆时,由于数组是引用类型,如果不进行特殊处理(如这里的深度克隆),克隆出的新对象将会与原对象共享同一份数组引用,修改一方会影响到另一方。

通过u.scores = u.scores.clone();这一行代码,对新对象的scores数组进行了深度克隆,即创建了数组的一个副本,这样新旧对象间的数据就不会相互影响了。


2.3、Objects.equals()


Objects.equals()方法会先做非空判断,再比较两个对象。




第 3 章

基本数据类型的包装类


Java的包装类型是为了解决基本数据类型(也称为原始类型)在面向对象编程环境中的局限性而设计的一组类。


基本数据类型的局限性:不支持泛型,Java泛型要求类型参数必须是对象类型,因此基本类型不能直接用作泛型参数。例如,你不能创建一个List<int>,但可以创建一个List<Integer>。


valueOf()静态方法可以把本类型转为包装类,Java中8种基本数据类型和包装类的对应关系如下:



3.1、Integer包装类



3.2、Long包装类





代码应用:将用户id保存到ThreadLocal线程变量池中。






3.3、包装类数据类型转换


字符串转为数值可以用parseInt和parseDouble等静态方法进行转换。


3.3.1、将字符串转为数值类型


3.3.2、将数值转换为字符串


3.4、包装类型数值的比较


Long和Integer,对于数不在 [-128,127] 之外的值不能使用==进行比较,而应该使用equals进行比较。


Long是一个封装类型,它是对应一个 long 类型的包装类。在 Java 里面之所以要提供 Long 这种基本类型的封装类,是因为 Java 是一个面向对象的语言,而基本类型不具备对象的特征,所以在基本类型上做了一层对象的包装并且提供了相关的属性和访问方法来完善基本类型的操作。


Long 这个封装类里面,除了基本的 long类型的操作之外,还引入了享元模式的设计,对-128 到 127 之间的数据做了一层缓存,也就是说,如果 Long 类型的目标值在-128 到 127 之间,就直接从缓存里面获取 Long 这个对象实例并返回,地址值是一样的,不在 -128  到 127范围 之间创建一个新的 Long 对象,地址值不一样,所以不能用==进行比较。


享元设计模式的好处是减少频繁创建 Long 对象带来的内存消耗从而提升性能。因此在这样一个前提下,如果定义两个 Long 对象,并且这两个 Long 的取值范围正好在-128 到 127 之间。如果直接用==号来判断,返回的结果必然是 true,因为这两个 Long对象 指向的内存地址是同一个。对于不在这个范围的数据,Long会创建一个新的对象,==是比较内存地址,所以相同的值返回的结果是 false。


之所以在测试环境上没有把这个问题暴露出来,是因为测试环境上验证的数据量有限,使得取值的范围正好在 Long 的缓存区间,从而通过了测试。但是在实际的应用里面,数据量远远超过LongCashe的取值范围,所以会导致校验失败的问题。


3.5、Math数学工具类



工具类就是类中的方法API都用static修饰,调用时通过类名.静态方法名即可调用。如果需要接收对象,则使用对应的类名声明一个对象变量接收即可。






3.6、BigDecimal



3.6.1、四则运算精度丢失的问题


使用“+,-,*,/”四则运行符号进行运算时会导致精度丢失。






3.6.2、f.add(k)==>f加k



3.6.3、f.subtract(k)==>f减k



3.6.4、f.multiply(k)==>f乘k



3.6.5、f.divide(k)==>f除k



3.6.6、f.divide(k) 设置精确到几位



3.6.7、doubleValue() 


doubleValue() 方法可以将BigDecimal类型转换为double类型。



第 4 章

字符串常用的API


4.1、String


String内部的value值是final修饰的,每一次对字符串进行操作时都会创建一个新的对象放到堆内存中,性能比较低,而需要占用内存,所以在开发中很少直接使用String类对字符串进行频繁操作。




String类常用的API有:


4.1.1、equals比较字符串内容



4.1.2、substring截取字符串


截取字符串内容 (包前不包后)。



4.1.3、contains


判断字符串中是否包含某个关键字。



4.1.4、replace


把字符串中的某个内容替换成新内容,并返回新的字符串对象给我们。



4.1.5、startsWith


判断字符串是否以某个字符开头。



4.1.6、split


把字符串按照某个指定内容分割成多个字符串,放到一个字符串数组中返回给我们。



4.2、StringBuilder


4.2.1、append追加字符串



4.2.2、insert在


指定的位置插入指定的字符串。



4.2.3、delete


删除指定位置的字符串。



4.2.4、reverse


反转StringBuilder中的字符顺序。



4.2.5、length


获取StringBuilder中的字符总数。



4.2.6、toString


将StringBuilder对象转换为String对象。



4.3、StringBuffer

4.3.1、append追加字符串


append():在 StringBuffer 的末尾追加指定的数据。



4.3.2、insert


insert():在指定的位置插入指定的字符串。



注意:在 insert() 方法中,指定的位置是索引,从0开始计数。如果索引等于字符串的长度,则新字符将追加到字符串的末尾。


4.3.3、delete


delete():删除指定位置的字符串



注意:在 delete() 方法中,指定的是起始索引和结束索引,结束索引处的字符不包括在内。


4.3.4、reverse


reverse():反转StringBuffer 中的字符顺序



4.3.5、length


获取StringBuffer 中的字符总数。



4.3.6、substring


substring():提取字符串的子串。



4.3.7、toString


StringBuffer 转换为String对象。





以上这些方法都是 StringBuffer 类中非常常用的。与 StringBuilder 不同的是,StringBuffer 的大部分主要方法(如 append、insert、delete、reverse)都是同步的,这意味着在多线程环境中,它们可以安全地被多个线程同时调用,而不会导致数据不一致的问题。然而,这种同步机制也带来了额外的性能开销,所以在单线程环境中,如果不需要考虑线程安全问题,通常推荐使用 StringBuilder 以获得更好的性能。


4.4、前三种字符串类的区别




4.4.1、可变性方面


String 内部的 value 值是 final 修饰的,所以它是不可变类。所以每次修改 String 的值,都会产生一个新的对象。

StringBuffer 和 StringBuilder 是可变类,字符串的变更不会产生新的对象。


4.4.2、线程安全方面


String 是不可变类,所以它是线程安全的。

StringBuffer 是线程安全的,因为它每个操作方法都加了 synchronized 同步关键字。

StringBuilder 不是线程安全的。所以在多线程环境下对字符串进行操作,应该使用 StringBuffer,否则使用StringBuilder。


4.4.3、性能方面


String 的性能是最的低的,因为不可变意味着在做字符串拼接和修改的时候,需要重新

创建新的对象以及分配堆内存。

其次是 StringBuffer 要比 String 性能高,因为它的可变性使得字符串可以直接被修改;

最后是 StringBuilder,它比 StringBuffer 的性能高,因为 StringBuffer 加了同步锁。


4.4.4、存储方面


String 存储在字符串常量池里面;

StringBuffer 和 StringBuilder 存储在堆内存空间。




最后再补充一下, StringBuilder 和 StringBuffer 都是派生自 AbstractStringBuilder这个抽象类。


4.5、StringUtils


4.5.1、isBlank


检查字符串是否为空或只包含空格。






4.5.2、isNotBlank


StringUtils.isNotBlank 是 Apache Commons Lang 库中的一个方法,用于检查一个字符串是否不为 null、长度大于0并且包含至少一个非空白字符。






4.5.2、isEmpty


检查字符串是否为 null 或空。






4.5.3、equals


比较两个字符串,考虑 null 值。




4.6、StringJoiner


StringJoiner 是 Java 中的一个类,用于将多个字符串连接起来。它的主要特点是可以指定分隔符,并可以通过链式编程来设置多个属性。



4.7、正则表达式

4.7.1、正则表达式的作用


正则表达式的作用1:用来校验字符串数据是否合法。

正则表达式的作用2:可以从一段文本中查找满足要求的内容。

正则表达式的作用3:批量替换文本中的不想展示的字符。


4.7.2、正则表达式书写规则






4.7.3、正则表达式校验手机号码



4.7.4、正则表达式校验邮箱



4.7.5、正则表达式信息爬取


需求:请把下面文本中的电话,邮箱,座机号码,热线都爬取出来。






4.7.6、正则表达式实现文本批量替换


步骤1:使用Pattern类的静态方法compile来编译正则表达式,生成一个Pattern对象。

步骤2:使用Pattern对象的matcher方法创建一个Matcher对象,用于在给定的文本中进行匹配。

步骤3:用Matcher对象的replaceAll方法来替换所有匹配的内容。使该方法接收一个字符串作为参数,这里使用replacement来替换。



第 5 章

异常的处理


5.1、常见程序错误分三类


1.编译错误:新手最常见,没遵循语法规范。该大写没有大写,该小写没有小写。
2.运行时错误:程序在执行时,因为网络因素。
3.逻辑错误:程序没有按照预期的逻辑顺序执行。



Java.lang包中有一个java.lang.Throwable类,这个类是java中所有错误和异常的超类,Throwable类有两个子类,Error与 Exception。


可查异常:编译器要求必须处置的异常,不然不给运行。

不可查异常:编译器不要求强制处置的异常,运行过后才报错, 包括运行时异常(RuntimeException与其子类)和错误(Error),如ArrayIndexOutOfBoundsException(数组的越界)。


5.2、异常处理


异常处理快捷键:Alt+enter



5.2.1、throws往外抛出异常

使用throws在方法上声明异常,意思就是告诉下一个调用者,这里面可能有异常,调用的时候必须处理一下这个异常。


5.2.2、try-catch捕获异常

使用try-catch语句提前对可能出现异常的代码进行捕获处理。


注意:捕获异常时,不要使用e.printStackTrace()打印太多的堆栈信息,这样会占用系统的内存,影响软件的性能。



5.2.3、finally关键字

finally用来创建在 try 代码块后面执行的代码块。不管前面try-catch的代码如何,finally代码肯定会被执行,一般用于资源回收释放等操作。


5.2.4、try-catch-finally例题


分析:
1.进入try的时候temp=1,然后return temp=2,值保存在临时空间中。
2.由于try中有return,但是要再返回前执行finally,finally中temp=3,并输出。
3.执行return temp=2,finally对temp的操作不会影响try返回的结果。


5.2.5、try-catch-finally题目


分析:

1.执行try代码块,a=1,然后进入异常
2.执行catch代码块,a=2,然后return的时候把a=2放入临时空间。
3.执行finally,a=3,并输出。再执行finally的return a=3,不会执行catch中的return。


5.2.5、try-catch-finally总结

1.不管有没有异常,finally代码必会执行。
2.try中或catch中有无return,finally也会执行。执行顺序是:先执行try,catch,遇到return先保存值到临时空间中,再执行finally,不论finally如何操作这个值,都不影响临时空间的值。finally执行完以后再返回临时空间的值。
3.finally中有return,程序会提前结束,不再执行try,catch中的return,此时try和catch临时空间的值会被舍弃掉。

5.3、自定义异常


在Java中,自定义异常是指为了满足特定应用需求而创建的、继承自现有异常类的用户自定义异常类。Java提供了一套丰富的预定义异常体系,但有时系统提供的异常类型可能无法精确表达业务逻辑中的错误情况或特殊条件,这时就需要开发者自行创建异常类来表示这些特殊的异常状态。




自定义异常可能是编译时异常,也可以是运行时异常。

[1] 如果自定义异常类继承Excpetion,则是编译时异常。

特点:方法中抛出的是编译时异常,必须在方法上使用throws声明,强制调用者处理。








[2] 如果自定义异常类继承RuntimeException,则运行时异常。

特点:方法中抛出的是运行时异常,不需要在方法上用throws声明。









第 6 章

系统类和日期类


6.1、系统类

6.1.1、System

System系统类,提供了一些获取获取系统数据的方法,比如获取系统时间。





6.1.2、Runtime

Runtime类是Java运行时环境(JRE)的一部分,它提供了与Java虚拟机(JVM)交互的接口,就说可以用它来获取JVM的一些信息,也可以用这个类去执行其他的程序。


6.2、JDK8日期类

6.2.1、LocalDateTime

LocalDateTime 是 Java 8 时间 API 中的一个类,它表示一个没有时区的日期和时间,就是获取程序所运行服务器的日期和时间。这个类是不可变的,意味着一旦创建了一个 LocalDateTime 对象,它的值就不能被修改。同时,它是线程安全的,可以在多线程环境中使用



LocalDateTime 提供了一些方法用于获取和操作日期和时间。

使用 now() 方法获取当前日期和时间。
使用ldt1.equals(ldt2)可以判断两个日期对象是否相等。
使用ldt1.isAfter(ldt2)可以判断ldt1是否在ldt2的后面。
使用ldt1.isBefore(ldt2)可以判断ldt1是否在ldt2的前面。
使用 of() 方法创建一个具有特定年、月、日、时、分、秒的 LocalDateTime 对象。








6.2.2、ZoneId和ZonedDateTime

ZoneId是一个类,表示时区ID,例如"Asia/Shanghai"是获取亚洲上海的时区ID,"America/New_York"是获取美洲纽约的时区ID。它包含时区信息,可以用于获取特定时区的偏移量。


ZonedDateTime表示带有时区的日期和时间。它结合了LocalDateTime和ZoneId,可以用于处理具有时区信息的时间。ZonedDateTime代表了一个特定的时间点,可以用于计算时间差、比较时间等操作。



6.2.3、Instant


通过获取Instant的对象可以拿到此刻的时间,该时间由两部分组成:从1970-01-01 00:00:00 开始走到此刻的总秒数+不够1秒的纳秒数。


6.2.4、DateTimeFormater


DateTimeFormatter是Java 8中新增的日期、时间格式器,主要用于LocalDateTime、LocalDate、LocalTime等Java 8新增的时间类进行格式化和解析。







第 7 章

Collection单列集合


Collection是单列集合的根接口,Collection接口下面又有两个子接口List接口、Set接口,List和Set下面分别有不同的实现类,如下图所示:




7.1、Collection集合的常用方法


ArrayList、LinkedList、HashSet、LinkedHashSet、TreeSet集合都可以调用下面的方法。





Collection集合常用方法:


1.add添加元素。

2.clear清空集合中的元素。

3.remove删除元素。

4.contains判断当前集合是否包含某个元素。

5.isEmpty判断当前集合是否为空。

6.size获取集合的长度。





7.2、List集合


List系列集合:添加的元素是有序、可重复、有索引的。


List集合有两个实现类:ArrayList和LinekdList。


7.2.1、List集合常用方法




1.add插入方法,没有指定索引则是往集合尾部插入元素。

2.remove删除指定索引处的元素。

3.set修改指定索引处的元素。

4.获取指定索引位置的元素。



7.2.2、List集合的遍历方式

List集合相比于前面的Collection多了一种可以通过索引遍历的方式,所以List集合遍历方式一共有四种:


1.普通for循环(只因为List有索引)。

2.迭代器。

3.增强for。

4.Lambda表达式。





7.2.3、集合循环遍历注意事项

[1] 使用迭代器遍历集合时,又同时通过集合对象删除集合中的数据,程序就会出现并发修改异常的错误。

[2] 增强for循环底层依赖迭代器,因此,增强for循环遍历集合,并通过集合删除元素时,会出现并发修改异常。

[3]使用普通for循环从头开始遍历删除相同元素会出现漏删的现象。解决方法就是从后往前倒着遍历删除,从前往后遍历,每删除一个元素要执行i--操作。



7.2.4、ArrayList底层原理

ArrayList集合底层是基于数组结构实现的,也就是说当你往集合容器中存储元素时,底层本质上是往数组中存储元素。特点如下:


7.2.5、ArrayList扩容机制

1.利用无参构造器创建的集合,会在底层创建一个默认长度为0的数组。


2.当往空集合添加第一个元素时,底层会创一个新的长度为10的数组,添加到索引为0的位置,再添加存放元素的指针就会往后移动。存满时,会自动进行扩容1.5倍,创建一个新的数组,长度为原来数组的1.5倍,然后将原来数组的值复制到新数组中。


3.如果一次添加多个元素,1.5倍还放不下,则新创建数组的长度以实际为准。



7.2.6、LinekdList底层

LinkedList是基于双向链表实现的。链表结构是由一个一个的节点组成,一个节点由数据值、下一个元素的地址组成,没有索引。

LinkedList的特点1:查询慢,无论查询哪个数据都要从头开始找。
LinkedList的特点2:增删快相对快。


7.2.7、LinekdList设计队列

队列的特点:先进先出。


使用addLast方法在链表的末尾添加元素,使用removeFirst方法会移除链表中的第一个元素。如果链表为空(即没有任何元素),调用这个方法会抛出 NoSuchElementException。


7.2.8、LinekdList设计栈结构

栈结构的特点是先进后出,后进先出。


使用LinekdList设计栈结构:使用push方法往链表头部添加元素,使用pop方法移除链表头部的元素。



7.2.9、ArrayList和LinekdList的区别

1.数据结构:ArrayList是基于数组的数据结构,而LinkedList是基于双向链表的数据结构。

2.动态扩容:当你在ArrayList中添加元素并且超出当前数组容量时,ArrayList会自动进行扩容。而LinkedList则不需要扩容,只需将添加的元素增加一个指针链接到链表头部或者尾部的元素即可。

3.插入和删除操作:在ArrayList中,插入和删除操作(特别是删除第一个或最后一个元素)可能会涉及到大量的元素移动,因为需要移动元素来填补空位。相反,LinkedList的在插入和删除操作通常更快,因为它只需要改变头部或者尾部元素指针即可。

4.访问元素:访问ArrayList的元素通常更快,因为它是通过索引直接访问的。而访问LinkedList的元素则需要从头或尾部遍历到指定的位置。

5.线程安全:ArrayList和LinkedList都不是线程安全的。

6.使用场景:一般来说,如果你需要频繁地访问元素,那么ArrayList可能是一个更好的选择。如果你需要频繁地插入和删除元素,那么LinkedList可能更适合。


7.3、可变参数


7.3.1、可变参数介绍


可变参数是一种特殊的形式参数,定义在方法、构造器的形参列表处,它可以让方法接收多个同类型的实际参数。

可变参数在方法内部,本质上是一个数组。





7.3.2、可变参数注意事项


1.一个形参列表中,只能有一个可变参数;否则会报错。





2.一个形参列表中如果多个参数,可变参数需要写在最后;否则会报错。



7.4、Collections集合工具类



7.3.1、addAll批量添加元素


7.3.2、shuffle打乱list集合顺序


7.3.3、sort对List集合进行升序排序



7.3.4、sort对List集合按照比较器进行排序

数值升序排序:




字符串升序排序:





降序排序:




排序总结:
o1-o2(左边减右边)升序排序。
o2-o1(右边减左边)降序排序。

7.5、树数据结构

7.5.1、普通二叉树

普通二叉树:没有排序,查找起来就会特别复杂。如果是已经排好序的元素,存放到二叉树中就跟链表一样,性能并没有提升。


7.5.2、红黑树


红黑树,又叫平衡二叉树,会根据树中的元素自动旋转整个树来确保左右的平衡性。如果右边添加的元素太多了,下图的根节点就会从13移动到17或者25的位置,就是要确定保证树的左右平衡。



红黑树能实现元素的快速查找:


比如要查找22这个元素的值,比较4次就可以找到了。第1次和13判断,大于13,走右边,大于17,再走右边,小于25,就走左边。比较4次就能找到22这个元素了。


7.5.3、二叉树的遍历方式


二叉树的遍历方式有三种,分别是前序遍历(VLR)、中序遍历(LDR)和后序遍历(LRD)。


前序遍历的顺序是“根左右”,即首先访问根节点,然后遍历左子树,最后遍历右子树。


中序遍历的顺序是“左根右”,即首先遍历左子树,然后访问根节点,最后遍历右子树。


后序遍历的顺序是“左右根”,即首先遍历左子树,然后遍历右子树,最后访问根节点。


红黑树的默认遍历方式是中序遍历:根—>左—>右。


7.6、Set接口


7.5.1、HashSet

HashSet集合底层是基于哈希表实现的,哈希表根据JDK版本的不同,也是有点区别的。


JDK8以前:哈希表 = 数组+链表。

JDK8开始:哈希表 = 数组+链表+红黑树。





jdk8版本的HashSet扩容原理:


当创建一个集合时,哈希表会创建一个默认长度为16的数组来存储元素。默认加载因子是0.75,当存储容量达到3/4的时候就会进行自动扩容倍,它会自动扩容至原来的两倍(乘以2),首次扩容后,数组的容量会从16变为32。


当向集合中添加元素时,Java会根据hashCode对数组长度进行取余确定元素存储位置。具体来说,它对元素的hashCode值进行取余操作,以确定元素在数组中的索引位置,例如第一次添加元素时,将元素的hashCode除以16,如果余数为8,元素就放在数组索引为8的位置。第二次添加元素时,还是对16进行取余,如果余数还是8,元素会被挂在原来元素的下面。


在数组容量扩容至两倍(即乘以2)后,对新的数组长度(即32)进行取余操作,并根据余数确定新元素的位置。这种机制有助于提高集合的存储效率和查找速度。


当数组长度大于等于64时,有链表长度超过8,该链表就会自动转换成红黑树,当该红黑树的元素小于等于6时,又会将红黑树转为链表。


7.5.2、HashSet如何确保元素唯一性

如何实现唯一性:

如果往集合中存储String或者Integer等jdk提供的对象调用它们本身hashCode和equals方法就能达到排重的效果如果是自己创建的对象,需要重写元素类的hashCode和equals方法,指定重复规则来确保集合元素的唯一性。



去重原理:

当HashSet里面插入一个对象,首先判断插入对象的hashCode值是否在Set集合中存在,如果hashCode值不存在则直接把对象放到集合中去;值存在也不能判断两个对象是相一样。还需调用equals方法判断对象的属性值是否相等,如果类中的元素都相等,就自动排重(后进来的元素替换掉原来的元素);如果不相同就可以把元素直接添加到集合中。


7.5.3、TreeSet

TreeSet集合的特点是可以对元素进行排序,如果往集合中存储String类型的元素,或者Integer类型的元素,它们本身就具备排序规则,所以直接就可以排序。


但是如果添加集合中的元素是自己对象,则必须指定元素的排序规则,否则会抛出异常。




排序方式1:让元素的类实现Comparable接口,重写compareTo方法。




实现步骤:

1.让集合的元素类实现Comparable接口。

2.重写comparaTo方法,this-o是升序排序;o-this是降序排序。





排序方式2:在创建TreeSet集合时,通过构造方法传递Compartor比较器对象。



实现步骤:创建TreeSet集合时,传递比较器对象排序。


Double.compare(double x, double y) 是用于比较两个 double 类型的值。

Integer.compare(int x, int y) 是用于比较两个 int 类型的值。


第 8 章

Map双列集合



8.1、HashMap

8.1.1、HashMap的工作原理

远方的音讯
梧桐长成凤凰至,人伴贤良品行高!
 最新文章