1、集合概述
---1.1集合继承结构图
---1.2Map集合继承结构图
---1.3集合的所有实现类
2、关于Collection接口
---2.1常用方法
---2.2Collection集合迭代
---2.3contains方法分析
---2.4remove方法分析
---2.5迭代器的更新
3、关于List接口
---3.1常用方法
---3.2ArrayList集合
---3.3LinkedList集合
---3.4Vector集合
4、关于Set接口
---4.1Set下的HashSet集合和TreeSet集合
5、关于Map接口
---5.1Map概述
---5.2Map接口中的常用方法
---5.3遍历Map集合
6、HashMap集合
---6.1HashMap集合概述
---6.2map.put(k,v)实现原理:
---6.3v = map.get(k)实现原理:
---6.4为什么哈希表的随机增删,以及查询效率都很高?
---6.5图例:
---6.6同时重写hashCode()和equals():
---6.7HashMap和Hashtable的区别
7、Hashtable和Properties属性类对象
---7.1Properties概述
---7.2代码示例
8、SortedMap和TreeMap
---8.1概述
---8.2TreeSet的自定义类型排序
---8.3实现Comparable接口
---8.4自平衡二叉树
---8.5实现比较器接口
9、Collections工具类
集合概述
- 集合概述
1、什么是集合?有什么用?
数组其实就是一个集合,集合实际上就是一个容器。可以来容纳其它类型的数据。
2、集合为什么说在开发中使用较多?
集合是一个容器,是一个载体,可以一次容纳多个对象。
在实际开发中,假设连接数据库,数据库当中有10条记录,那么假设把这10条记录查询出来,在Java程序中会将10条数据封装成10个java对象,然后将10个java对象放到某一个集合当中,将集合传到前端,然后遍历集合,将一个数据一个数据展现出来。
3、集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,集合当中存储的都是Java对象的内存地址。(或者说集合中存储的是引用)
list.add(100);//自动装箱Integer
注意:
集合在java中本身是一个容器,是一个对象。
集合中任何时候存储的都是"引用"。
4、在java中每一个不同的集合,底层会对应不同的数据结构。往不同的集合中存储元素,等于将数据放到了不同的数据结构当中。
5、什么是数据结构?
数据存储的结构就是数据结构。不同的数据结构,数据存储方式不同。例如:数组、二叉树、链表、哈希表..
以上这些都是常见的数据结构
例如:往集合01中放数据,可能是放到数组上了;
往集合02中放数据,可能是放到二叉树上了。使用不同的集合等同于使用了不同的数据结构。
6、集合的使用和所在包
new ArrayList(); 创建一个集合,底层是数组。
new LinkedList(); 创建一个集合对象,底层是链表。
new Treeset (); 创建一个集合对象,底层是二叉树。
所有的集合类和集合接口都在java.util包下。
7、在java中集合分为两大类:
一类是单个方式存储元素:单个方式存储元素,这一类集合中超级父接口:Java.util.Collection;
一类是以键值对儿的方式存储元素。这一类集合中超级父接口: Java.util.Map;
- 集合继承结构图
1、
2、List集合
List集合存储元素特点:有序可重复,存储的元素有下标。
有序实际上是说存进去是这个顺序,取出来还是这个顺序。这里的顺序不是说按照大小排序。
有序是因为List集合都有下标,下标从0开始,以1递增。
3、Set集合
Set集合存储元素特点:无序不可重复。
无序表示存进去是这个顺序,取出来就不一定是这个顺序了。另外Set集合中元素没有下标,Set集合中的元素还不能重复。
4、list集合下的
ArrayList:
ArrayList集合底层采用了数组这种数据结构,ArrayList集合是非线程安全的。
LinkedList:
LinkedList集合底层采用了双句链表数据结构。
Vector:
Vector集合底层采用了数组这种数据构,Vector集合是线程安全的。Vector所有的方法都有synchronized关键字修饰。所以线程安全,但是效率较低,现在保证线程安全有别的方案,所以Vector使用较少了。
5、Set集合下的
HashSet:
实际上HashSet集合在new的时候底层实际上new了一个HashMap集合。向HashSet集合中存储元素,实际上是存储到HashMap集合中了。HashMap集合是一个哈希表数据结构。
SortedSet:
SortedSet集合存储元素的特点:由于继承了Set集合,所以它的特点也是无序不可重复,但是放在SortedSet集合中的元素可以自动排序成为可排序集合。放到该集合中的元素是自动按照大小顺序排序的。
SortedSet--->TreeSet:
TreeSet集合底层实际上是TreeMap new TreeSet集合的时候,底层实际上new 一个TreeMap集合。往TreeSet集合中放数据的时候,实际上是将数据放到TreeMap集合中了。TreeMap集合底层采用了二叉树数据结构。
- Map集合继承结构图
Map集合的key,就是一个Set集合。
往Set集合中放数据,实际上放到了Map集合的key部分。
- 总结所有的实现类
ArrayList:底层是数组。
LinkedList:底层是双向链表。
Vector:底层是数组,线程安全的,效率较低,使用较少。
HashSet:底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分了。
Treeset:底层是TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合key部分了。
HashMap:底层是哈希表。
Hashtable:底层是哈希表,只不过线程安全,效率较低,使用较少。
Properties:是线程安全的,并且key和value只能存储字符串String。
TreeMap:底层是二叉树,TreeMap集合的key可以自动按照大小顺序排序。 - 各集合特点
1、List集合存储元素的特点:有序可重复。
有序:存进去的顺序和取出的顺序相同,每一个元素都有下标。
可重复:存进去1,可以再存储一个1。
2、Set (Map)集合存储元素的特点:无序不可重复。
无序:存进去的顺序和取出的顺序不一定相同,另外Set集合中元素没有下标。
不可重复:存进去1,不能再存储1了。
3、Sortedset (SortedMap)集合存储元素特点:首先是无序不可重复的,但是SortedSet集合中的元素是可排序的。
无序:存进去的顺序和取出的顺序不一定相同。另外Set集合中元素没有下标。
不可重复:存进去1,不能再存储1了。
可排序:可以按照大小顺序排列。
关于Collection接口
- 常用方法
1、boolean add(E e);(E是Object类型)(向集合中添加元素)
2、int size();(获取集合中元素的个数)(不是容量)
3、void clear();(清空集合)
4、boolean contains (Object o);(判断当前集合中是否包含元素o)
5、boolean remove(Object o);(删除集合中某个元素)
6、boolean isEmpty();( 判断该集合中元素的个数是否为0)
7、Object[] toArray();(将集合转换为数组)
代码示例:
import java.util.ArrayList;
import java.util.Collection;
public class Demo{
public static void main(String[] args) {
Collection c = new ArrayList();
/*boolean add(E e);
(E是Object类型)
(向集合中添加元素)*/
c.add(1200);//自动装箱
c.add(1.23);
c.add(new Object());
c.add(true);
/*int size();
(获取集合中元素的个数)*/
System.out.println("集合中元素个数是:" + c.size());
/*void clear();
(清空集合)*/
c.clear();
System.out.println("集合中元素个数是:" + c.size());
/*boolean contains (Object o);
(判断当前集合中是否包含元素o)*/
c.add("钢铁侠");
c.add("蜘蛛侠");
c.add(1);
System.out.println(c.contains("钢铁侠"));
System.out.println("集合中元素个数是:" + c.size());
/*boolean remove(Object o);
(删除集合中某个元素)*/
c.remove(1);
System.out.println("集合中元素个数是:" + c.size());
/*boolean isEmpty();
判断该集合中元素的个数是否为0
*/
System.out.println(c.isEmpty());
/*Object[] toArray();
将集合转换为数组
*/
Object[] obj = c.toArray();
for (int i = 0; i < obj.length; i++) {
System.out.println(obj[i]);
}
}
}
输出:
- Collection集合迭代
1、以下的遍历方式/迭代方式,是所有Collection通用的一种方式。在Map集合中不能用。在所有的Collection以及子类中使用。
2、图例
3、代码示例:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Demo{
public static void main(String[] args) {
Collection c = new ArrayList();
c.add(11);
c.add("qwer");
c.add(new Object());
//第一步:获取集合对象的迭代器对象Iterator
Iterator it = c.iterator();
/*第二步:通过以上获取的迭代器对象开始迭代/遍历集合。
以下两个方法是迭代器对象Iterator中的方法:
boolean hasNext() 如果仍有元素可以迭代,则返回true。
Object next() 返回迭代的下一个元素。*/
while (it.hasNext()){
/*
不管存进去什么类型,取出来还是什么类型。
但统一都是Object类型。
*/
Object obj = it.next();
System.out.println(obj);
}
}
}
输出:
- contains方法分析
1、contains方法是用来判断集合中是否包含某个元素的方法,它在底层调用equals方法进行比对。equals方法返回true,就表示包含这个元素。
代码示例:
import java.util.ArrayList;
import java.util.Collection;
public class Demo{
public static void main(String[] args) {
Collection c = new ArrayList();
String s1 = new String("qwe");
c.add(s1);
String s2 = new String("asd");
c.add(s2);
System.out.println("集合中元素个数是:" + c.size());//2
String x = new String("qwe");
System.out.println(c.contains(x));//true
}
}
2、放在集合里面的元素要重写equals方法
代码示例:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Objects;
public class Demo{
public static void main(String[] args) {
Collection c = new ArrayList();
User u1 = new User("qwe");
c.add(u1);
User u2 = new User("qwe");
/*没有重写equals之前,输出:false*/
//System.out.println(c.contains(u2));
/*重写equals之后,输出:true*/
System.out.println(c.contains(u2));
}
}
class User{
private String name;
public User() { }
public User(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(name, user.name);
}
}
- remove方法分析
1、同样:放在集合里面的元素要重写equals方法
代码示例:
public class Demo{
public static void main(String[] args) {
Collection c = new ArrayList();
User u1 = new User("qwe");
c.add(u1);
User u2 = new User("qwe");
c.remove(u2);
/*没有重写equals之前,输出:1*/
//System.out.println(c.size());
/*重写equals之后,输出:0*/
System.out.println(c.size());
}
}
- 集合结构只要发生改变,迭代器必须重新获取。
1、代码示例:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class Demo{
public static void main(String[] args) {
Collection c = new ArrayList();
c.add(1);
c.add(2);
c.add(3);
/*获取的迭代器对象,迭代器用来遍历集合,
此时相当于对当前集合的状态拍了一个快照。
迭代器迭代的时候会参照这个快照进行迭代。*/
Iterator it = c.iterator();
while (it.hasNext()){
Object obj = it.next();
c.remove(obj);
System.out.println(obj);
}
}
}
输出:
2、出异常的原因是:
这里输出了1之后才出现异常:java.util.ConcurrentModificationException
是因为第二次循环的时候没有重新获取迭代器。
所以出异常根本原因是:集合中元素删除了,但是没有更新迭代器(迭代器不知道集合变化了)。
在删除之后集合结构发生变化,应该重新去获取迭代器,但是这里没有。
c.remove(obj);直接通过集合去删除元素,没有通知迭代器,导致迭代器的快照和原集合状态不同。
3、所以该改为:
while (it.hasNext()){
Object obj = it.next();
/*迭代器去删除时,会自动更新送代器。
并且更新集合(删除集合中的元素)。
删除的一定是迭代器指向的当前元素。*/
it.remove();
System.out.println(obj);
}
输出:
关于List接口
- 常用方法
1、void add(int index, E element)
将指定的元素插入此列表中的指定位置(可选操作).
2、E get(int index)
返回此列表中指定位置的元素。
3、int indexOf(Object o)
返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。
4、int lastIndexOf(Object o)
返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
5、E remove(int index)
删除该列表中指定位置的元素(可选操作)。
6、E set(int index, E element)
用指定的元素(可选操作)替换此列表中指定位置的元素。
代码示例:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Demo{
public static void main(String[] args) {
List list = new ArrayList();
/*默认都是向集合末尾添加元素*/
list.add(11);
list.add(11);
list.add("qwe");
list.add("qwe");
/*void add(int index, E element)
使用不多,对于ArrayList集合来说效率较低*/
list.add(0,"r");
/*E get(int index)
因为有下标,可以用for循环来遍历*/
System.out.println("下标对应的元素为:" + list.get(0));
/*int indexOf(Object o)*/
System.out.println("第一次出现索引为:" + list.lastIndexOf("qwe"));
/*int lastIndexOf(Object o)*/
System.out.println("最后一次出现索引为:" + list.lastIndexOf(11));
/*E remove(int index)*/
list.remove(4);
/*E set(int index, E element)*/
list.set(1,"Thompson");
System.out.println("=============");
Iterator it = list.iterator();
while (it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
}
}
输出:
- ArrayList集合(最常用的集合)
1、默认初始化容量10
/*默认初始化容量是10,
(底层先创建了一个长度为0的教组,
当添加第一个元素的时候,才初始化容量为10*/
List list = new ArrayList();
//指定初始化容量
List list1 = new ArrayList(20);
2、集合底层是一个Object[]数组。
3、扩容:增长到原容量的1.5倍。Arraylist集合底层是数组。因为数组扩容效率比较低,建议在使用Arraylist集合的时候预估计元素的个数,给定一个初始化容量。
4、补充:二进制位运算
代码示例:
public class DemoTest{
public static void main(String[] args) {
/*二进制右移一位(除以2)*/
System.out.println(10>>1);//5
/*二进制左移一位(乘以2)*/
System.out.println(10<<1);//20
}
}
5、数组优点:检索效率比较高。
数组缺点:随机增删元素效率比较低,向数组末尾添加元素,效率很高。
6、ArrayList集合的另外一种构造方法
ArrayList(Collection<? extends E> c)
构造一个包含指定集合的元素的列表,按照它们由集合的迭代器返回的顺序。
代码示例:
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
public class Demo{
public static void main(String[] args) {
Collection c = new HashSet();
c.add(11);
c.add("qwe");
/*通过这个构造方法就可以
将HashSet集合转换成list集合*/
List list = new ArrayList(c);
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
}
}
输出:
7、ArrayList集合是非线程安全的。(不是线程安全的集合)
- LinkedList集合
1、优缺点
链表优点:随机增删元素效率较高。(因为增删元素不涉及到大量元素位移)
链表缺点:查询效率较低,每一次查找某个元素的时候都需要从头节点开始往下遍历。不能通过数学表达式计算被查找元素的内存地址。
2、单链表中的节点。
节点是单向链表中基本的单元。
每一个节点Node都有两个属性:
属性一:存储的数据。
属性二:下一个节点的内存地址。
3、代码理解:
public class Node {
//存储的数据
Object element;
//下一个节点的内存地址
Node next;
public Node() {
}
public Node(Object element, Node next) {
this.element = element;
this.next = next;
}
}
public class Link {
//头结点
Node header;
int size = 0;
public int size(){
return size;
}
//增
public void add(Object data){
if (header==null){
/*为空说明没有节点,
* new一个头结点对象。*/
header = new Node(data, null);
}else {
/*头结点已存在,需找出当前末尾节点,
* 使末尾节点的next是新节点*/
Node currentLastNode = findLast(header);
currentLastNode.next = new Node(data, null);
}
size++;
}
private Node findLast(Node node) {
if (node.next==null){
/*说明是末尾节点*/
return node;
}
/*说明不是末尾节点(递归算法)*/
return findLast(node.next);
}
//删...
//改...
//查...
}
4、双向链表图例:
LinkedList集合是双向链表。
- Vector集合
1、底层也是一个数组,初始化容量为10。
2、扩容之后是原容量的2倍。而ArrayList集合扩容是原容量1.5倍。
3、Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的。但效率较低,使用较少。
4、代码示例:
import java.util.Iterator;
import java.util.Vector;
public class Demo {
public static void main(String[] args) {
Vector vector = new Vector();
vector.add(11);
vector.add("qwe");
Iterator it = vector.iterator();
while (it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
}
}
5、怎么将一个线程不安全的ArrayList集合转换成线程安全的呢?
使用集合工具类:java.util. Collections;
java.util.Collection是集合接口。
java.util. Collections是集合工具类。
代码示例:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class Demo {
public static void main(String[] args) {
//非线程安全
List list = new ArrayList();
//变为线程安全
Collections.synchronizedList(list);
list.add(1);
list.add(2);
}
}
关于Set接口
- Set下的HashSet集合和TreeSet集合
1、HashSet集合:
①无序不可重复。
②底层是HashMap,放到HashSet集合中
的元素等同于放到HashMap集合key部分了。
③HashMap集合是一个哈希表数据结构,HashSet集合初始化容量是16,初始化容里建议是2的倍数。扩容之后是原容量的2倍。
2、TreeSet集合:
无序不可重复的,但是存储的元素可以自动按照大小顺序排序,称为:可排序集合。
这里的无序指的是存进去的顺序和取出来的顺序不同。并且没有下标。
关于Map接口
- Map概述
1、Map和Collection没有继承关系。
2、Map集合以key和value的方式存储数据:键值对。
key和value都是引用数据类型。
key和value都是存储对象的内存地址。
key起到主导的地位, value是key的一个附属品。
- Map接口中的常用方法
1、V put(K key, V value)
向Map集合中添加键值对。
2、V get(Object key)
如果有对应的key,则返回对应的value,没有返回null。
3、void clear()
从Map中删除所有的映射(清空Map集合)。
4、boolean containsKey(Object key)
判断Map中是否包含某个key,包含则返回true 。
5、boolean containsValue(Object value)
判断Map中是否包含某个value,包含则返回true 。
6、boolean isEmpty()
如果此Map不包含键值映射(元素个数为0),则返回true 。
7、Collectionvalues()
返回Map集合中所有的value,返回一个Collection。Set
8、V remove(Object key)
如果存在(从可选的操作),从该Map中删除一个键的映射。(删除键值对)
9、int size()
返回此Map中键值对的数量。
以上9种方法代码示例:
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
public class Demo{
public static void main(String[] args) {
//创建Map集合对象
Map<Integer, String> map = new HashMap<>();
//向Map集合中添加键值对
map.put(1, "zhangsan");
map.put(2, "lisi");
map.put(3, "wangwu");
map.put(4, "zhaoliu");
//通过可用获取value
String value = map.get(1);
System.out.println(value);
//获取键值对的数量
System.out.println("键值对的数量为:" + map.size());
map.remove(4);
System.out.println("删除后键值对的数量为:" + map.size());
//判断是否包含某个key
System.out.println(map.containsKey(1));//true
//判断是否包含某个value
/*contains方法底层调用的都是equals进行比对的,
所以自定义的类型需要重与equals方法。*/
System.out.println(map.containsValue("zhangsan"));//true
//获取所有的value
Collection<String> values = map.values();
for (String s : values) {
System.out.println("获取所有的value:" + s);
}
//清空map集合
map.clear();
System.out.println("清空后键值对的数量为:" + map.size());
//判断是否为空
System.out.println(map.isEmpty());//true
}
}
输出:
10、Set
返回此Map中包含的key。(所有的键是一个set集合)
11、Set<Map.Entry<K,V>> entrySet()
将Map集合转换为Set集合。(Set集合中元素的类型是Map.Entry<K,V>,是一个静态内部类)
- 遍历Map集合
第一种方式:获取所有的key,通过遍历key,来遍历value。
代码示例:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Demo{
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "zhangsan");
map.put(2, "lisi");
map.put(3, "wangwu");
//获取所有的key,所有的key是一个Set集合
Set<Integer> keys = map.keySet();
//遍历key,通过key获取value
//foreach遍历
/*for (Integer key:keys
) {
System.out.println(key + "=" + map.get(key));
}*/
//迭代器遍历
Iterator<Integer> it = keys.iterator();
while (it.hasNext()){
Integer key = it.next();
String value = map.get(key);
System.out.println(key + "=" + value);
}
}
}
输出:
第二种方式:Set<Map.Entry<K,V>> entrySet()
这个方法是把Map集合直接全部转换成Set集合。
这种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值。这种方式比较适合于大数据量。
图例:
代码示例:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
public class Demo{
public static void main(String[] args) {
Map<Integer, String> map = new HashMap<>();
map.put(1, "zhangsan");
map.put(2, "lisi");
map.put(3, "wangwu");
//Set集合中元素的类型是:Map.Entry
Set<Map.Entry<Integer,String>> set = map.entrySet();
//迭代器,遍历Set集合,每次取出一个Node
/*Iterator<Map.Entry<Integer,String>> it = set.iterator();
while (it.hasNext()){
Map.Entry<Integer,String> node = it.next();
Integer key = node.getKey();
String value = node.getValue();
System.out.println(key + "=" + value);
}*/
//foreach
for (Map.Entry<Integer,String> node:set
) {
System.out.println(node.getKey() + "=" + node.getValue());
}
}
}
输出:
HashMap集合
- HashMap集合概述
1、 HashMap集合底层是哈希表/散列表的数据结构。
2、哈希表是一个怎样的数据结构呢?
哈希表是一个数组和单向链表的结合体。
数组:在查询方面效率很高,随机增删方面效率很低。
单向链装:在随机增删方面效率较高,在查询方面效率很低。
哈希表得以上的两种数据结构融合在一起,充分发挥它们各自的优点。
3、HashMap集合底层的源代码:
//静态内部类HashMap.Node
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
/*哈希值(哈希值是key的
hashCode()方法的执行结果)。
hash值通过哈希函数/算法,
可以转换存储成数组的下标。*/
final K key;
V value;
HashMap.Node<K,V> next;
...
}
//HashMap底层实际上就是一个数组。(一维数组)
transient Node<K,V>[] table;
4、HashMap集合的默认初始化容量是16,当HashMap集合底层数组的容量达到75%的时(默认加载因子是0.75),数组开始扩容。HashMap集合的初始化容量必须是2的倍数。
- map.put(k,v)实现原理:
第一步:先将k,v封装到Node对象当中。
第二步:底层会调用k的hashCode0方法得出hash值,然后通过哈希函数/哈希算法,将hash值转换成数组的下标,下标位置上如果没有任何元素,就把Node添加到这个位置上了。如果说下标对应的位置上有链表,此时会拿着k和链表上每一个节点中的k进行equals,如果所有的equals方法返回都是false,那么这个新节点将会被添加到链表的末尾;如果其中有一个equals返回了true,那么这个节点的value将会被覆盖。
- v = map.get(k)实现原理:
1、先调用k的hashCodeo()方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置上,如果这个位置上什么也没有,返回null。
2、如果这个位置上有单向链表,那么会拿着参数k和单向链表上的每个节点中的k进行equals,如果所有equals方法返回false,那么get方法返回null;只要其中有一个节点的k和参数k进行equals的时候返回true,那么此时这个节点的value就是我们要找的,get方法最终返回这个value。
- 为什么哈希表的随机增删,以及查询效率都很高?
1、增删是在链表上完成。
2、查询也不需要都扫描,只需要部分扫描。
3、重点:通过讲解可以得出HashMap集合的key,会先后调用两个方法:一个方法是hashCode(),一个方法是equals(),那么这两个方法都需要重写。
- 图例:
- 同时重写hashCode()和equals():
1、同一个单向链表上所有节点的hash相同,因为他们的数组下标是一样的。但同一个链表上k和k的equals方法肯定返回的是false,都不相等。
2、hashCode()方法需要重写,在重写时不可以返回一个固定值,会变成一堆数组,没有链表的概念了。(散列分布不均匀)
3、所以放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法。且hashCode()方法先被调用。
4、equats方法返回如果是true(同一链表),hashCode()方法返回的值必须一样(哈希值要相同)。
5、在JDK8之后,如果哈希表单向链表中元素超过8个,单向链表这种数据结构会变成红黑树数据结构。当红黑树上的节点数量小于6时,会重新把红黑树变成单向链表数据结构。这种方式也是为了提高检索效率,因为二叉树的检素会再次缩小扫描范围。扩容之后的容量是原来的2倍。
6、代码示例:
import java.util.Objects;
public class Demo{
private String name;
public Demo() {
}
public Demo(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Demo demo = (Demo) o;
return Objects.equals(name, demo.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
import java.util.HashSet;
import java.util.Set;
public class DemoTest{
public static void main(String[] args) {
Demo d1 = new Demo("zhangsan");
Demo d2 = new Demo("zhangsan");
/*重写hashCode()前d1、d2的hashCode不同,
重写后相同。*/
System.out.println(d1.hashCode());
System.out.println(d2.hashCode());
Set<Demo> demos = new HashSet<>();
demos.add(d1);
demos.add(d2);
/*重写hashCode()前size为2,重写后为1*/
System.out.println(demos.size());
}
}
- HashMap和Hashtable的区别
1、HashMap集合允许key为null值,但只能有一个。key重复的话value就会被覆盖。
2、Hashtable的key和value都是不能为null的。HashMap集合的key和value都是可以为null的。
3、Hashtable方法都带有synchronized,是线程安全的。因为线程安全有其它的方案,这个Hashtable对线程的处理导致效率较低,所以就使用较少了。Hashtable和HashMap一样,底层都是哈希表数据结构。Hashtable的初化容量是11,默认加载因子是:0.75。Hashtable的扩容是:原容量乘2再加1。
Hashtable和Properties属性类对象
- Properties概述
1、Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。Properties被称为属性类对象。
2、Properties是线程安全的。
3、Properties的两个方法:一个存,一个取。
- 代码示例:
import java.util.Properties;
public class Demo{
public static void main(String[] args) {
//创建一个Properties对象
Properties pro = new Properties();
//需要掌握Properties的两个方法:一个存,一个取。
pro.setProperty("username","root");
pro.setProperty("password","123");
//通过key获取value
System.out.println(pro.getProperty("username"));
System.out.println(pro.getProperty("password"));
}
}
输出:
SortedMap和TreeMap
- 概述
1、TreeSet集合底层实际上是一个TreeMap
2、TreeMap集合底层是一个二叉树。
3、放到TreeSet集合中的元素,等同于放到TreeMap集合的key部分了。
4、TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动。称为:可排序集合。(对String也是可排序的)
代码示例:
import java.util.TreeSet;
public class Demo{
public static void main(String[] args) {
TreeSet<String> ts = new TreeSet<>();
ts.add("zhangsan");
ts.add("lisi");
ts.add("wangwu");
for (String s:ts
) {
//按照字典顺序,升序
System.out.println(s);
}
TreeSet<Integer> ts2 = new TreeSet<>();
ts2.add(30);
ts2.add(35);
ts2.add(11);
ts2.add(24);
for (Integer i:ts2
) {
System.out.println(i);
}
}
}
输出:
- TreeSet的自定义类型排序
1、对于自定义类型来说,没有指定对象之间的比较规则的话,是不可以自动排序的。会出现以下异常:
java.lang.ClassCastException
- 实现Comparable接口
1、代码示例:
import java.util.TreeSet;
public class Demo{
public static void main(String[] args) {
TreeSet<Person> ts = new TreeSet<>();
ts.add(new Person(11));
ts.add(new Person(24));
ts.add(new Person(35));
for (Person i : ts) {
System.out.println(i.toString());
}
}
}
class Person implements Comparable<Person>{
int age;
public Person(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
@Override
public int compareTo(Person o) {
int age1 = this.age;
int age2 = o.age;
/*if (age1==age2){
return 0;
}else if (age1>age2){
return 1;
}else {
return -1;
}*/
return age1-age2;
}
}
输出:
2、对于return age1-age2;
的解释:
compareTo方法的返回值很重要。返回0表示相同, value会覆盖;返回大于0,会继续在右子树上找;返回小于0,会继续在左子树上找。
(源码)
while (p != null) {
int cmp = k.compareTo(p.key);
if (cmp < 0)
p = p.left;
else if (cmp > 0)
p = p.right;
else
return p;
}
3、int和String同时存在时
public int compareTo(Vip v) {
return this.num - v.num != 0?
this.num - v.num : this.name.compareTo(v.name);
/*if (this.num == v.num){
//年龄相同时按照字母排序
return this.name.compareTo(v.name);
}else {
return this.num - v.num;
}*/
}
- 自平衡二叉树
1、遵循左小右大原则存放。
2、TreeSet集合/TreeMap集合采用的是:中序遍历方式。Iterator迭代器采用的是中序遍历方式(左根右)。
- 实现比较器接口
1、TreeSet集合中元素可排序的第二种方式:使用比较器的方式。
2、代码示例:
import java.util.Comparator;
import java.util.TreeSet;
public class Demo{
public static void main(String[] args) {
//创建TreeSet集合的时候需要使用比较器
TreeSet<User> users = new TreeSet<>(new UserComparator());
//匿名内部类的方式
/*TreeSet<User> users = new TreeSet<>(new Comparator<User>() {
@Override
public int compare(User o1, User o2) {
return o1.age - o2.age;
}
});*/
users.add(new User(11));
users.add(new User(35));
users.add(new User(24));
for (User u:users
) {
System.out.println(u);
}
}
}
class User{
int age;
public User(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"age=" + age +
'}';
}
}
class UserComparator implements Comparator<User> {
@Override
public int compare(User o1, User o2) {
//指定比较规则,按照年龄排序
return o1.age - o2.age;
}
}
3、放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
第一种:放在集合中的元素实现java.Lang.Comparable接口。
第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。
4、Comparable和Comparator怎么选择呢?
当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口。
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。Comparator接口的设计符合OCP原则。
Collections工具类
1、java.util.Collection是集合接口;
java.util.Collections是集合工具类,方便集合的操作。
2、代码示例:
import java.util.*;
public class Demo{
public static void main(String[] args) {
//ArrayList集合是线程不安全的
List<String> list = new ArrayList<>();
//变成线程安全的
Collections.synchronizedList(list);
list.add("zhangsan");
list.add("lisi");
list.add("wangwu");
//排序
Collections.sort(list);
for (String s:list
) {
System.out.println(s);
}
List<User> users = new ArrayList<>();
users.add(new User(11));
users.add(new User(24));
/*注意:List集合中元素排序,
需要保证List集合中的元素实现了Comparable接口。*/
Collections.sort(users);
for (User i:users
) {
System.out.println(i);
}
Set<String> set = new HashSet<>();
set.add("curry");
set.add("curry1");
set.add("Thompson");
List<String> list2 = new ArrayList<>(set);
Collections.sort(list2);
/*也可以用这种方式排序:
Collections.sort(list集合,new 比较器对象);*/
for (String l:list2
) {
System.out.println(l);
}
}
}
class User implements Comparable<User>{
int age;
public User(int age) {
this.age = age;
}
@Override
public String
toString() {
return "User{" +
"age=" + age +
'}';
}
@Override
public int compareTo(User u) {
return this.age - u.age;
}
}
输出: