您的位置:首页 > 编程语言 > Java开发

Javase进阶--集合--动力节点(该文章是我在B站上所看视频写的笔记)

2020-06-05 07:13 162 查看

集合

什么是集合?有什么用?

数组其实就是一个集合。集合实际上就是一个容器。可以来容纳其它类型的数据。

集合为什么说在开发中使用较多?

集合是一个容器,是一个载体,可以一次容纳多个对象。
在实际开发中,假设连接数据库,数据库当中有10条记录,
那么假设把这10条记录查询出来,在java程序中会将10条数据封装成10个java对象,
然后将10个java对象放到某一个集合当中,将集合传到前端,
然后遍历集合,将一个数据一个数据展现出来。

集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,

集合当中存储的都是java对象的内存地址。(或者说集合中存储的是引用。)
list.add(100); //自动装箱Integer
注意:
集合在java中本身是一个容器,是一个对象。
集合中任何时候存储的都是“引用”。

在java中每一个不同的集合,底层会对应不同的数据结构。

往不同的集合中存储元素,等于将数据放到了不同的数据结构当中。
什么是数据结构?数据存储的结构就是数据结构。
不同的数据结构,数据存储方式不同。

例如:

数组、二叉树、链表、哈希表...
以上这些都是常见的数据结构。
你往集合c1中放数据,可能是放到数组上了。
你往集合c2中放数据,可能是放到二叉树上了。
.....
你使用不同的集合等同于使用了不同的数据结构。
new ArrayList(); 创建一个集合,底层是数组。
new LinkedList(); 创建一个集合对象,底层是链表。
new TreeSet(); 创建一个集合对象,底层是二叉树。
.....

所有的集合类和集合接口都在java.util包下。

java.util.*;

在java中集合分为两大类:

一类是单个方式存储元素:

单个方式存储元素,这一类集合中超级父接口:java.util.Collection;

Collection中能存放什么元素?

没有使用“泛型”之前,Collection中可以存储Object的所有子类型。
使用了“泛型”之后,Collection中只能存储某个具体的类型。
集合后期我们会学习“泛型”语法。目前先不用管。Collection中什么都能存,
只要是Object的子类型就行。(集合中不能直接存储基本数据类型,也不能存java对象,只是存储java对象的内存地址。)

Collection中的常用方法

boolean add(Object e) 向集合中添加元素
int size()  获取集合中元素的个数
void clear() 清空集合
boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false
boolean remove(Object o) 删除集合中的某个元素。
boolean isEmpty()  判断该集合中元素的个数是否为0
Object[] toArray()  调用这个方法可以把集合转换成数组。【作为了解,使用不多。】
代码演示:(Collection常用方法)
import java.util.ArrayList;
import java.util.Collection;

public class CollectionTest01 {
public static void main(String[] args) {
// 创建一个集合对象
//Collection c = new Collection(); // 接口是抽象的,无法实例化。
// 多态
Collection c = new ArrayList();
// 测试Collection接口中的常用方法
c.add(1200); // 自动装箱(java5的新特性。),实际上是放进去了一个对象的内存地址。Integer x = new Integer(1200);
c.add(3.14); // 自动装箱
c.add(new Object());
c.add(new Student());
c.add(true); // 自动装箱

// 获取集合中元素的个数
System.out.println("集合中元素个数是:" + c.size()); // 5

// 清空集合
c.clear();
System.out.println("集合中元素个数是:" + c.size()); // 0

// 再向集合中添加元素
c.add("hello"); // "hello"对象的内存地址放到了集合当中。
c.add("world");
c.add("浩克");
c.add("绿巨人");
c.add(1);

// 判断集合中是否包含"绿巨人"
boolean flag = c.contains("绿巨人");
System.out.println(flag); // true
boolean flag2 = c.contains("绿巨人2");
System.out.println(flag2); // false
System.out.println(c.contains(1)); // true

System.out.println("集合中元素个数是:" + c.size()); // 5

// 删除集合中某个元素
c.remove(1);
System.out.println("集合中元素个数是:" + c.size()); // 4

// 判断集合是否为空(集合中是否存在元素)
System.out.println(c.isEmpty()); // false
// 清空
c.clear();
System.out.println(c.isEmpty()); // true(true表示集合中没有元素了!)

c.add("abc");
c.add("def");
c.add(100);
c.add("helloworld!");
c.add(new Student());

// 转换成数组(了解,使用不多。)
Object[] objs = c.toArray();
for(int i = 0; i < objs.length; i++){
// 遍历数组
Object o = objs[i];
System.out.println(o);
}
}
}

class Student{

}

集合遍历/迭代(重点*****)

步骤:第一步:获取集合对象的迭代器对象Iterator
第二步:通过以上获取的迭代器对象开始迭代/遍历集合。
以下两个方法是迭代器对象Iterator中的方法:
boolean hasNext()如果仍有元素可以迭代,则返回 true。
Object next() 返回迭代的下一个元素。
注意:以下讲解的遍历方式/迭代方式,是所有Collection通用的一种方式。
在Map集合中不能用。在所有的Collection以及子类中使用。
代码演示:(HashSet和ArrayList集合)
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.HashSet;
public class CollectionTest02 {
public static void main(String[] args) {
// 创建集合对象
// ArrayList集合:有序可重复
Collection c = new ArrayList();
// 添加元素
c.add("abc");
c.add("def");
c.add(100);
c.add(new Object());
c.add(100);
// 对集合Collection进行遍历/迭代
// 第一步:获取集合对象的迭代器对象Iterator
Iterator it = c.iterator();
// 第二步:通过以上获取的迭代器对象开始迭代/遍历集合。
while(it.hasNext()){
// 存进去是什么类型,取出来还是什么类型。
Object obj = it.next();
/*if(obj instanceof Integer){
System.out.println("Integer类型");
}*/
// 只不过在输出的时候会转换成字符串。因为这里println会调用toString()方法。
System.out.println(obj);
}

// HashSet集合:无序不可重复
Collection c2 = new HashSet();
// 无序:存进去和取出的顺序不一定相同。
// 不可重复:存储100,不能再存储100.
c2.add(100);
c2.add(200);
c2.add(300);
c2.add(90);
c2.add(400);
c2.add(50);
c2.add(60);
c2.add(100);
Iterator it2 = c2.iterator();
while(it2.hasNext()){
System.out.println(it2.next());
}
}
}

深入Collection集合的contains方法和equals方法:

boolean contains(Object o)
判断集合中是否包含某个对象o
如果包含返回true, 如果不包含返回false。

contains方法是用来判断集合中是否包含某个元素的方法,
那么它在底层是怎么判断集合中是否包含某个元素的呢?
调用了equals方法进行比对。
equals方法返回true,就表示包含这个元素。

结论:存放在一个集合中的类型,一定要重写equals方法。

代码演示:
import java.util.ArrayList;
import java.util.Collection;

/*
测试contains方法
测试remove方法。

*/
public class CollectionTest05 {
public static void main(String[] args) {
// 创建集合对象
Collection c = new ArrayList();
// 创建用户对象
User u1 = new User("jack");
// 加入集合
c.add(u1);

// 判断集合中是否包含u2
User u2 = new User("jack");

// 没有重写equals之前:这个结果是false
//System.out.println(c.contains(u2)); // false
// 重写equals方法之后,比较的时候会比较name。
System.out.println(c.contains(u2)); // true

c.remove(u2);
System.out.println(c.size()); // 0

/*Integer x = new Integer(10000);
c.add(x);

Integer y = new Integer(10000);
System.out.println(c.contains(y)); // true*/

// 创建集合对象
Collection cc = new ArrayList();
// 创建字符串对象
String s1 = new String("hello");
// 加进去。
cc.add(s1);

// 创建了一个新的字符串对象
String s2 = new String("hello");
// 删除s2
cc.remove(s2); // s1.equals(s2) java认为s1和s2是一样的。删除s2就是删除s1。
// 集合中元素个数是?
System.out.println(cc.size()); // 0
}
}

class User{
private String name;
public User(){}
public User(String name){
this.name = name;
}

// 重写equals方法
// 将来调用equals方法的时候,一定是调用这个重写的equals方法。
// 这个equals方法的比较原理是:只要姓名一样就表示同一个用户。
public boolean equals(Object o) {
if(o == null || !(o instanceof User)) return false;
if(o == this) return true;
User u = (User)o;
// 如果名字一样表示同一个人。(不再比较对象的内存地址了。比较内容。)
return u.name.equals(this.name);
}

}

集合元素中的remove

重点:当集合的结构发生改变时,迭代器必须重新获取,
如果还是用以前老的迭代器,会出现 异常:java.util.ConcurrentModificationException

重点:在迭代集合元素的过程中,不能调用集合对象的remove方法,删除元素:  c.remove(o); 迭代过程中不能这样。
会出现:java.util.ConcurrentModificationException

重点:在迭代元素的过程当中,一定要使用迭代器Iterator的remove方法,删除元素, 不要使用集合自带的remove方法删除元素。
代码演示
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

public class CollectionTest06 {
public static void main(String[] args) {
// 创建集合
Collection c = new ArrayList();

// 注意:此时获取的迭代器,指向的是那是集合中没有元素状态下的迭代器。
// 一定要注意:集合结构只要发生改变,迭代器必须重新获取。
// 当集合结构发生了改变,迭代器没有重新获取时,调用next()方法时:java.util.ConcurrentModificationException
Iterator it = c.iterator();

// 添加元素
c.add(1); // Integer类型
c.add(2);
c.add(3);

// 获取迭代器
//Iterator it = c.iterator();
/*while(it.hasNext()){
// 编写代码时next()方法返回值类型必须是Object。
// Integer i = it.next();
Object obj = it.next();
System.out.println(obj);
}*/

Collection c2 = new ArrayList();
c2.add("abc");
c2.add("def");
c2.add("xyz");

Iterator it2 = c2.iterator();
while(it2.hasNext()){
Object o = it2.next();
// 删除元素
// 删除元素之后,集合的结构发生了变化,应该重新去获取迭代器
// 但是,循环下一次的时候并没有重新获取迭代器,所以会出现异常:java.util.ConcurrentModificationException
// 出异常根本原因是:集合中元素删除了,但是没有更新迭代器(迭代器不知道集合变化了)
//c2.remove(o); // 直接通过集合去删除元素,没有通知迭代器。(导致迭代器的快照和原集合状态不同。)
// 使用迭代器来删除可以吗?
// 迭代器去删除时,会自动更新迭代器,并且更新集合(删除集合中的元素)。
it2.remove(); // 删除的一定是迭代器指向的当前元素。
System.out.println(o);
}

System.out.println(c2.size()); //0
}
}

ArrayList集合

1、默认初始化容量10(底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10。)
2、集合底层是一个Object[]数组。
3、构造方法:
new ArrayList();
new ArrayList(20);
4、ArrayList集合的扩容:
增长到原容量的1.5倍。
ArrayList集合底层是数组,怎么优化?
尽可能少的扩容。因为数组扩容效率比较低,建议在使用ArrayList集合
的时候预估计元素的个数,给定一个初始化容量。
5、数组优点:
检索效率比较高。(每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,
然后知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高。)
6、数组缺点:
随机增删元素效率比较低。
另外数组无法存储大数据量。(很难找到一块非常巨大的连续的内存空间。)
7、向数组末尾添加元素,效率很高,不受影响。
8、面试官经常问的一个问题?
这么多的集合中,你用哪个集合最多?
答:ArrayList集合。
因为往数组末尾添加元素,效率不受影响。
另外,我们检索/查找某个元素的操作比较多。
9、ArrayList集合是非线程安全的。(不是线程安全的集合。)
代码演示
import java.util.ArrayList;
import java.util.List;

/*

*/
public class ArrayListTest01 {
public static void main(String[] args) {

// 默认初始化容量是10
// 数组的长度是10
List list1 = new ArrayList();
// 集合的size()方法是获取当前集合中元素的个数。不是获取集合的容量。
System.out.println(list1.size()); // 0

// 指定初始化容量
// 数组的长度是20
List list2 = new ArrayList(20);
// 集合的size()方法是获取当前集合中元素的个数。不是获取集合的容量。
System.out.println(list2.size()); // 0

list1.add(1);
list1.add(2);
list1.add(3);
list1.add(4);
list1.add(5);
list1.add(6);
list1.add(7);
list1.add(8);
list1.add(9);
list1.add(10);

System.out.println(list1.size());

// 再加一个元素
list1.add(11);
System.out.println(list1.size()); // 11个元素。
/*
int newCapacity = ArraysSupport.newLength(oldCapacity,minCapacity - oldCapacity,oldCapacity >> 1);
*/
// 100 二进制转换成10进制: 00000100右移一位 00000010 (2)  【4 / 2】
// 原先是4、现在增长:2,增长之后是6,增长之后的容量是之前容量的:1.5倍。
// 6是4的1.5倍
}
}

HashSet集合转换为List集合

代码演示:
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

/*
集合ArrayList的构造方法
*/
public class ArrayListTest02 {
public static void main(String[] args) {

// 默认初始化容量10
List myList1 = new ArrayList();

// 指定初始化容量100
List myList2 = new ArrayList(100);

// 创建一个HashSet集合
Collection c = new HashSet();
// 添加元素到Set集合
c.add(100);
c.add(200);
c.add(900);
c.add(50);

// 通过这个构造方法就可以将HashSet集合转换成List集合。
List myList3 = new ArrayList(c);
for(int i = 0; i < myList3.size(); i++){
System.out.println(myList3.get(i));
}
}
}

Vactor集合

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 是集合工具类。
代码演示
import java.util.*;
public class VectorTest {
public static void main(String[] args) {
// 创建一个Vector集合
List vector = new Vector();
//Vector vector = new Vector();

// 添加元素
// 默认容量10个。
vector.add(1);
vector.add(2);
vector.add(3);
vector.add(4);
vector.add(5);
vector.add(6);
vector.add(7);
vector.add(8);
vector.add(9);
vector.add(10);

// 满了之后扩容(扩容之后的容量是20.)
vector.add(11);

Iterator it = vector.iterator();
while(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}

// 这个可能以后要使用!!!!
List myList = new ArrayList(); // 非线程安全的。

// 变成线程安全的
Collections.synchronizedList(myList); // 这里没有办法看效果,因为多线程没学,你记住先!

// myList集合就是线程安全的了。
myList.add("111");
myList.add("222");
myList.add("333");
}
}

链表:LinkedList

链表的优点:
由于链表上的元素在空间存储上内存地址不连续。
所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。
在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议
使用LinkedList。

LinkedList集合底层也是有下标的。
注意:ArrayList之所以检索效率比较高,不是单纯因为下标的原因。是因为底层数组发挥的作用。
LinkedList集合照样有下标,但是检索/查找某个元素的时候效率比较低,因为只能从头节点开始一个一个遍历。

链表的缺点:
不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头
节点开始遍历,直到找到为止。所以LinkedList集合检索/查找的效率
较低。

ArrayList:把检索发挥到极致。(末尾添加元素效率还是很高的。)
LinkedList:把随机增删发挥到极致。
加元素都是往末尾添加,所以ArrayList用的比LinkedList多。

链表没有初始化容量,最初链表是没有任何元素的,first和last都是null。
代码演示
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

/*

*/
public class LinkedListTest01 {
public static void main(String[] args) {

List list = new LinkedList();
list.add("a");
list.add("b");
list.add("c");

for(int i = 0; i <list.size(); i++){
Object obj = list.get(i);
System.out.println(obj);
}

// LinkedList集合有初始化容量吗?没有。
// 最初这个链表中没有任何元素。first和last引用都是null。
// 不管是LinkedList还是ArrayList,以后写代码时不需要关心具体是哪个集合。
// 因为我们要面向接口编程,调用的方法都是接口中的方法。
//List list2 = new ArrayList(); // 这样写表示底层你用了数组。
List list2 = new LinkedList(); // 这样写表示底层你用了双向链表。

// 以下这些方法你面向的都是接口编程。
list2.add("123");
list2.add("456");
list2.add("789");

for(int i = 0; i < list2.size(); i++){
System.out.println(list2.get(i));
}

}
}

一类是以键值对儿的方式存储元素

以键值对的方式存储元素,这一类集合中超级父接口:java.util.Map;

Map

java.util.Map接口中常用的方法:
1、Map和Collection没有继承关系。
2、Map集合以key和value的方式存储数据:键值对
key和value都是引用数据类型。
key和value都是存储对象的内存地址。
key起到主导的地位,value是key的一个附属品。
3、Map接口中常用方法:
V put(K key, V value) 向Map集合中添加键值对
V get(Object key) 通过key获取value
void clear()    清空Map集合
boolean containsKey(Object key) 判断Map中是否包含某个key
boolean containsValue(Object value) 判断Map中是否包含某个value
boolean isEmpty()   判断Map集合中元素个数是否为0
V remove(Object key) 通过key删除键值对
int size() 获取Map集合中键值对的个数。
Collection<V> values() 获取Map集合中所有的value,返回一个Collection

Set<K> keySet() 获取Map集合所有的key(所有的键是一个set集合)

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 【注意:Map集合通过entrySet()方法转换成的这个Set集合,Set集合中元素的类型是 Map.Entry<K,V>】
2=lisi     【Map.Entry和String一样,都是一种类型的名字,只不过:Map.Entry是静态内部类,是Map中的静态内部类】
3=wangwu
4=zhaoliu ---> 这个东西是个什么?Map.Entry
代码演示:
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class MapTest01 {
public static void main(String[] args) {
// 创建Map集合对象
Map<Integer, String> map = new HashMap<>();
// 向Map集合中添加键值对
map.put(1, "zhangsan"); // 1在这里进行了自动装箱。
map.put(2, "lisi");
map.put(3, "wangwu");
map.put(4, "zhaoliu");
// 通过key获取value
String value = map.get(2);
System.out.println(value);
// 获取键值对的数量
System.out.println("键值对的数量:" + map.size());
// 通过key删除key-value
map.remove(2);
System.out.println("键值对的数量:" + map.size());
// 判断是否包含某个key
// contains方法底层调用的都是equals进行比对的,所以自定义的类型需要重写equals方法。
System.out.println(map.containsKey(new Integer(4))); // true
// 判断是否包含某个value
System.out.println(map.containsValue(new String("wangwu"))); // true

// 获取所有的value
Collection<String> values = map.values();
// foreach
for(String s : values){
System.out.println(s);
}

// 清空map集合
map.clear();
System.out.println("键值对的数量:" + map.size());
// 判断是否为空
System.out.println(map.isEmpty()); // true
}
}

Map集合遍历:

代码演示:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/*
Map集合的遍历。【非常重要】
*/
public class MapTest02 {
public static void main(String[] args) {

// 第一种方式:获取所有的key,通过遍历key,来遍历value
Map<Integer, String> map = new HashMap<>();
map.put(1, "zhangsan");
map.put(2, "lisi");
map.put(3, "wangwu");
map.put(4, "zhaoliu");
// 遍历Map集合
// 获取所有的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);
}*/
// foreach也可以
for(Integer key : keys){
System.out.println(key + "=" + map.get(key));
}

// 第二种方式:Set<Map.Entry<K,V>> entrySet()
// 以上这个方法是把Map集合直接全部转换成Set集合。
// Set集合中元素的类型是:Map.Entry
Set<Map.Entry<Integer,String>> set = map.entrySet();
// 遍历Set集合,每一次取出一个Node
// 迭代器
/*Iterator<Map.Entry<Integer,String>> it2 = set.iterator();
while(it2.hasNext()){
Map.Entry<Integer,String> node = it2.next();
Integer key = node.getKey();
String value = node.getValue();
System.out.println(key + "=" + value);
}*/

// foreach
// 这种方式效率比较高,因为获取key和value都是直接从node对象中获取的属性值。
// 这种方式比较适合于大数据量。
for(Map.Entry<Integer,String> node : set){
System.out.println(node.getKey() + "--->" + node.getValue());
}
}
}

HashMap集合

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; // 下一个节点的内存地址。
}
}
哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。)
4、最主要掌握的是:
map.put(k,v)
v = map.get(k)
以上这两个方法的实现原理,是必须掌握的。
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集合的存取效率,所必须的。
代码演示:
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/*

*/
public class HashMapTest01 {
public static void main(String[] args) {
// 测试HashMap集合key部分的元素特点
// Integer是key,它的hashCode和equals都重写了。
Map<Integer,String> map = new HashMap<>();
map.put(1111, "zhangsan");
map.put(6666, "lisi");
map.put(7777, "wangwu");
map.put(2222, "zhaoliu");
map.put(2222, "king"); //key重复的时候value会自动覆盖。

System.out.println(map.size()); // 4

// 遍历Map集合
Set<Map.Entry<Integer,String>> set = map.entrySet();
for(Map.Entry<Integer,String> entry : set){
// 验证结果:HashMap集合key部分元素:无序不可重复。
System.out.println(entry.getKey() + "=" + entry.getValue());
}
}
}

HashCode

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表示两个对象相同,在同一个单向链表上比较。
那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。
所以has
4000
hCode()方法的返回值也应该相同。

3、hashCode()方法和equals()方法不用研究了,直接使用IDEA工具生成,但是这两个方法需要同时生成。

4、终极结论:
放在HashMap集合key部分的,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。

5、对于哈希表数据结构来说:
如果o1和o2的hash值相同,一定是放到同一个单向链表上。
当然如果o1和o2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。
HashMap集合key部分允许null吗?
允许
但是要注意:HashMap集合的key null值只能有一个。
代码演示(深入版):
import java.util.Objects;
import java.util.HashSet;
import java.util.Set;

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表示相等)

System.out.println("s1的hashCode=" + s1.hashCode()); //284720968 (重写hashCode之后-1432604525)
System.out.println("s2的hashCode=" + s2.hashCode()); //122883338 (重写hashCode之后-1432604525)

// s1.equals(s2)结果已经是true了,表示s1和s2是一样的,相同的,那么往HashSet集合中放的话,
// 按说只能放进去1个。(HashSet集合特点:无序不可重复)
Set<Student> students = new HashSet<>();
students.add(s1);
students.add(s2);
System.out.println(students.size()); // 这个结果按说应该是1. 但是结果是2.显然不符合HashSet集合存储特点。怎么办?
}
}
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;
}

// hashCode

// equals(如果学生名字一样,表示同一个学生。)
/*public boolean equals(Object obj){
if(obj == null || !(obj instanceof Student)) return false;
if(obj == this) return true;
Student s = (Student)obj;
return this.name.equals(s.name);
}*/

@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);
}

@Override
public int hashCode() {
return Objects.hash(name);
}
}

Hashtable

Hashtable的key可以为null吗?
Hashtable的key和value都是不能为null的。
HashMap集合的key和value都是可以为null的。

Hashtable方法都带有synchronized:线程安全的。
线程安全有其它的方案,这个Hashtable对线程的处理
导致效率较低,使用较少了。

Hashtable和HashMap一样,底层都是哈希表数据结构。
Hashtable的初始化容量是11,默认加载因子是:0.75f
Hashtable的扩容是:原容量 * 2 + 1
代码演示
import java.util.Hashtable;
import java.util.Map;

