集合
1、集合框架
1.1 概念
集合也叫容器,所谓容器就是用来装东西的,但是在我们java的世界,东西就是对象,所以java里的容器是用来装对象的。
1.2集合架构
1.3为什么使用Java集合
- 提供有用的数据结构和算法来减少编写代码所需的工作量
- Java集合提供高性能和高质量的数据结构和算法,从而提高速度和质量
- 减少学习、使用和设计新 API 所需的额外工作量
2、JavaCollection :接口
2.1、常用方法
- add()方法向集合中添加元素
- clear()方法,清空集合中所有元素
- contains()方法判断集合是否包含某个元素
- isEmpty判断集合是否为空
- remove()方法 移除集合中元素,返回boolean类型。如果集合中不包含次元素,则删除失败
- size()返回集合中元素的个数
- toArray()将集合转换成数组。
- addAll ()向一个集合中添加另一个集合
- containsAll 判断一个集合中是否包含另一个集合
- removeAll 从一个集合中移除另一个集合
public class Method_采用方法 {
public static void main(String[] args){
//创建Collection对象/实例
Collection collection = new ArrayList();
//collection是一个接口,所以这儿是父类引用指向子类对象,多态的过程,属于动态绑定。
//集合里存都是对象,但是我们为什么可以传基本数据类型,因为基本数据类型会自动装箱。
//Collection接口常用方法:
collection.add("小明");
collection.add(10);
System.out.println(collection);
//contains() 判断集合中是否包含指定元素--返回boolean类型
System.out.println(collection.contains("小六"));
//isEmpty()判断集合的元素个数是否为0
System.out.println(collection.isEmpty());
//remove() 移除集合中指定的元素,如果存在返回true、否则返回false
System.out.println(collection.remove("小米"));
//size() 获取集合元素个数
System.out.println(collection.size());
//toArray() 将集合变为数组
Object[] arr = collection.toArray();
System.out.println(java.util.Arrays.toString(arr));
//将数组转换为集合
int[] nums ={1,5,3,7,6,3,5,5,9};
List asList = java.util.Arrays.asList(nums); //存的地址??
//for(Object value:asList) System.out.print(value);
Collection coll = new ArrayList();
coll.add("collection");
coll.add("ArrayList");
//addAll() 将另一个集合中元素添加到当前集合中
coll.addAll(collection);
System.out.println(coll);
//containsAll() 判断集合中是否包含另一个集合
System.out.println(coll.containsAll(collection));
//coll1.removeAll(coll);
//System.out.println(coll1);
//retainAll 保留coll中包含的元素
coll.retainAll(collection);
System.out.println(coll);
}
}
3、 迭代器
3.1 Iterator
迭代器用于遍历列表和修改元素。迭代器接口具有以下三种方法:
- public boolean hasNext() – 如果迭代器有更多元素,此方法返回 true。
- public object next() - 它返回元素并将光标指针移动到下一个元素。
- public void remove() - 此方法删除迭代器返回的最后一个元素。
3.2 迭代器原理
迭代是重复反馈过程的活动,其目的通常是为了逼近所需目标或结果。每一次对过程的重复称为一次“迭代”,而每一次迭代得到的结果会作为下一次迭代的初始值。
public class Iterator_Create {
/**
* - public boolean hasNext() – 如果迭代器有更多元素,此方法返回 true。
* - public object next() - 它返回元素并将光标指针移动到下一个元素。
* - public void remove() - 此方法删除迭代器返回的最后一个元素。
* */
public static void main(String[] args){
/** 利用Arrays.asList([]),快速创建集合类对象实例 */
Collection list = Arrays.asList(1,1,5,9,4,3,3,1);
System.out.println(list);
Iterator iterator = list.iterator();
while(iterator.hasNext()){
System.out.print(iterator.next()+" ");
}
}
}
3.3 迭代器使用常见问题
- 1、迭代器迭代完成之后,迭代器的位置在最后一位。 所以迭代器只能迭代一次
- 2、迭代器在迭代的时候,不要调用多次next方法,可能会出错NoSuchElementException
- 3、在迭代器迭代的时候,不能向集合中添加或者删除元素ConcurrentModificationException
public class Iterator_Problem{
public static void main(String[] args) {
//使用迭代器需要注意的问题
//1、迭代器只能使用一次
// Collection coll = new ArrayList();
// coll.add("jack");
// coll.add("rose");
// coll.add(10);
// coll.add(20.2);
// Iterator it = coll.iterator();
// while(it.hasNext()){
// System.out.println(it.next());
// }
// while(it.hasNext()){
// System.out.println(it.next());
// }
//2、迭代器使用的时候不能多次使用next方法
// Collection coll = new ArrayList();
// coll.add("jack");
// coll.add("rose");
// coll.add(10);
// coll.add(20.2);
// coll.add(100);
// Iterator it = coll.iterator();
// while(it.hasNext()) {
// Object obj = it.next();
// System.out.println(it.next());
// }
//3、迭代器在迭代的时候不要添加或者删除元素
Collection coll = new ArrayList();
coll.add("jack");
coll.add("rose");
coll.add(10);
coll.add(20.2);
coll.add(100);
Iterator it = coll.iterator();
while(it.hasNext()) {
coll.remove(10);
//coll.add("zhaosi");
System.out.println(it.next());
}
//--------------------------------------------------------
//collection是一个接口,所以这儿是父类引用指向子类对象,多态的过程,属于动态绑定。
Collection collection = new ArrayList();
//使用引用collection往我们的即合理添加元数
collection.add(1);//集合里存都是对象,但是我们为什么可以传基本数据类型,因为基本数据类型会自动装箱。
//使用contains方法检测对象是否在集合内
System.out.println(collection.contains(1));
//删除一个元数
collection.remove(1);
//删除之后再次检查是否包含
System.out.println(collection.contains(1));
collection.add("str");
collection.add(1);
collection.add(new String("a"));
//判断集合是否是空
System.out.println(collection.isEmpty());
//输出集合里的元素
//System.out.println(collection.toString());
//获取一个迭代器 通过集合引用.的形式获取迭代器对象。
Iterator iterator = collection.iterator();
//通过迭代器判断当前指向的位置是否有值
while (iterator.hasNext()) {
//当我们调用完next()方法后,引用会移动到指向下一个元数的位置
System.out.println(iterator.next());
//collection.add(3);
}
System.out.println("--------------------------");
//该while循环证明获取的iterator迭代器对象只能用于循环一次。
/*while (iterator.hasNext()) {
System.out.println(iterator.next());
}*/
//增强for循环,也是集合类迭代器的一个语法糖。但是,如果能用迭代器的时候尽量不要使用增强的for循环
/*for (Object o : collection) {
System.out.println(o);
}*/
}
}
4、泛型基本使用
泛型:参数化类型 JDK1.5之后
- 用途:
- 泛型擦除: JDK1.7之后
- 泛型需要注意的问题:
5、List接口
List是可能包含重复元素的有序集合。它是一个扩展 Collection 接口的接口。List进一步分为以下几类:
- ArrayList
- LinkedList
- Vectors
5.1、ArrayList类
ArrayList 是 List 接口的实现,可以在List中动态添加或删除元素。此外,如果添加的元素超过初始大小,则ArrayList的大小会动态增加。
ArrayList注意点:默认构造参数不会初始化容器大小,但是可以调用带参的构造方法给他一个初始容量大小关于arraylist扩容:扩容机制扩容后的大小=老数组大小+(老数组大小/2)调用默认构造方法创建arrylist时不会初始化容器大小,需要等到我们真正在添加值的时候才会给容器分配大小,默认分配为10
常用方法:
- add(int index, E element)
- remove(int index)
- set(int index, E element)
- get(int index)
- subList(int beginIndex,int endIndex)
- list.listIterator();
public class List接口 {
/**
* - add(int index, E element)
* - remove(int index)
* - (last)index(E element)
* - set(int index, E element)
* - get(int index)
* - subList(int beginIndex,int endIndex)
* - list.listIterator();
* */
public static void main(String[] args){
//List<Integer> listTest = java.util.Arrays.asList(1,5,5,1,3,6,7,2);
//List<String> list = new ArrayList<>();
List<Object> list2 = java.util.Arrays.asList("jack","rose","cxk","尼古拉斯");
System.out.println(list2);
/**
* 在从 Arrays.asList()转换过来的List的不支持add()和remove()方法,
* 这是由于从 Arrays.asList() 返回的是返回 java.util.ArraysArrayList,
* 而不是ArrayList。ArraysArrayList和ArrayList都是继承AbstractList,
* add() 和 remove() 等方法在AbstractList中默认 throw UnsupportedOperationException而不做任何操作。
* */
List<Object> list = new ArrayList();
list.add("jack");
list.add("rose");
list.add("cxk");
list.add("尼古拉斯");
list.add(new People("小刘",12));
list.add(new People("苏苏",22));
System.out.println(list);
//在集合指定下标位置上添加元素 add(index, element)
list.add(0,"插队");
list.add(0,"Collection");
System.out.println(list);
//返回元素在集合中的下标,如果不存在返回-1
System.out.println(list.indexOf("小刘"));
//返回元素在集合中的下标(找到集合中最后一个相同元素),如果不存在返回-1
System.out.println(list.lastIndexOf("jack"));
//移除集合中指定下标位置上的元素 下标范围:0 ~ size-1
list.remove(1);
System.out.println(list);
//修改集合中指定下标位置上的元素 下标范围:0 ~ size-1
list.set(4,"List");
System.out.println(list);
//截取集合中指定下标开始到结束位置上的元素
List<Object> newList = list.subList(2, list.size());
System.out.println(newList.size());
System.out.println(newList);
//获取指定下标位置上的元素
System.out.println(list.get(0));
}
}
class People extends Object{
String name;
int age;
public People(){}
public People(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "People{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
List的遍历
public class List_3种遍历 {
/**
* List集合遍历的三种方式:
* 1、迭代器
* 2、for循环
* 3、foreach循环(增强for循环) 可以遍历数组和集合:
* for(元素的类型 变量 : 数组|集合){ //变量就表示遍历出来的元素}
*/
public static void main(String[] args) {
List<Object> list = java.util.Arrays.asList("cxk","wyf","lyf"," ","??");
//迭代器遍历:
Iterator<Object> iterator = list.iterator();
while(iterator.hasNext()){
System.out.print(" ["+iterator.next()+"]");
}
System.out.println();
//for循环遍历:
for(int i=0;i<list.size();i++){
System.out.print(" ["+list.get(i)+"]");
}
System.out.println();
//foreach循环遍历
for(Object obj : list){
System.out.print(" ["+obj+"]");
}
}
}
5.1.1 实现原理
ArrayList其底层实现使用数组
5.2、LinkedList类
LinkedList是包含项目的链接序列。每个链接都包含与另一个链接的连接。
5.2.1 常用方法
常用的方法与ArrayList一致。自己独有一些向首尾添加移除等方法(可以模拟对列、堆栈等数据结构)
public class LinkedList_Method {
public static void main(String[] args) {
//创建LinkedList对象
LinkedList linkedList = new LinkedList();
linkedList.add("jack");
linkedList.add("rose");
linkedList.add("cxk");
linkedList.add("李雷");
linkedList.add(1, "马冬梅");
System.out.println(linkedList);
System.out.println(linkedList.getFirst());
System.out.println(linkedList.getLast());
System.out.println(linkedList.get(3));
System.out.println("================模拟栈结构==================");
LinkedList<String> list1 = new LinkedList(); //后进先出
list1.push("aa");
list1.push("bb");
System.out.println(list1.pop());
System.out.println(list1.pop());
System.out.println("================模拟对列结构==================");
LinkedList<String> list2 = new LinkedList<String>(); //先进先出
//向对列的尾部添加元素
list2.offer("哈哈");
list2.offer("呵呵");
list2.offer("嘻嘻");
list2.offer("hiahia");
//获取并移除对列的头部的元素
System.out.println(list2.poll());
System.out.println(list2.poll());
System.out.println(list2.poll());
System.out.println(list2.poll());
//获取但不移除对列的头部的元素
//System.out.println(list2.element());
//获取但不移除对列的头部的元素
//System.out.println(list2.peek());
System.out.println(list2);
}
}
5.2.2 LinkedList实现原理
Java LinkedList 类使用两种类型的链表来存储元素:
- 单链表
- 双向链表
单链表:在单链表中,此列表中的每个节点都存储节点的数据以及指向列表中下一个节点的指针或引用。
双链表:在双链表中,它有两个引用,一个指向下一个节点,另一个指向前一个节点。
5.3、Vector类
5.3.1 常用方法
与ArrayList的方法基本一致
Java
public class VectorDemo {
public static void main(String[] args) {
Vector<String> vector = new Vector<String>();
vector.add("aa");
System.out.println(vector);
/**
* ArrayList、LinkedList、Vector的区别
*
* ArrayList和Vector底层使用数组实现(增删慢、查询快)
* ArrayList是当添加元素的时候,才会扩容数组,默认长度为10
* Vector是当创建对象的是,就创建长度为10的数组
* ArrayList线程不安全,效率高
* Vector是线程安全的,效率低
*
* LinkedList底层使用双向链表实现(增删快、查询慢)
*/
}
}
5.3.2 实现原理
与ArrayList的底层一致,使用数组实现
5.3.3 常见面试问题
ArrayList、LinkedList、Vector的区别**😗
- ArrayList和Vector底层使用数组实现(增删慢、查询快)
- LinkedList底层使用双向链表实现(增删快、查询慢)
6、Map集合
6.1 Map集合特点
- Map集合是双列集合,由key和value组成。称之为键值对
- 键的特点:无序,无下标,不重复。
- 值的特点:无序,无下标,可重复
6.2 Map集合体系结构
6.3 HashMap
6.3.1 HashMap基本使用
常用方法
- put(K key, V value)
- get(Object key)
- Set keySet()
- Collection values()
- Set<Map.Entry<K,V>>entrySet()
- boolean containsKey(Object key)
- boolean containsValue(Object value)
- V remove(Object key)
- int size()
public class HashMap_Method {
public static void main(String[] args) {
//创建HashMap
HashMap<Object, Object> hashMap = new HashMap<>(12);
//向map集合中添加元素
hashMap.put("usa", "漂亮国");
hashMap.put("jp", "日本");
hashMap.put("en", "英国");
hashMap.put("ca", "加拿大");
hashMap.put("cn", "中华人民共和国");
hashMap.put("cn", "中国");
hashMap.put("china", "中国");
System.out.println(hashMap);
//从集合中获取元素的值。 根据key获取对应value
System.out.println(hashMap.get("cn"));
//清空map集合中的元素
//hashMap.clear();
//System.out.println(hashMap);
//判断是否包含指定的key
System.out.println(hashMap.containsKey("jp"));
//判断是否包含指定的value
System.out.println(hashMap.containsValue("中国"));
//判断集合中的元素长度是否为0
System.out.println(hashMap.isEmpty());
//根据key移除map中的元素
hashMap.remove("jp");
System.out.println(hashMap);
//返回map集合中元素的个数
System.out.println(hashMap.size());
}
}
public class HashMap_遍历 {
public static void main(String[] args) {
Map map = new HashMap();
map.put("usa", "漂亮国");
map.put("jp", "日本");
map.put("en", "英国");
map.put("ca", "加拿大");
map.put("cn", "中华人民共和国");
map.put("cn", "中国");
map.put("china", "中国");
System.out.println(map);
System.out.println("========= Set keySet()方法===========");
//返回map集合中所有的key ----keySet()方法
Set<String> keySet = map.keySet();
for(String key : keySet){
System.out.print(key+" ");
}
System.out.println();
System.out.println("============Collection values()方法===========");
//返回map集合中所有的value
Collection<String> coll = map.values();
System.out.println(coll);
System.out.println("===========Set entrySet()方法===========");
//返回map集合中所有的key和value (Entry)
Set<Map.Entry<String,String>> entrySet = map.entrySet();
//遍历只能采用这种方式之一
for(Map.Entry<String,String> entry: entrySet){
System.out.print(entry.getKey()+"+");
System.out.print(entry.getValue()+" ");
}
}
}
关于hash函数
CRC-64 (1975) → Used in networking for errordetection.
MurmurHash (2008) → Designed to a fast, generalpurpose hash function.
Google CityHash (2011) → Designed to be faster forshort keys (<64 bytes).
Facebook XXHash (2012) → From the creator of zstdcompression.
Google FarmHash (2014)→ Newer version of CityHashwith better collision rates.
LINEAR PROBE HASHING : 线性探测hash
动态Hash
Dynamic hash tables resize themselves on demand.
→ Chained Hashing
→ Extendible Hashing
→ Linear Hashing
6.3.2 HashMap实际应用
- 可以使用Map < String , Obeject > 表示一个实体类
- 可以使List <Map<String,Object>> 表示一个实体类集合
public class LIst_Map {
/**
* 实体类表示:-------------------- Map<Object,Object>
* public class Product{
* private int id;
* private String name;
* private double price;
* .....
* }
*
* 实体类集合:
* 多件商品:List<Product>
* 多件商品:List<Map<String,Object>>
*
* */
public static void main(String[] args) {
//创建Map:表示一件商品
Map<String,Object> map = new HashMap<>();
map.put("id", 1);
map.put("name", "神州");
map.put("price",3000.5);
Map<String, Object> map1 = new HashMap<String, Object>();
map1.put("id", 2);
map1.put("name", "联想");
map1.put("price",3002.5);
//使用List<Map>表示多件商品
ArrayList<Map<String,Object>> list = new ArrayList<>();
list.add(map);
list.add(map1);
System.out.println(list);
for(Map<String,Object> mapTemp : list){
System.out.println(mapTemp);
}
}
}
案例:使用集合保存省市数据
public class List_Map02 {
//使用集合保存省市数据
public static void main(String[] args) {
Map<String, List<String>> map = new HashMap<String, List<String>>();
List<String> city1 = new ArrayList<String>();
city1.add("武汉");
city1.add("监利");
city1.add("黄冈");
city1.add("荆州");
map.put("湖北省", city1);
List<String> city2 = new ArrayList<String>();
city2.add("长沙");
city2.add("岳阳");
city2.add("常德");
city2.add("湘潭");
map.put("湖南省", city2);
//System.out.println(map.get("湖北省"));
/**遍历方式:*/
for(Map.Entry<String, List<String>> entry : map.entrySet()){
System.out.print(entry.getKey()+"->");
System.out.println(entry.getValue()+" ");
}
}
}
/**
* 湖北省->[武汉, 监利, 黄冈, 荆州]
* 湖南省->[长沙, 岳阳, 常德, 湘潭]
* */
6.4 HashMap源码解析
6.4.1 put的过程原码
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
//tab表示存放Node节点的数据 p表示当前节点 n表示长度 i表示节点在数组中的下标
Node<K,V>[] tab; Node<K,V> p; int n, i;
//判断数组如果为空或者数组长度为0,那么就对数组进行扩容,数组默认初始大小为16
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//将数组的长度-1与hash值进行与运算(计算的结果一定是0~数组长度-1)得到元素应该存放的下标
//如果当前下标位置为空,那么直接将Node节点存放在当前位置
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//如果当前位置不为空(分为三种情况)
else {
Node<K,V> e; K k;
//情况1:要添加的元素与当前位置上的元素相同(hash(hashCode)、key(equals)一致),则直接替换
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
//情况2:如果要添加的元素是红黑树节点,那么将其添加到红黑树上
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
//情况3:如果要添加的元素是链表,则需要遍历
else {
for (int binCount = 0; ; ++binCount) {
//将当前元素的下一个节点赋给e
//如果e为空,则创建新的元素节点放在当前位置的下一个元素上,并退出循环
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
//如果链表的元素个数大于8个(且当数组中的元素个数大于64),则将其转换成红黑树
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
//要添加的元素与当前位置上的元素相同(hash(hashCode)、key(equals)一致),则直接退出循环
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
//如果返回的e不为null
if (e != null) { // existing mapping for key
//将e的值赋给oldValue
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
//返回以前的值(当添加的元素已经存在返回的是以前的值)
return oldValue;
}
}
++modCount;
//如果数组的元素个数大于阈值则进行扩容
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
6.4.2 resize过程原码
final Node<K,V>[] resize() {
//oldTab 表示原来数组(如果是第二次扩容:长度为16的那个)
Node<K,V>[] oldTab = table;
//oldCap 表示原数组的容量(长度)
int oldCap = (oldTab == null) ? 0 : oldTab.length;
//oldThr 表示数组原来的阈值 12
int oldThr = threshold;
//newCap 新数组的容量 newThr 新数组的阈值
int newCap, newThr = 0;
if (oldCap > 0) {
if (oldCap >= MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return oldTab;
}
//新数组的容量扩大一半 newCap 32
else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
oldCap >= DEFAULT_INITIAL_CAPACITY)
//新阈值扩大老阈值的一半 newThr 24
newThr = oldThr << 1; // double threshold
}
else if (oldThr > 0) // initial capacity was placed in threshold
newCap = oldThr;
else { // zero initial threshold signifies using defaults
newCap = DEFAULT_INITIAL_CAPACITY;
newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
}
if (newThr == 0) {
float ft = (float)newCap * loadFactor;
newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
(int)ft : Integer.MAX_VALUE);
}
//threshold 24
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//创建一个长度为32的数组
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//table指向新数组
table = newTab;
if (oldTab != null) {
//将原数组中的元素拷贝到新数组中
for (int j = 0; j < oldCap; ++j) {
Node<K,V> e;
//如果当前位置元素不为空
if ((e = oldTab[j]) != null) {
oldTab[j] = null;
//情况1:当前位置上的下一个元素为空,则直接将这个元素拷贝到新数组中
if (e.next == null)
newTab[e.hash & (newCap - 1)] = e;
//情况2:当前位置上的元素红黑树类型,则需要进行切割
else if (e instanceof TreeNode)
((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
//情况3:当前位置上的元素链表类型,则需要进行分散拷贝
else { // preserve order
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
}
}
}
return newTab;
}
6.4.3 get的过程原码
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
//当前first与要找到的hash和key都相等直接返回当前这个first元素
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//如果当前first不为空(有两种情况)
if ((e = first.next) != null) {
//当前位置是一个红黑树
if (first instanceof TreeNode)
//根据hash、key从红黑树上找到对应的元素
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
//当前位置是一个链表
do {
//循环进行比较直到找到向的hash和key的元素,并返回
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
//如果数组的为空、数组的长度为0、当前下标位置上的值为null,这三种情况都返回null
return null;
}
6.5 HashTable
Hashtable常用方法与HashMap一致
HashMap与Hashtable区别:
- 1、Hashtable是线程安全的,HashMap是线程不安全的
- 2、Hashtable中不允许存储null作为key和value,而HashMap可以
在实际开发中一般都是用HashMap。考虑线程安全使用ConCurrentHashMap
public static void main(String[] args) {
Hashtable<Integer, String> hashtable = new Hashtable<>();
hashtable.put(2, "b");
hashtable.put(1, "a");
hashtable.put(5, "l");
hashtable.put(9, "h");
hashtable.put(20, "hz");
//hashtable.put(10, null);//hashtable不允许插入空键或者是值,会报NullPointerException
hashtable.remove(10);
System.out.println(hashtable);
//遍历
for (Map.Entry<Integer, String> entry : hashtable.entrySet()) {
System.out.print("<" + entry.getKey() + ", ");
System.out.print(entry.getValue() + "> ");
}
}
6.6 TreeMap
/**TreeMap按键的升序对条目进行排序。*/
public static void main(String[] args) {
Map<Integer, String> map = new TreeMap();
map.put(2, "b");
map.put(1, "a");
map.put(5, "l");
map.put(9, "h");
map.put(20, "hz");
System.out.println(map);
//遍历1
System.out.println("for-each遍历:");
for (Map.Entry<Integer, String> entry : map.entrySet()) {
System.out.print("<" + entry.getKey() + ", ");
System.out.print(entry.getValue() + "> ");
}
System.out.println("\n通过迭代器遍历:");
//遍历2
Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
Iterator<Map.Entry<Integer, String>> iterator = entrySet.iterator();
while (iterator.hasNext()) {
Map.Entry<Integer, String> next = iterator.next();
System.out.print("<" + next.getKey() + ", ");
System.out.print(next.getValue() + "> ");
}
}
6.7 LinkedHashMap
/**
* LinkedHashMap,保证插入的顺序
*/
public class TestLinkedHashMap {
public static void main(String[] args) {
LinkedHashMap<Integer, String> lhm = new LinkedHashMap<>();
lhm.put(2, "b");
lhm.put(1, "a");
lhm.put(5, "l");
lhm.put(9, "h");
lhm.put(20, "hz");
System.out.println(lhm);
//遍历
for (Map.Entry<Integer, String> entry : lhm.entrySet()) {
System.out.print("<" + entry.getKey() + ", ");
System.out.print(entry.getValue() + "> ");
}
}
}
HashMap没有维持任何顺序。
TreeMap按键的升序对条目进行排序。
LinkedHashMap保持插入顺序。
7、Set接口
7.1、HashSet类
7.1.1 HashSet基本使用
常用方法与Collection接口中定义的方法一致
特点:
- 无序 —>原因:因为使用了hashmap作为底层存储,hashmap的hash函数算出来的值不能保证有序
- 无下标–>使用hash算法算出存储位置,而不是使用下标
- 不可重复–>因为set的add方法内部调用的是Hashmap的put方法,而我们添加的值是作为
HashMap的key来存储,所以根据hashmap的hash算法,如果同一个key,那么hash函数算出来的值一定相等,只要相等,就不会重复存储,所以就实现了去重。
HashSet<String> set = new HashSet<String>();
set.add("jack");
set.add("rose");
set.add("cxk");
set.add("jack");
System.out.println(set);
7.1.2 HashSet 去重原理
HashSet底层去重:
/**
* Set集合的使用,hashset去重原理:因为set的add方法内部调用的是Hashmap的put方法,而我们添加的值是作为
* HashMap的key来存储,所以根据hashmap的hash算法,如果同一个key,那么hash函数算出来的值一定相等,只要相等,
* 就不会重复存储,所以就实现了去重。
*/
public class TestHashset {
public static void main(String[] args) {
//要使用一个集合,第一步就得先把集合对象创建出来。
//因为Set是一个接口,具体的实现类是HashSet,所以这儿是一个多态,父类引用指向子类对象
Set<Order> set = new HashSet();
//往集合里添加元素,因为我们结合在创建时限制了只能存储Order类型的值,所以需要创建Order对象
//使用无参构造方法创建一个类
Order order1 = new Order();
order1.setName("cloth");
order1.setId(1);
set.add(order1);
//使用带参构造方法创建对象
set.add(new Order(2, "shoes", 100, 399.0f ));
set.add(new Order(2, "shoes", 100, 399.0f ));
//输出集合
System.out.println(set);
//使用迭代器遍历集合
Iterator<Order> iterator = set.iterator();
while (iterator.hasNext()) {
Order order = iterator.next();//获取到的是集合里的单个对象
System.out.print(order.getName() + " ");
}
System.out.println();
//使用for each遍历
for (Order o : set) {
System.out.print(o.getName() + " ");
}
System.out.println();
//如果需要判断自定义对象是否包含在集合内,我们需要重写equals方法和hashcode方法
System.out.println(set.contains(new Order(2, "shoes", 100, 399.0f )));
System.out.println("\n----------------------------------------");
//set集合里添加基本数据类型
HashSet<Integer> setInt = new HashSet<>();
setInt.add(100);
setInt.add(200);
setInt.add(300);
setInt.add(400);
setInt.add(500);
System.out.println(setInt);
//删除元素
setInt.remove(300);
setInt.add(100);
System.out.println(setInt);
//判断集合是否包含某个元素
System.out.println(setInt.contains(500));
}
}
重写hashCode和equals方法
public class hashCode_equals_重写 {
public class Order {
private Integer id;
private String name;
private Integer number;
private Float price;
public Order() {}
public Order(Integer id, String name, Integer number, Float price) {
this.id = id;
this.name = name;
this.number = number;
this.price = price;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getNumber() {
return number;
}
public void setNumber(Integer number) {
this.number = number;
}
public Float getPrice() {
return price;
}
public void setPrice(Float price) {
this.price = price;
}
//重写toString
@Override
public String toString() {
return "{id : " + id + " name:" + name + " number:" + number + " price:" + price + "}";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Order order = (Order) o;
return Objects.equals(id, order.id) &&
Objects.equals(name, order.name) &&
Objects.equals(number, order.number) &&
Objects.equals(price, order.price);
}
@Override
public int hashCode() {
return Objects.hash(id, name, number, price);
}
}
}
7.2、LinkedHashSet类
LinkedHashSet
特点:
- 1、有序 保证插入顺序
- 2、无下标
- 3、不可重复
与父类的方法一致,去重的原理,也与父类一致
public class LinkedHashSet类 {
public static void main(String[] args) {
//LinkedHashSet 有序(链表维护顺序) 不能重复
LinkedHashSet<String> set = new LinkedHashSet<>();
set.add("jack");
set.add("大娃");
set.add("二娃");
set.add("rose");
set.add("爷爷");
set.add("爷爷");
for (String s : set) {
System.out.println(s);
}
//1、底层实现 (LinkedHashMap)
//2、去重原理 (与hashSet一致)
}
}
7.3、TreeSet类
TreeSet特点:
- 1、TreeSet类似于HashSet,不同之处在于它按升序对元素进行排序,而HashSet不维护任何顺序。
- 2、无下标
- 3、不可重复
7.3.1 常用方法
与HashSet类的方法一致
特点:
- 使用TreeSet集合存储对象的时候,对象必须要实现Comparable接口
7.3.2 实现原理
TreeSet在存储元素的时候,会调用compareTo方法。两个作用:
- 1、排序: 返回值大于0升序,返回值小于0降序
- 2、去重(返回值为0) TreeSet认为返回0,两个对象就是相同的对象
public class TreeSet类 {
public static void main(String[] args) {
//TreeSet的去重原理
TreeSet<Person> set = new TreeSet<Person>();
set.add(new Person("admin","123"));
set.add(new Person("aa","bb"));
set.add(new Person("jack","123"));
set.add(new Person("cxk","abc"));
set.add(new Person("rose123","123"));
set.add(new Person("admin","123"));
//如果两个对象的用户名和密码都相等,则认为就是相同的对象,且按照名字长度升序存放
for (Person person : set) {
System.out.println(person);
}
/**
* TreeSet集合在保存对象元素的时候,对象必须要实现Comparable接口重写compareTo方法。
* TreeSet的去重原理为:如果compareTo方法的返回值为0,则认为是相同的对象
* 如果compareTo方法的返回大于0则是升序排序,小于0则是降序排序
*/
}
}
class Person implements Comparable<Person>{
String username;
String password;
public Person(String username, String password) {
super();
this.username = username;
this.password = password;
}
public Person() {
}
@Override
public String toString() {
return "User [username=" + username + ", password=" + password + "]";
}
@Override
public int compareTo(Person o) {
if(!this.username.equals(o.username)) {
return this.username.length() - o.username.length();
}else {
if(this.password.equals(o.password)) {
return 0;
}else {
return this.username.length() - o.username.length();
}
}
}
}
8、Comparable
public class Comparable_compareTo_排序 {
/**
* Comparable是一个接口,我们要让集合给我们的自定义类按照某种属性排序,
* 就要实现该接口里的compareTo方法
*/
public static void main(String[] args) {
ArrayList<Student> list = new ArrayList();
list.add(new Student("wangyi"));
list.add(new Student("zhangsan"));
list.add(new Student("lisi"));
//使用集合的排序工具类lections给我们的集合排序
Collections.sort(list);
System.out.println(list);
}
}
class Student implements Comparable<Student>{
private String username;
public Student(String username) {
this.username = username;
}
@Override
public int compareTo(Student o) {
System.out.println(this.username.compareTo(o.username));
return -this.username.compareTo(o.username);
}
@Override
public String toString() {
return "Student{" +
"username='" + username + '\'' +
'}';
}
}
9、Collections工具类
集合:工具类(Collections)
- Collections.reverse(List<?>list)
- Collections.shuffle(List<?>list)
- Collections.sort(List<?> list)
public class Collections_工具类 {
/**
* - Collections.reverse(List<?>list)
* - Collections.shuffle(List<?>list)
* - Collections.sort(List<?> list)
* */
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
list.add("jack");
list.add("大娃");
list.add("二娃");
list.add("rose");
list.add("妖怪");
System.out.println(list);
//按照字典顺序
Collections.sort(list);
System.out.println(list);
//将集合元素进行翻转
Collections.reverse(list);
System.out.println(list);
//将集合中的元素进行随机打乱
Collections.shuffle(list);
System.out.println(list);
//Arrays数组工具类 Collections集合工具类
}
}
10、泛型使用
泛型类类名
泛型接口接口名
泛型方法 public T 方法名*(T t,…){ }
/** 泛型类 类名<T> */
public class Class_泛型<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
/** 泛型方法 public <T> T 方法名(T t,...){} */
public <B> void m1(B t){ }
}
/**泛型接口 接口名<T>*/
interface MyInterface<T> {
public void show(T t);
}
10.2 泛型上下边界
泛型上下边界
public class Demo01 {
//? 表示不确定类型 此时的?表示Object
public static void test01(List<?> list) {}
public static void test02(List<? extends Number> list) { }
public static void test03(List<? super Number> list) { }
public static <T> void test04(List<? extends Comparable<T>> list) {}
public static void main(String[] args) {
List<String> list1 = new ArrayList<String>();
List<Integer> list2 = new ArrayList<Integer>();
List<Number> list3 = new ArrayList<Number>();
List<Object> list4 = new ArrayList<Object>();
test01(list1);
test01(list2);
test01(list3);
test01(list4);
//test02(list1); //错误,方法定义泛型的上边界,泛型只能是Number及其Number子类
test02(list2);
test02(list3);
//test02(list4); //错误,方法定义泛型的上边界,泛型只能是Number及其Number子类
//test03(list1); //错误,方法定义泛型的下边界,泛型只能是Number及其Number父类
//test03(list2);
test03(list3);
test03(list4);
test04(list1);
test04(list2);
//test04(list3); //错误,方法定义泛型的上边界,泛型必须实现Comparable接口
//test04(list4); //错误,方法定义泛型的上边界,泛型必须实现Comparable接口
}
}