目录
总体
List = 排成一长队的小猪
Set = 一群小猪贴上号,然后赶到一个猪圈里
Map = 放在一个个,有房间号的屋子里面的一群小猪
Java集合主要有 3 种重要的类型:
List:
有序 可重复
Set:
无序 不可重复
Map:
无序 key不允许重复,value可以重复
(身份证号—姓名)
1、List集合
常用方法
方法 | 含义 |
---|---|
void add(int index, Object element) | 添加元素(默认都是向集合末尾添加元素) |
Object set(int index, Object element) | 修改指定位置的元素 |
Object get(int index) | 取出指定的集合元素 |
int indexOf(Object o) | 获取指定对象第一次出现处的索引 |
int lastIndexOf(Object o) | 获取指定对象最后一次出现处的索引 |
Object remove(int index) | 删除指定下标位置的元素 |
- 演示
public class ListTest01 {
public static void main(String[] args) {
// 创建List类型的集合。
//List myList = new LinkedList();
//List myList = new Vector();
List myList = new ArrayList();
// 添加元素
myList.add("A"); // 默认都是向集合末尾添加元素。
myList.add("B");
myList.add("C");
myList.add("C");
myList.add("D");
//在列表的指定位置插入指定元素(第一个参数是下标)
// 这个方法使用不多,因为对于ArrayList集合来说效率比较低。
myList.add(1, "KING");
// 迭代
Iterator it = myList.iterator();
while(it.hasNext()){
Object elt = it.next();
System.out.println(elt);
}
// 根据下标获取元素
Object firstObj = myList.get(0);
System.out.println(firstObj);
// 因为有下标,所以List集合有自己比较特殊的遍历方式
// 通过下标遍历。【List集合特有的方式,Set没有。】
for(int i = 0; i < myList.size(); i++){
Object obj = myList.get(i);
System.out.println(obj);
}
// 获取指定对象第一次出现处的索引。
System.out.println(myList.indexOf("C")); // 3
// 获取指定对象最后一次出现处的索引。
System.out.println(myList.lastIndexOf("C")); // 4
// 删除指定下标位置的元素
// 删除下标为0的元素
myList.remove(0);
System.out.println(myList.size()); // 5
// 修改指定位置的元素
myList.set(2, "Soft");
// 增强for
for (Object data : myList) {
System.out.println(data);
}
}
}
-
遍历方式:for、迭代器、forEach
-
List 接口下面主要有两个实现 ArrayList 和 LinkedList,他们都是有顺序的,也就是放进去 是什么顺序,取出来还是什么顺序,也就是基于线性存储,可以看作是一个可变数组
-
ArrayList:查询数据比较快,添加和删除数据比较慢(基于可变数组)
-
LinkedList:查询数据比较慢,添加和删除数据比较快(基于链表数据结构)
-
Vector:Vector 已经不建议使用,Vector 中的方法都是同步的,效率慢,已经被 ArrayList 取代
-
Stack是继承 Vector 实现了一个栈,栈结构是后进先出,目前已经被 LinkedList 取代
01、ArrayList
简介
- 1、默认初始化容量
10
(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10。) - 2、集合底层是一个
Object[]数组
。 - 3、构造方法:
- new ArrayList();
- new ArrayList(20);
- 4、ArrayList集合的扩容:
增长到原容量的1.5倍
ArrayList集合底层是数组,怎么优化?
尽可能少的扩容。因为数组扩容效率比较低,建议在使用ArrayList集合的时候预估计元素的个数,给定一个初始化容量。
- 5、数组优点:
检索效率比较高。(每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,
然后知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高。)
- 6、数组缺点:
随机增删元素效率比较低。
另外数组无法存储大数据量。(很难找到一块非常巨大的连续的内存空间。)
- 7、
向数组末尾添加元素,效率很高,不受影响。
- 8、面试官经常问的一个问题?
这么多的集合中,你用哪个集合最多?
答:ArrayList集合。
因为往数组末尾添加元素,效率不受影响。
另外,我们检索/查找某个元素的操作比较多。
- 7、ArrayList集合是非线程安全的。(不是线程安全的集合。)
- 8、注意:ArrayList之所以检索效率比较高,不是单纯因为下标的原因。是因为底层
数组
发挥的作用。
02、单向链表 双向链表
- 单向链表(每一个节点在内存中存储上在空间位置都是无规律的)
- 双向链表
03、LinkedList
简介
- 底层是一个双向链表
链表的优点:
由于链表上的元素在空间存储上内存地址不连续。
所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。
在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议
使用LinkedList。
链表的缺点:
不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头
节点开始遍历,直到找到为止。所以LinkedList集合检索/查找的效率
较低。
ArrayList:把检索发挥到极致。(末尾添加元素效率还是很高的。)
LinkedList:把随机增删发挥到极致。
加元素都是往末尾添加,所以ArrayList用的比LinkedList多。
- LinkedList集合
没有初始化容量
最初这个链表中没有任何元素。first和last引用都是null。 - LinkedList集合也有下标,但是检索/查找某个元素的时候效率
比较低
,因为只能从头节点开始一个一个遍历
04、Vector
简介
-
1、底层也是一个数组。
2、初始化容量:10
3、怎么扩容的?扩容之后是原容量的2倍。 10--> 20 --> 40 --> 80
4、ArrayList集合扩容特点:
ArrayList集合扩容是原容量1.5倍。5、Vector中所有的方法都是线程同步的,都带有synchronized关键字,
是线程安全的。效率比较低,使用较少了。6、怎么将一个线程不安全的ArrayList集合转换成线程安全的呢?
使用集合工具类:
java.util.Collections;java.util.Collection 是集合接口。 java.util.Collections 是集合工具类。
2、Set集合
- 是一个无序集合,不允许放重复的数据
- 没有下标
01、HashSet
简介
-
底层实际上是一个HashMap,HashMap底层采用了哈希表的数据结构
-
HashSet其实是HashMap的
key
部分 -
HashSet集合初始化容量
16
,默认加载因子是0.75
-
扩容:扩容之后是原容量
2
倍。
02、TreeSet
简介
- 无序不可重复,但是存储的元素可以自动按照大小顺序排序
- 这里的无需指的是存进去的顺序和取出来的顺序不同,而且没有下标
- TreeSet集合底层实际上是TreeMap(new TreeSet集合的时候,底层实际上new了一个TreeMap集合。)
- TreeMlap集合底层采用了二叉树数据结构。
数据库中有很多数据:
userid name birth
-------------------------------------
1 zs 1980-11-11
2 ls 1980-10-11
3 ww 1981-11-11
4 zl 1979-11-11
编写程序从数据库当中取出数据,在页面展示用户信息的时候按照生日升序或者降序。
这个时候可以使用TreeSet集合,因为TreeSet集合放进去,拿出来就是有顺序的
- 无序不可重复,可以自动按照大小顺序排序
public class TreeSetTest02 {
public static void main(String[] args) {
// 创建一个TreeSet集合
TreeSet<String> ts = new TreeSet<>();
// 添加String
ts.add("zhangsan");
ts.add("lisi");
ts.add("wangwu");
ts.add("zhangsi");
ts.add("wangliu");
// 遍历
for(String s : ts){
// 按照字典顺序,升序!
System.out.println(s);
}
TreeSet<Integer> ts2 = new TreeSet<>();
ts2.add(100);
ts2.add(200);
ts2.add(900);
ts2.add(800);
ts2.add(600);
ts2.add(10);
for(Integer elt : ts2){
// 升序!
System.out.println(elt);
}
}
}
- 输出(升序)
lisi
wangliu
wangwu
zhangsan
zhangsi
10
100
200
600
800
900
03、TreeSet的排序
对自定义的类型来说,TreeSet可以排序吗?
-
以下程序中对于Person类型来说,无法排序。因为没有指定Person对象之间的比较规则。谁大谁小并没有说明
-
运行的时候出现了这个异常: 类型转换异常
java.lang.ClassCastException: class com.bjpowernode.javase.collection.Person cannot be cast to class java.lang.Comparable
-
异常原因:
Person类没有实现java.lang.Comparable接口
。
public class TreeSetTest03 {
public static void main(String[] args) {
Person p1 = new Person(32);
//System.out.println(p1);
Person p2 = new Person(20);
Person p3 = new Person(30);
Person p4 = new Person(25);
// 创建TreeSet集合
TreeSet<Person> persons = new TreeSet<>();
// 添加元素
persons.add(p1);
persons.add(p2);
persons.add(p3);
persons.add(p4);
// 遍历
for (Person p : persons){
System.out.println(p);
}
}
}
class Person {
int age;
public Person(int age){
this.age = age;
}
// 重写toString()方法
public String toString(){
return "Person[age="+age+"]";
}
}
改进方式1 (编写比较规则 实现java.lang.Comparable接口 实现compareTo方法)
public class TreeSetTest04 {
public static void main(String[] args) {
Customer c1 = new Customer(32);
Customer c2 = new Customer(20);
Customer c3 = new Customer(30);
Customer c4 = new Customer(25);
// 创建TreeSet集合
TreeSet<Customer> customers = new TreeSet<>();
// 添加元素
customers.add(c1);
customers.add(c2);
customers.add(c3);
customers.add(c4);
// 遍历
for (Customer c : customers){
System.out.println(c);
}
}
}
// 放在TreeSet集合中的元素需要实现java.lang.Comparable接口。
// 并且实现compareTo方法。equals可以不写。
class Customer implements Comparable<Customer>{
int age;
public Customer(int age){
this.age = age;
}
//编写比较规则
@Override
public int compareTo(Customer c) {
// c1.compareTo(c2);
// this是c1 / c是c2 / c1和c2比较的时候,就是this和c比较
/*int age1 = this.age;
int age2 = c.age;
if(age1 == age2){
return 0;
} else if(age1 > age2) {
return 1;
} else {
return -1;
}*/
//return this.age - c.age; // =0 >0 <0 //升序
return c.age - this.age; //降序
}
public String toString(){
return "Customer[age="+age+"]";
}
}
- 输出
Customer[age=32]
Customer[age=30]
Customer[age=25]
Customer[age=20]
练习编写比较规则
-
要求: 先按照年龄升序,如果年龄一样的再按照姓名升序。
-
compareTo方法的返回值很重要:(底层是自平衡二叉树)
- 返回0表示相同,value会覆盖。
- 返回>0,会继续在右子树上找。【10 - 9 = 1 ,1 > 0的说明左边这个数字比较大。所以在右子树上找。】
- 返回<0,会继续在左子树上找。。
public class TreeSetTest05 {
public static void main(String[] args) {
TreeSet<Vip> vips = new TreeSet<>();
vips.add(new Vip("zhangsi", 20));
vips.add(new Vip("zhangsan", 20));
vips.add(new Vip("king", 18));
vips.add(new Vip("soft", 17));
for(Vip vip : vips){
System.out.println(vip);
}
}
}
class Vip implements Comparable<Vip>{
String name;
int age;
public Vip(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Vip{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Vip v) {
// 写排序规则,按照什么进行比较。
if(this.age == v.age){
// 年龄相同时按照名字排序。
// 姓名是String类型,可以直接比。调用compareTo来完成比较。
return this.name.compareTo(v.name);
} else {
// 年龄不一样
return this.age - v.age;
}
}
}
- 输出:
Vip{name='soft', age=17}
Vip{name='king', age=18}
Vip{name='zhangsan', age=20}
Vip{name='zhangsi', age=20}
改进方式2 (使用比较器的方式)
-
比较器实现java.util.Comparator接口。(Comparable是java.lang包下的。Comparator是java.util包下的。)
public class TreeSetTest06 {
public static void main(String[] args) {
// 创建TreeSet集合的时候,需要使用这个比较器。
// TreeSet<WuGui> wuGuis = new TreeSet<>();//这样不行,没有通过构造方法传递一个比较器进去。
// 给构造方法传递一个比较器。
//TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());
// 编写比较器方式2 匿名内部类
TreeSet<WuGui> wuGuis = new TreeSet<>(new Comparator<WuGui>() {
@Override
public int compare(WuGui o1, WuGui o2) {
return o1.age - o2.age;
}
});
wuGuis.add(new WuGui(1000));
wuGuis.add(new WuGui(800));
wuGuis.add(new WuGui(810));
for(WuGui wuGui : wuGuis){
System.out.println(wuGui);
}
}
}
// 乌龟
class WuGui{
int age;
public WuGui(int age){
this.age = age;
}
@Override
public String toString() {
return "小乌龟[" +
"age=" + age +
']';
}
}
// 编写比较器方式1 定义类
/*
class WuGuiComparator implements Comparator<WuGui> {
@Override
public int compare(WuGui o1, WuGui o2) {
// 指定比较规则
// 按照年龄排序
return o1.age - o2.age;
}
}*/
-
最终的结论:
-
放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
第一种:放在集合中的元素实现java.lang.Comparable接口。
第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。
-
Comparable和Comparator怎么选择呢?
当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。
-
Comparator接口的设计符合OCP原则。
-
3、Map集合
简介
- Map和Collection没有继承关系
- Map集合以key和value的方式存储数据:
键值对
- key和value都是
引用数据类型
。 - key和value都是存储对象的
内存地址
。 - key起到主导的地位,value是key的一个附属品。
常用方法
方法 | 含义 | |
---|---|---|
1 | V put (K key, V value) | 向Map集合中添加键值对 |
2 | V get (Object key) | 通过key获取value |
3 | void clear () | 清空Map集合 |
4 | boolean containsKey (Object key) | 判断Map中是否包含某个key |
5 | boolean containsValue (Object value) | 判断Map中是否包含某个value |
6 | boolean isEmpty () | 判断Map集合中元素个数是否为0 |
7 | V remove (Object key) | 通过key删除键值对 |
8 | int size() | 获取Map集合中键值对的个数 |
9 | Collection values() | 获取Map集合中所有的value,返回一个Collection |
10 | Set keySet() | 获取Map集合所有的key(所有的键是一个set集合) |
11 | Set<Map.Entry<K,V>> entrySet() | 将Map集合转换成Set集合(Set集合中元素的类型是:Map.Entry) |
- 演示
public class MapTest01 {
public static void main(String[] args) {
// 创建Map集合对象
Map<Integer, String> map = new HashMap<>();
// 1、向Map集合中添加键值对
map.put(1, "zhangsan"); // 1在这里进行了自动装箱。
map.put(2, "lisi");
map.put(3, "wangwu");
map.put(4, "zhaoliu");
// 2、通过key获取value
String value = map.get(2);
System.out.println(value);
// 8、获取键值对的数量
System.out.println("键值对的数量:" + map.size());
// 7、通过key删除key-value
map.remove(2);
// 4、判断是否包含某个key
// contains方法底层调用的都是equals进行比对的,所以自定义的类型需要重写equals方法。
System.out.println(map.containsKey(new Integer(4))); // true
// 5、判断是否包含某个value
System.out.println(map.containsValue(new String("wangwu"))); // true
// 9、获取所有的value
Collection<String> values = map.values();
// foreach
for(String sss : values){
System.out.println(sss);
}
// 10、获取Map集合所有的key
// 输出:[1, 3, 4]
Set<Integer> qqq = map.keySet();
System.out.println(qqq);
// 11、获取Map集合所有的key(所有的键是一个set集合)
// 输出:[1=zhangsan, 3=wangwu, 4=zhaoliu]
Set<Map.Entry<Integer, String>> entries = map.entrySet();
System.out.println(entries);
// 3、清空map集合
map.clear();
System.out.println("键值对的数量:" + map.size());
// 6、判断是否为空
System.out.println(map.isEmpty()); // true
}
}
entrySet详解
entrySet:详解:
11、将Map集合转换成Set集合(Set集合中元素的类型是:Map.Entry)
Set<Map.Entry<K,V>> entrySet()
将Map集合转换成Set集合
假设现在有一个Map集合,如下所示:
map1集合对象
key value
----------------------------
1 zhangsan
2 lisi
3 wangwu
4 zhaoliu
----------------------------
Set set = map1.entrySet();
set集合对象
[1=zhangsan,
2=lisi,
3=wangwu,
4=zhaoliu] ---> 这个东西是个什么?Map.Entry
----------------------------------------------------------
注意:Map集合通过entrySet()方法转换成的这个Set集合,
Set集合中元素的类型是 Map.Entry<K,V>
----------------------------------------------------------
Map.Entry和String一样,都是一种类型的名字,
只不过:Map.Entry是静态内部类,是Map中的静态内部类
01、Map集合的遍历方式
- 数据准备
Map<Integer, String> map = new HashMap<>();
map.put(1, "zhangsan");
map.put(2, "lisi");
map.put(3, "wangwu");
map.put(4, "zhaoliu");
- 遍历方式1 (常用的遍历式)
- 通过 map.entrySet() 遍历
- 比较适合于大数据量
- 效率较高。因为获取key和value都是直接从node对象中获取的属性值
for(Map.Entry<Integer,String> node : map.entrySet()){ //将Map集合转换成Set集合
System.out.println(node.getKey() + "--->" + node.getValue());
}
- 遍历方式2
- 通过keySet get(key) 遍历
Set<Integer> keys = map.keySet(); //获取Map集合所有的key
for(Integer key : keys){
System.out.println(key + "=" + map.get(key));
}
- 遍历方式3
- 通过 iterator迭代器和 keySet get(key)遍历
// 获取所有的key,所有的key是一个Set集合
Set<Integer> keys = map.keySet();
// 遍历key,通过key获取value
Iterator<Integer> it = keys.iterator();
while(it.hasNext()){
// 取出其中一个key
Integer key = it.next();
// 通过key获取value
String value = map.get(key);
System.out.println(key + "=" + value);
}
- 遍历方式4
- 通过 iterator迭代器遍历
// Set<Map.Entry<K,V>> entrySet()
// 以上这个方法是把Map集合直接全部转换成Set集合。
// Set集合中元素的类型是:Map.Entry
Set<Map.Entry<Integer,String>> set = map.entrySet(); //把Map集合直接全部转换成Set集合
// 遍历Set集合,每一次取出一个Node
// 迭代器
Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
while(it2.hasNext()){
Map.Entry<Integer,String> node = it2.next();
/*
此时 node:
1=zhangsan
2=lisi
3=wangwu
4=zhaoliu
*/
Integer key = node.getKey();
String value = node.getValue();
System.out.println(key + "=" + value);
}
- 遍历方式5
- Lambda表达式
map.forEach((key,value) -> {
System.out.println(key + "===" +value);
});
02、HashMap浅谈
-
1、HashMap集合底层是 哈希表/散列表 的数据结构。
-
2、哈希表是一个怎样的数据结构呢?
哈希表是一个
数组
和单向链表
的结合体。
数组:在查询方面效率很高,随机增删方面效率很低
。
单向链表:在随机增删方面效率较高,在查询方面效率很低
。
哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点。 -
3、HashMap集合底层的源代码:
public class HashMap{ // HashMap底层实际上就是一个数组。(一维数组) Node<K,V>[] table; // 静态的内部类HashMap.Node static class Node<K,V> { final int hash; // 哈希值(哈希值是key的hashCode()方法的执行结果。hash值通过哈希函数/算法,可以转换存储成数组的下标。) final K key; // 存储到Map集合中的那个key V value; // 存储到Map集合中的那个value Node<K,V> next; // 下一个节点的内存地址。 } }
-
5、HashMap集合的key部分特点:
- 无序,不可重复。
- 为什么无序? 因为不一定挂到哪个单向链表上。
- 不可重复是怎么保证的?
equals
方法来保证HashMap集合的key不可重复。 - 如果key重复了,value会
覆盖
。 - 放在HashMap集合key部分的元素其实就是放到HashSet集合中了。
- HashSet集合中的元素也需要同时
重写
hashCode()+equals()方法。
-
6、哈希表HashMap使用不当时无法发挥性能!
假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了 纯单向链表。这种情况我们成为:散列分布不均匀。 什么是散列分布均匀? 假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的, 是散列分布均匀的。 假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题? 不行,因为这样的话导致底层哈希表就成为一维数组了,没有链表的概念了。 也是散列分布不均匀。 散列分布均匀需要你重写hashCode()方法时有一定的技巧。
-
7、重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法。
-
8、HashMap集合的默认初始化容量是
16
,默认加载因子是0.75
这个默认加载因子是当HashMap集合底层数组的容量达到
75%
的时候,数组开始扩容。重点,记住:HashMap集合
初始化容量必须是2的倍数
,这也是官方推荐的,这是因为达到散列均匀,为了提高HashMap集合的存取效率,所必须的。
-
9、JDK8之后哈希表单向链表的元素
超过8个时 ---> 变为红黑树
红黑树的节点
小于6个时 ---> 变为单向链表
03、HashMap为什么需要重写HashCode和equals
1、向Map集合中存,以及从Map集合中取,都是先调用key的hashCode方法,然后再调用equals方法!
equals方法有可能调用,也有可能不调用。
- 拿put(k,v)举例,什么时候equals不会调用?
- k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。
- 数组下标位置上如果是null,equals不需要执行。
- 拿get(k)举例,什么时候equals不会调用?
- k.hashCode()方法返回哈希值,哈希值经过哈希算法转换成数组下标。
- 数组下标位置上如果是null,equals不需要执行。
2、注意:
-
如果一个类的equals方法重写了,那么hashCode()方法必须重写。
并且equals方法返回如果是
true
),hashCode()方法返回的值必须一样
。 -
equals方法返回true(代表在
同一个单向链表
上)表示两个对象相同,在同一个单向链表上比较。 -
那么对于
同一个
单向链表上的节点来说,他们的哈希值都是相同的。 -
所以hashCode()方法的返回值也应该相同。
3、结论:
- 放在
HashMap
集合key
部分的,以及放在HashSet
集合中的元素,需要同时重写hashCode方法和equals方法。 - 使用HashMap,如果key是自定义的类,就必须重写hashcode()和equals()。
4、对于哈希表数据结构来说:
- 如果o1和o2的hash值相同,一定是放到
同一个单向链表
上。 - 当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。
- 定义一个实体类
public class Student {
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
// equals(如果学生名字一样,表示同一个学生。)
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return Objects.equals(name, student.name);
}
// hashCode
@Override
public int hashCode() {
return Objects.hash(name);
}
}
- 定义一个实现类
public class HashMapTest02 {
public static void main(String[] args) {
Student s1 = new Student("zhangsan");
Student s2 = new Student("zhangsan");
// 重写equals方法之前是false
//System.out.println(s1.equals(s2)); // false
// 重写equals方法之后是true
System.out.println(s1.equals(s2)); //true (s1和s2表示相等)
//重写hashCode之前 284720968 (重写hashCode之后 -1432604525)
System.out.println("s1的hashCode=" + s1.hashCode());
//重写hashCode之前 122883338 (重写hashCode之后 -1432604525)
System.out.println("s2的hashCode=" + s2.hashCode());
Set<Student> students = new HashSet<>();
students.add(s1);
students.add(s2);
System.out.println(students.size());
}
}
- 输出
true
s1的hashCode=-1432604525
s2的hashCode=-1432604525 (如果实体类不重写HashCode方法,这两个HashCode的输出结果是不同的)
1 (如果不重写HashCode方法,此处输出的结果是2)
- HashMap集合key和value允许null
- 但是要注意:HashMap集合的key null值只能有一个。
public class HashMapTest03 {
public static void main(String[] args) {
Map map = new HashMap();
// HashMap集合允许key和value为null
map.put(null, null);
System.out.println(map.size()); // 1
// key重复的话value是覆盖!
map.put(null, 100);
System.out.println(map.size()); //1
// 通过key获取value
System.out.println(map.get(null)); // 100
}
}
04、HashTable
- Hashtable的key和value都是不能为null的。
- Hashtable方法都带有synchronized:线程安全的。
线程安全有其它的方案,这个Hashtable对线程的处理
导致效率较低,使用较少了。 - Hashtable和HashMap一样,底层都是哈希表数据结构。
- Hashtable的初始化容量是
11
,默认加载因子是:0.75f
- Hashtable的扩容是:
原容量 * 2 + 1
public class HashtableTest01 {
public static void main(String[] args) {
Map map = new Hashtable();
//空指针异常
map.put(null, "123");
map.put(100, null);
}
}
05、Properties
-
Properties是一个Map集合,继承Hashtable,Properties的key和value都是
String类型
。 -
Properties被称为
属性类对象
。 -
Properties是 线程安全的。
public class PropertiesTest01 {
public static void main(String[] args) {
// 创建一个Properties对象
Properties pro = new Properties();
// 需要掌握Properties的两个方法,一个存,一个取。
pro.setProperty("url", "jdbc:mysql://localhost:3306/bjpowernode");
pro.setProperty("driver","com.mysql.jdbc.Driver");
pro.setProperty("username", "root");
pro.setProperty("password", "123");
// 通过key获取value
String url = pro.getProperty("url");
String driver = pro.getProperty("driver");
String username = pro.getProperty("username");
String password = pro.getProperty("password");
System.out.println(url);
System.out.println(driver);
System.out.println(username);
System.out.println(password);
}
}