public class HashtableTest01 {
public static void main(String[] args) {
Map map = new Hashtable();

//map.put(null, "123");
map.put(100, null);
}
}

Properties

代码演示:
Prope	rties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。
Properties被称为属性类对象。
Properties是线程安全的。
import java.util.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
Stri
20000
ng 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);

}
}

TreeSet

1、TreeSet集合底层实际上是一个TreeMap
2、TreeMap集合底层是一个二叉树。
3、放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了。
4、TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。(根据字典顺序排序)
称为:可排序集合。

对自定义的类型来说,TreeSet可以排序吗?
可以排序,但是放在TreeSet集合中的元素需要实现java.lang.Comparable接口。
并且实现(重写)compareTo方法。equals可以不写。

如果不重写方法会出现以下异常:
java.lang.ClassCastException:
class com.bjpowernode.javase.collection.Person
cannot be cast to class java.lang.Comparable
出现这个异常的原因是:
Person类没有实现java.lang.Comparable接口。
代码演示:(TreeSet排序)
import java.util.TreeSet;

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;
}

// 需要在这个方法中编写比较的逻辑,或者说比较的规则,按照什么进行比较!
// k.compareTo(t.key)
// 拿着参数k和集合中的每一个k进行比较,返回值可能是>0 <0 =0
// 比较规则最终还是由程序员指定的:例如按照年龄升序。或者按照年龄降序。
@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+"]";
}
}
代码演示(深入版):
import java.util.TreeSet;
/*
先按照年龄升序,如果年龄一样的再按照姓名升序。
*/
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 +
'}';
}

/*
compareTo方法的返回值很重要:
返回0表示相同,value会覆盖。
返回>0,会继续在右子树上找。【10 - 9 = 1 ,1 > 0的说明左边这个数字比较大。所以在右子树上找。】
返回<0,会继续在左子树上找。
*/
@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;
}
}
}

TreeSet的第二种排序方法:比较器

TreeSet集合中元素可排序的第二种方式:使用比较器的方式。
最终的结论:
放到TreeSet或者TreeMap集合key部分的元素要想做到排序,包括两种方式:
第一种:放在集合中的元素实现java.lang.Comparable接口。
第二种:在构造TreeSet或者TreeMap集合的时候给它传一个比较器对象。
Comparable和Comparator怎么选择呢?
当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现Comparable接口。
如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用Comparator接口。

Comparator接口的设计符合OCP原则。
代码演示:
import java.util.Comparator;
import java.util.TreeSet;

public class TreeSetTest06 {
public static void main(String[] args) {
// 创建TreeSet集合的时候,需要使用这个比较器。
// TreeSet<WuGui> wuGuis = new TreeSet<>();//这样不行,没有通过构造方法传递一个比较器进去。

// 给构造方法传递一个比较器。
//TreeSet<WuGui> wuGuis = new TreeSet<>(new WuGuiComparator());

// 大家可以使用匿名内部类的方式(这个类没有名字。直接new接口。)
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 +
']';
}
}

// 单独在这里编写一个比较器
// 比较器实现java.util.Comparator接口。(Comparable是java.lang包下的。Comparator是java.util包下的。)
/*
class WuGuiComparator implements Comparator<WuGui> {

@Override
public int compare(WuGui o1, WuGui o2) {
// 指定比较规则
// 按照年龄排序
return o1.age - o2.age;
}
}
*/

Collections工具类

java.util.Collection 集合接口
java.util.Collections 集合工具类,方便集合的操作。
代码演示:
import java.util.*;

public class CollectionsTest {
public static void main(String[] args) {

// ArrayList集合不是线程安全的。
List<String> list = new ArrayList<>();

// 变成线程安全的
Collections.synchronizedList(list);

// 排序
list.add("abf");
list.add("abx");
list.add("abc");
list.add("abe");

Collections.sort(list);
for(String s : list){
System.out.println(s);
}

List<WuGui2> wuGuis = new ArrayList<>();
wuGuis.add(new WuGui2(1000));
wuGuis.add(new WuGui2(8000));
wuGuis.add(new WuGui2(500));
// 注意:对List集合中元素排序,需要保证List集合中的元素实现了:Comparable接口。
Collections.sort(wuGuis);
for(WuGui2 wg : wuGuis){
System.out.println(wg);
}

// 对Set集合怎么排序呢?
Set<String> set = new HashSet<>();
set.add("king");
set.add("kingsoft");
set.add("king2");
set.add("king1");
// 将Set集合转换成List集合
List<String> myList = new ArrayList<>(set);
Collections.sort(myList);
for(String s : myList) {
System.out.println(s);
}

// 这种方式也可以排序。
//Collections.sort(list集合, 比较器对象);
}
}

class WuGui2 implements Comparable<WuGui2>{
int age;
public WuGui2(int age){
this.age = age;
}

@Override
public int compareTo(WuGui2 o) {
return this.age - o.age;
}

@Override
public String toString() {
return "WuGui2{" +
"age=" + age +
'}';
}
}

泛型(JDK5之后)

泛型这种语法机制,旨在程序编译阶段起作用,只是给编译器参考的。(运行阶段泛型没用)
泛型的好处:
集合中存储的元素类型统一了。
从集合中取出来的元素类型是泛型指定的类型,不需要进行大量的“向下转型”
泛型的缺点:
导致集合中存储的怨怒缺乏多样性。
大多数业务中,集合中元素的类型还是统一的,所以这种泛型特性被大家所认可。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class GenericTest01 {
public static void main(String[] args) {
// JDK8之后引入了自动推断机制(又称钻石表达式)
//以下两种写法均可,JDK8以后最好用第二种
//List<Animal> myList = new ArrayList<Animal>();
List<Animal> myList = new ArrayList<>();
Cat c = new Cat();
Bird b = new Bird();
myList.add(c);
myList.add(b);
//获取迭代器,这个表示迭代器迭代的是Animal类型
Iterator<Animal> it = myList.iterator();
while (it.hasNext()){
//使用泛型之后,每一次迭代返回的数据都是Animal类型
Animal a = it.next();
//调用父类方法不需要进行强制类型转换了,直接调用
a.move();
//调用子类方法需要进行强制类型转换。
if(a instanceof Cat){
Cat c2 = (Cat)a;
c2.catMove();
}
if (a instanceof Bird){
Bird b2 = (Bird)a;
b2.birdmove();
}
};
}
}
class Animal{
public void move(){
System.out.println("动物在走路");
}
}
class Cat extends Animal{
public void catMove(){
System.out.println("猫在走猫步");
}
}
class Bird extends Animal{
public void birdmove(){
System.out.println("鸟儿在飞翔");
}
}

foreach使用

增强for(foreach)
语法:
for(元素数据类型 变量名 : 数组或集合){
}
代码演示
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/*
集合使用foreach
*/
public class ForEachTest02 {
public static void main(String[] args) {
// 创建List集合
List<String> strList = new ArrayList<>();

// 添加元素
strList.add("hello");
strList.add("world!");
strList.add("kitty!");

// 遍历,使用迭代器方式
Iterator<String> it = strList.iterator();
while(it.hasNext()){
String s = it.next();
System.out.println(s);
}

// 使用下标方式(只针对于有下标的集合)
for(int i = 0; i < strList.size(); i++){
System.out.println(strList.get(i));
}

// 使用foreach
for(String s : strList){ // 因为泛型使用的是String类型,所以是:String s
System.out.println(s);
}

List<Integer> list = new ArrayList<>();
list.add(100);
list.add(200);
list.add(300);
for(Integer i : list){ // i代表集合中的元素
System.out.println(i);
}
}
}

代码大总结(所需掌握基础描述)

主要掌握的内容:
1.每个计划对象的创建(new)
2,向集合中添加元素
3,从集合中取出某个元素
4,遍历集合

主要的集合类:
ArrayList
LinkedList
HashSet(HashMap中的key,存储在HashMap集合key的元素需要同时重写hashCode + equals)
TreeSet
HashMap
Properties
TreeMap

代码演示:ArrayList

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;

public class ArrayListTest {
public static void main(String[] args) {
//创建ArrayList集合对象
//ArrayList<String> list = new ArrayList<>();
//创建LinkedList
LinkedList<String> list = new LinkedList<>();
//往集合里面添加元素
list.add("123123");
list.add("123asd3");
list.add("123ad3");
list.add("11e123");
//从集合中取出某个元素:list有下标,用下标取出
String s = list.get(0);
System.out.println(s);
//遍历:forr循环遍历
System.out.println("for循环遍历-----");
for (int i = 0; i <list.size() ; i++) {
String s1 = list.get(i);
System.out.println(s1);
}
//迭代器遍历
System.out.println("以下为迭代器遍历:——————");
Iterator<String> it = list.iterator();
while (it.hasNext()){
String s2 = it.next();
System.out.println(s2);
}
System.out.println("while循环改为for循环遍历----");
for (Iterator<String> it2 = list.iterator(); it.hasNext();){
System.out.println(it2.next());
}
//foreach遍历
System.out.println("以下为foreach遍历---------");
for (String s3 : list){
System.out.println(s3);
}
}
}

代码演示:HashMap

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class HashMapTest {
public static void main(String[] args) {
//创建map集合:
HashMap<Integer,String> map = new HashMap<>();
map.put(1,"asda");
map.put(56,"dsf");
map.put(21,"sdf");
map.put(13,"asda");
map.put(1,"adasd");//key重复会覆盖
//获取map集合的个数
System.out.println(map.size());
//获取map集合key为2的数据
System.out.println(map.get(2));
//Map集合遍历
Set<Integer> keys = map.keySet();
for (Integer key : keys){
System.out.println(key + " = " +map.get(key));
}
System.out.println("-----------");
//第二种方法:将Map集合转换为Set集合,Set集合中每个元素是Node,Node节点中有key,value
Set<Map.Entry<Integer,String>> nodes = map.entrySet();
for (Map.Entry<Integer,String> node : nodes){
System.out.println(node.getKey() + " = " +node.getValue());
}

}
}

代码演示:HashSet

import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;

/*
HashSet的特点:无序不可重复。
HashSet是HashMap的key值。
自定义类中需要重写toString,HashCode,equals方法
* */

public class HashSetTest {
public static void main(String[] args) {
//创建一个HashSet集合
HashSet<Student> set = new HashSet<>();
//添加元素:无序不可重复
set.add(new Student(123,"a"));
set.add(new Student(235,"a"));
set.add(new Student(123,"a"));
//迭代器遍历集合
Iterator<Student> iterator = set.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
System.out.println("---------");
//foreach遍历集合
for (Student s : set){
System.out.println(s);
}
}
}

class Student{
int num;
String name;

public Student(int num, String name) {
this.num = num;
this.name = name;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return num == student.num &&
Objects.equals(name, student.name);
}

@Override
public int hashCode() {
return Objects.hash(num, name);
}

@Override
public String toString() {
return "Student{" +
"num=" + num +
", name='" + name + '\'' +
'}';
}
}

代码演示:TreeSet

import java.util.Comparator;
import java.util.Iterator;
import java.util.Objects;
import java.util.TreeSet;
/*
* TreeSet集合有序不可重复
* */
public class TreeSetTst {
public static void main(String[] args) {
TreeSet<A> treeSet = new TreeSet<>();
treeSet.add(new A("1"));
treeSet.add(new A("1"));
treeSet.add(new A("7"));
treeSet.add(new A("2"));
//迭代器遍历
Iterator<A> it = treeSet.iterator();
while (it.hasNext()){
System.out.println(it.next());
}
System.out.println("------------");
//foreach遍历
for (A s : treeSet){
System.out.println(s);
}
System.out.println("----------");
//new BComparator()比较器
TreeSet<B> tree = new TreeSet<>(new BComparator());
tree.add(new B(1,"asda"));
tree.add(new B(5,"asda"));
tree.add(new B(1,"ahdf"));
tree.add(new B(2,"wer"));
//迭代器遍历
Iterator<B> iterator2 = tree.iterator();
while (iterator2.hasNext()){
System.out.println(iterator2.next());
}

System.out.println("------------");
//将它倒序:传入比较器,匿名内部类
TreeSet<String> treeSet1 = new TreeSet<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
return Integer.valueOf(o2) - Integer.valueOf(o1);//o1-o2为升序,o2-o1为降序
}
});
treeSet1.add("11");
treeSet1.add("12");
treeSet1.add("19");
treeSet1.add("11");
treeSet1.add("16");
Iterator<String> it2 = treeSet1.iterator();
while (it2.hasNext()){
System.out.println(it2.next());
}

}
}
//第一种自定义类,比较器
class A implements Comparable<A>{
String name;

public A(String name) {
this.name = name;
}

@Override
public String toString() {
return "A{" +
"name='" + name + '\'' +
'}';
}

@Override
public int compareTo(A o) {
return Integer.valueOf(this.name)-Integer.valueOf(o.name);
}
}
//第二种自定义类的比较器Comparator
class B{
int i;
String name;

public B(int i, String name) {
this.i = i;
this.name = name;
}

@Override
public String toString() {
return "B{" +
"i=" + i +
", name='" + name + '\'' +
'}';
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
B b = (B) o;
return i == b.i &&
Objects.equals(name, b.name);
}

@Override
public int hashCode() {
return Objects.hash(i, name);
}
}
//Comparator比较器:
class BComparator implements Comparator<B>{

@Override
public int compare(B o1, B o2) {
return o1.i-o2.i;
}
}

代码演示:ProperTies

import java.util.Iterator;
import java.util.Properties;
import java.util.Set;

public class ProperTiesTest {
public static void main(String[] args) {
Properties pro = new Properties();
//存数据:
pro.setProperty("wqe","ad");
pro.setProperty("qwe","zfa");
pro.setProperty("qwe","fhgg");
//取数据:
String username = pro.getProperty("qwe");
String password = pro.getProperty("wqe");
System.out.println(username);
System.out.println(password);

}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: