集合
什么是集合
数组其实就是一个集合,集合实际上就是一个容器,用于容纳其它类型的数据。
集合是一个载体,可以一次容纳多个对象。
在实际开发中,假设连接数据库,效据库当中有10条记录, 那么假设把这10条记录查询出来,在java程序中会将10条收据封装成10个java对象,然后将10个java对象放到某一个集合当中,将集合传到前端,然后遍历集合,将一个数据一个效据展现出来。
理解
集合不能直接存储 基本数据类型,不能直接存储java对象。
集合本身都是存储 引用数据类型。
list.add(100);
这里自动装箱 Integer
画图理解如下:
集合中,任何时候,存储的都是引用。
集合中存放的就是对象的内存地址。
在java中,每一个不同的集合,底层会有 不同的数据结构。
常见数据结构:
数组
二叉树
链表
哈希表
图…
eg: 往集合c1中放数据,可能是放在数组上了。
往集合c2中放数据,可能是放到二叉树上了。
new ArrayList() 创建一个集合,底层是数组。
new LinkList() 创建一个集合对象,底层是链表。
new TreeSet() 创建一个集合对象,底层是二叉树。
集合所处的包
java.util.*;
所有的集合类和集合按口都在java.uti1包下。
集合继承图
进一步往下扩展
扩展 实现关系 实现 List 接口
扩展关系 实现Set接口
总结 list 和 set
新的框架Map接口
集合分类
在java中,集合分为两大类:
- 单个方式存储元素
- 键值对方式存储元素
单个方式存储元素,这一类集合中超级父接口:java.util.Collection;
以键值对的方式存储元泰,这一类集合中超级父接口:Java.util.Map;
单个方式存储元素
单个方式存储元素,这一类集合中超级父接口:java.util.Collection;
键值对方式存储元素
以键值对的方式存储元泰,这一类集合中超级父接口:java.util.Map;
集合体系的结构
遍历 就是 迭代
就是 把集合中的 元素 一个一个拿出来。
Iterable 可迭代的,可遍历的,所有集合元素都是可以迭代,可以遍历的。
集合collection对象 继承 interable
当遍历集合元素进行迭代时候,调用 Iterable中interator方法,进行迭代。
创建 Iterator 对象,迭代集合中元素。
所有集合集合继承iterable的含义是 所有集合都是可以迭代的。
迭代器中有三个方法
hasNext()
next()
remove()
不成对方式
List集合
有序 可重复,存储元素没有下标。
这里的有序 是指 list集合 都有下标。
下标从0开始,以1递增。eg:0123456789
List下面又有
ArrayList
LinkedList
Vector
这集合都是 实现List
和List关系是 实现接口
ArrayList 底层采用了数组 数据结构 , 非线程安全的。
LinkedList 底层采用了双向链表 数据结构
Vector 底层采用了数组 数据结构, 线程安全的。
Set集合
无序 不可重复
元素没有下标
存进去是这个元素,取出来就未必了。
set集合中元素不能重复。
Set下面常用的
HashSet
SortedSet
HashSet 底层实际上 new一个HashMap集合。
向HashSet集合中存储元素,实际上就是存储到HashMap集合中了。
HashMap底层采用哈希表结构。
SortedSet接口
特点是 可以拍序 不可重复。
TreeSet类 实现SortedSet接口。
TreeSet 的底层实际上是一个 TreeMap结合。
向TreeSet集合中存储元素,实际上就是存储到TreeMap集合中了。
TreeMap 底层采用的二叉树结构。
成对的方式 Map
Map集合和Colection集合没有关系。
Map集合以key 和 value这种键值对的方式存储元素。 key 和 value
都是存储java对象的内存地址。
Map集合和Set集合存储特点相同
HashMap 非线程安全的
Hashtable 线程安全的
以上这两个都属于 类,是继承 实现Map接口的。
SortedMap接口
存储特点:
无序,不可以重复,存储进去的元素会自动排序。
TreeMap类,实现SortedMap接口
底层是一个二叉树。
Properties 属性类,继承实现Hashtable
继承 Hashtable
存储元素也是采用 key和value键值对的形式存储,key和value只支持String类型。
Properties 被称为属性类。
总结所有的实现类:
ArrayList: 底层是数组。
LinkedList: 底层是双链表。
Vector: 底层是数组,线程安全,效率低,使用较少。
HashSet: 底层是HashMap,放到HashSet集合中的元素等同于放到HashMap集合key部分。
TreeSet: 底层是TreeMap,放到TreeSet集合中的元素等同于放到TreeMap集合key部分。
HashMap: 底层是哈希表。
Hashtable: 底层也是哈希表,线程安全,效率低,使用较少。
Properties: 线程安全的,key和value只能存储字符串。
TreeMap: 底层是二叉树。 TreeMap集合的key 自动按照大小顺序排序。
List集合存储元素的特点:
有序可重复
有序:存进去的顺序和取出来的顺序相同,每一个元素都有下标。
可重复: 存进去1,可以再存进去一个1.
Set(Map)集合存储元素的特点:
无序不可重复
无序:存进去的顺序和取出的顺序 不一一定相同,另外set结合中元素没有下标。
不可重复:存进去1,不能再存储1了。
SortedSet(SortedMap)集合存储元素的特点:
首先是 无序不可重复
但是 sortedset中元素 是可排序。
可排序:可以按照大小顺序排列。
Map集合中的key就是一个Set集合。
往Map集合中放数据,实际上就是放到Map集合的Key部分。
Collection
collection接口常用方法
java.util.Collection接口中常用的方法
-
collections中能存放哪些元素?
没有使用"泛型",collection可以存放object的所有类型。
使用"泛型"后,collection中只能存储某个具体的类型。
集合 不能直接存储 基本数据类型,也不能存储java对象,只能存储java对象的内存地址。 -
Collection常用方法
boolean add(Oject e) 向集合中添加元素
int size() 获取集合中元素的个数
void clear() 清空集合
boolean contain(Object o) 判断当前集合中是否包含元素o; 包含返回true,不包含返回false。
boolean isEmpty() 判断集合是否为空
Object[] toArray() 调用这个方法可以把集合转换成数组
import java.util.ArrayList;
import java.util.Collection;
/**
*/
public class CollectionTest {
public static void main(String[] args) {
// 创建一个集合对象
// Collection c = new Collection();
/*接口是抽象的,无法实例化
所以通过new一个接口下面的类 创建对象
*/
// 多态 父类型引用 指向子类型对象
Collection c = new ArrayList();
//测试collection接口中的常用方法
c.add(1200);// 自动装箱:实际上是放进去了一个对象的内存地址。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());
// 清空集合
c.clear();
System.out.println(c);
// 再向拟合中添加元素
c.add("hello");// "hello"对象的内存地址存放到集合当中。
c.add("绿巨人");
System.out.println(c);
// 判断集合中是否包含 "绿巨人"
boolean flag = c.contains("绿巨人");
System.out.println(flag);
boolean flag2 = c.contains("绿巨人2");
System.out.println(flag2);
//判断集合是否为空
System.out.println(c.isEmpty());
//转换成数组 (了解,使用不多)
Object[] objs = c.toArray();
for (int i = 0; i < objs.length; i++){
System.out.println(objs[i]);
}
}
}
重点方法 Iterable
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
/**
*
* 关于集合遍历/迭代专题。(重点)
*/
public class CollectionTest02 {
/**
* 注意:在所有Collection通用的一种方式。
* 在Map集合中不能用,但是在所有的Collection以及子类中使用。
* 创建集合对象
*/
public static void main(String[] args) {
Collection c = new HashSet(); //后面的集合无所谓,主要看前面的Collection接口如何遍历/迭代。
//添加元素
c.add("abc");
c.add("def");
c.add(100);
c.add(new Object());
// 对集合collection进行遍历/迭代
// 第一步: 获取集合对象的迭代器
Iterator it = c.iterator();
// 第二步: 通过以上获取的迭代器对象开始迭代/遍历集合。
/**
* 迭代器里面两个方法:
* boolean hasNext() 如果仍有元素可以迭代,则返回true。
* Object next() 返回迭代的下一个元素。
*/
boolean hasNext = it.hasNext();
// //有没有下一个,如果有取一下
// if (hasNext){
// Object object = it.next();
// System.out.println(object);
// }
//
// if (hasNext){
// Object object = it.next();
// System.out.println(object);
// }
// if (hasNext){
// Object object = it.next();
// System.out.println(object);
// }
// if (hasNext){
// Object object = it.next();
// System.out.println(object);
// }
//如果有就取出来,知道没有
while (it.hasNext()){
Object object = it.next();
System.out.println(object);
}
}
}
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
/**
* 关于集合遍历/迭代专题。(重点)
*/
public class CollectionTest02 {
public static void main(String[] args) {
// 创建集合对象
Collection c1 = new ArrayList();
// 添加元素
c1.add(1);
c1.add(2);
c1.add(3);
c1.add(4);
// 迭代集合
/**
* 存进去什么类型,取出来就是什么类型
* 但是在print的时候输出的是字符串
* printIn调用了ToString()方法。
*/
Iterator it = c1.iterator();
while (it.hasNext()) {
Object o = it.next();
System.out.println(o);
}
// HashSet集合: 无序不可重复
// 无序: 存进去 和 取出 顺序未必相同
// 不可重复: 存储100,不能在存储100, 但是底层算法会处理重复情况,不让其报错!
Collection c2 = new HashSet();
c2.add(100);
c2.add(200);
c2.add(300);
c2.add(100);
c2.add(900);
c2.add(1000);
Iterator it2 = c2.iterator();
while (it2.hasNext()){
Object o2 = it2.next();
System.out.println(o2);
}
}
}
contains方法
深入Collection集合中的contain方法:
boolean contains(Object o)
判断集合中是否包含某个对象o。
如果包含返回true,如果不包含返回false。
contain方法是用来判断结合中是否包含某个元素的方法
在底层调用了equals方法进行对比
equals 方法是 比较 内容,而非地址
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
/**
* 深入Collection集合中的contain方法:
* boolean contains(Object o)
* 判断集合中是否包含某个对象o。
* 如果包含返回true,如果不包含返回false。
*
*/
public class CollectionTest02 {
public static void main(String[] args) {
// 创建集合对象
Collection c1 = new ArrayList();
// 向集合中存储元素
String s1 = new String("abc"); // s1 = 0x1111
String s2 = new String("def"); // s2 = 0x2222
c1.add(s1); // 往集合中放了abc
c1.add(s2);
//查看集合中元素个数
System.out.println(c1.size()); // 2
//新建对象String
String x = new String("abc"); // x = 0x5555
System.out.println(c1.contains(x)); // true 判断集合中是否包含abc
}
}
小tips:
equals 比较的是字符串内容,而非地址
图解上面程序
x没在c里面。c确实不包含 x。
String.的equals方法 比较的是 内容 而不是内存地址
contains方法 是用来 判断集合中是否包含某个元素的方法。
在底层 通过 调用 equals方法 来进行判断 是否包含这个元素。
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
public class CollectionTest02 {
public static void main(String[] args) {
// 创建集合对象
Collection c1 = new ArrayList();
// 创建用户对象 u1
User u1 = new User("jack");
// 创建用户对象 u2
User u2= new User("jack");
// 集合添加元素u1
c1.add(u1);
System.out.println(c1.contains(u2)); //false
// u1.equals(u2) 调用的是Object的 equals比较,比较的是内存地址
}
}
class User{
private String name;
public User(){};
public User(String name){
this.name = name;
}
}
false
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
public class CollectionTest02 {
public static void main(String[] args) {
// 创建集合对象
Collection c1 = new ArrayList();
// 创建用户对象 u1
User u1 = new User("jack");
// 创建用户对象 u2
User u2= new User("jack");
// 集合添加元素u1
c1.add(u1);
//重写equals方法后,比较的是name属性
System.out.println(c1.contains(u2)); //true
}
}
class User{
private String name;
public User(){};
public User(String name){
this.name = name;
}
// 如果名字一样,表示同一个人,不再比较内存地址了
public boolean equals(Object o){
if (this == o) return true;
if (o == null || !(o instanceof User)) return false;
User u1 = (User) o;
return u1.name.equals(this.name);
}
}
true
结论:
存放在集合里面的元素类,需要重写equals方法。
重写 就是 比较 内容。
不重写 就是 比较 内存地址。
interger类 本身重写了 equals方法
所以调用结果为 true.
remove方法也调用了equals方法
java调用s1 和 s2是一样的,删除s2就是删除s1了。
重点:
当集合的结构 发生改交时,送代器必须重新获取,如果还是用以前老的送代器,会出现 异常。
异常:java.util.ConcurrentModificationException
import java.util.*;
import java.util.Map;
/**
重点:
当集合的结构 发生改交时,送代器必须重新获取,如果还是用以前老的送代器,会出现 异常。
异常:java.util.ConcurrentModificationException
*/
public class Test {
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();
System.out.println(obj);
}
int i = 10;
Collection c2 = new ArrayList();
c2.add("abc");
c2.add("def");
c2.add("xyz");
Iterator it2 = c2.iterator();
while (it2.hasNext()){
Object obj = it2.next();
c2.remove(obj);
// delete element
/*
删除元素后,集合结构会发生变化,应该重新获取 迭代器
但是,循环下一次的时候并没有重新获取选代器,
所以会出现异常:java.util.ConcurrentModificationException
*/
System.out.println(obj);
}
}
}
在迭代集合元素的过程中,不能调用集合的remove 方法,删除元素:remove(o);送代过程中不能这样。
使用迭代器去删除元素
public class Test {
public static void main(String[] args) {
int i = 10;
Collection c2 = new ArrayList();
c2.add("abc");
c2.add("def");
c2.add("xyz");
Iterator it2 = c2.iterator();
while (it2.hasNext()){
Object obj = it2.next();
it2.remove(); // 使用迭代器删除,删除的是 迭代器当前指向的 元素
System.out.println(obj);
}
System.out.println(c2.size());
}
}
abc
def
xyz
0
直接通过集合去删除元素,没有通知迭代器。(导致送代器的快照和原集合状态不同。
List
- List集合存储元素特点:
有序可重复有序;
List集合中的元素有下标,从0开始,以1递增。
2.List既然是collection接口的子接口,
那么肯定list接口有自己"特色"的方法: 以下只列出List接口特有的常用的方法:
void add(int index, Object element)
Object get(int index)
int indexOf (Object o)
int LastIndexof (Object o)
Object remove (int index)
Object set(int index, Object element)
Array集合:
1.默认 初始化容量10,底层先创建了一个长度为0的数组,当添加第一个元素的时候,初始化容量10。
2.集合底层是一个Object[]数组。
3.构造方法:
eg:
new ArrayList()
new ArrayList(20)
-
ArrayList集合的扩容:
增长到原容量的1.5倍。
ArrayList 集合底层是数组,怎么优化?
尽可能少的广容。团为数組扩容效率比较低,建议在使用ArrayList集合
的时候预估计元素的个数,给定一个初始化容量。 -
数组优点:
检索效率比较高 -
数组缺点:
随机增删元素效率比较低
另外数组无法存储大数据量。(很雅找到一块非常巨大的连续的内存存储空间。) -
向数组末尾 添加元素,效率很高,不受影响
-
面试官经常问的问题
这么多集合,那个集合最多
ArrayList集合
因为 往数组末尾添加元素, 效率不受影响。
另外, 我们 检索/查找 某个元素的操作比较多。
import java.util.*;
public class Test {
public static void main(String[] args) {
List myList1 = new ArrayList(); //默认初始化容量为10
List myList2 = new ArrayList(100); //指定初始化容量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));
}
}
}
50
100
900
200
单向链表
图
概念
对于链表数据结构来说:基本的单元是节点Node。
对于单向链表来说,任何一个节点Node中都有两个属性:
第一:存储的数据。 第二:下一节点的内存地址。
链表优点:随机增删元素效率较高。(因为增删元素不涉及到大量元素位移)
链表缺点:查询效率较低,每一次查找某个元素的时候都需要从头节点开始往下遍历。
代码
node的代码
/*
单链表的节点:
节点是单向链表中基本的单元。
每一个节点Node都有两个属性:
一个属性: 存储的数据。
另一个属性: 下一个节点的内存地址。
*/
public class Node{
// 存储的结构
Object element;
// 下一个节点的内存地址
Node next;
public Node(){
}
public Node(Object element, Node next){
this.element = element;
this.next = next;
}
}
link的代码
public class Link {
// 头结点
Node head = null;
int size = 0;
public int size(){
return this.size;
}
// 向末尾添加元素
public void add(Object data){
//创建一个新的节点
//让之前 单链表的末尾节点next 指向 新节点对象。
if (head == null){ // 说明没有新的节点对象,就new一个新的节点对象作为头节点。
new Node(data, null);
} else{
// 说明头不是空,头结点已经有了! 应该找出当前末尾节点,让当前末尾节点的next是新节点
Node currentLastNode = findLast(head);
currentLastNode.next = new Node(data, null);
}
this.size++;
}
private Node findLast(Node node){
// 如果一个节点的next是null,说明这个节点就是 末尾节点。
if(node.next == null) return node;
return findLast(node.next);// 递归算法
}
public void remove(Object object){
}
public void modify(Object newobj){
}
public int find(Object obj){
return 1;
}
}
运行代码
import java.util.*;
/*
对于链表结构来说: 基本的单元是 节点 Node
对于单项链表来说, 任何一个节点Node都有两个属性:
第一: 存储的数据
第二: 下一个节点的内存地址
*/
public class Test {
public static void main(String[] args) {
// 创建集合对象
Link link = new Link();
// 往集合中添加元素
link.add(100);
link.add(200);
link.add(300);
link.add("abc");
System.out.println(link.size);
}
}
linkedlist底层也是有下标的
注意:ArrayList之所以检索效率比较高,不是单纯团为下标的原因。是因为底房数组发挥的作用。
链表的优点:
由于能表上的元素在空同存储上內存地址不连续。
所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。
在以后的开发中,如果遇到随机增删/集合中元素的业务比较多时,建议使用Linkedlist。
链表的缺点:
不能通过数学表达式计算拔查找元素的内存地址,每一次查找都是从新节点开始遍历,直到找到为止。所以Linkedlist 集合检索/查找的效率较低。
ArrayList集合是 非线程安全的。(不是线程安全的集合)。
ArrayList:把 检索 发挥到极致。
Linkedlist:把 随机增删 发挥到极致。
加元素都是往 末尾添加,所以Arraylist用的上LinkedList多。
import java.util.*;
public class Test {
public static void main(String[] args) {
// linkedlist 底层也是有下标的
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);
}
}
}
双向链表
图解
基本单元 : 节点 Node; 上一个节点地址; 下一个节点地址。
定义
- LinkedList是双向链表。
- 对于链表数据结构来说, 随机增删效率较高。检索效率较低。
- 链表中的元素在空间存储上,内存地址不连续。
代码
import java.util.*;
public class Test {
public static void main(String[] args) {
// linkedlist 底层也是有下标的
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
// 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));
}
}
}
Vector
import java.util.*;
/**
* 1、底层也是一个数组。
*
* 2、初始化容量:10
*
* 3、怎么扩容的?
* 扩容之后是原容量的2倍。
* 10--> 20 --> 40 -->80
*
* 4、 Arraylist集合扩容特点:
* ArrayList扩容是原容量 1.5倍
* 10 --> 15 --> 15 * 1.5 == 22.5
*
* 5。 Vector中所有的方法部是线程同步的,都带有synchronized关健字, 是给程安全的。效率比较低,使用较少了.
*
**/
public class Test {
public static void main(String[] args) {
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);
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.add("111");
mylist.add("222");
mylist.add("333");
}
}
1
2
3
4
5
6
7
8
9
10
11
java泛型
就是 ListArray<> 在括号内增加自动类型推断。
代码
import java.awt.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test {
public static void main(String[] args) {
List myList = new ArrayList();
Cat c = new Cat();
Bird b = new Bird();
myList.add(c);
myList.add(b);
//遍历集合,取出Cat让它抓老鼠,取出Bird让它飞。
Iterator it = myList.iterator();
while (it.hasNext()){
Object obj = it.next();
if (obj instanceof Cat){
((Cat) obj).catchMouse();
}
if (obj instanceof Bird){
((Bird) obj).fly();
}
}
// 使用泛型来指定集合中的数据类型
// 泛型这种机制,只有在 程序编译阶段起作用,只是给编译器参考的。
/**
* 好处: 集合中存储的元素类型一致
* 坏处: 导致集合中存储的元素类型不能多样性。
*
* 大多数业务中,集合中元素的类型是统一的,所以这种泛型还是会被大家认可。
*/
List<Animal> mylist = new ArrayList<Animal>();
Cat c1 = new Cat();
Bird b2 = new Bird();
mylist.add(c1);
mylist.add(b2);
Iterator<Animal> it2= mylist.iterator();
while (it2.hasNext()){
// 使用泛型后,每次迭代使用的
Animal a = it2.next();
a.move();
}
}
}
class Animal{
public void move(){
System.out.println("动物在移动!");
}
}
class Cat extends Animal{
public void catchMouse(){
System.out.println("猫抓老鼠!");
}
}
class Bird extends Animal{
public void fly(){
System.out.println("鸟儿在飞!");
}
}
自定义泛型
public class Test<asdasdasdasdasdadas> {
public void doSome(asdasdasdasdasdadas o){
System.out.println(o);
}
public static void main(String[] args) {
// new对象时候, 指定了泛型:String类型
Test<String> gt = new Test<>();
gt.doSome("abc");
Test<Integer> t1 = new Test<>();
t1.doSome(100);
// t1.doSome("abc"); // 类型不匹配
}
}
forreach循环
public class Test{
public static void main(String[] args) {
int[] arr = {432,4,65,46,54,76,54};
for (int i = 0; i < arr.length; i++){
System.out.println(arr[i]);
}
//增强for循环(foreach)
// foreach有一个缺点,没有下标
// 在需要使用下标的循环中,不建议使用
for (int data: arr){
System.out.println(data);
}
}
}
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Test{
public static void main(String[] args) {
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){
System.out.println(s);
}
}
}
HashSet
import java.util.HashSet;
import java.util.Set;
public class Test {
public static void main(String[] args) {
Set<String> strs = new HashSet<>();
strs.add("hello1");
strs.add("hello2");
strs.add("hello3");
strs.add("hello4");
/**
* 1.存储时顺序和取出时顺序不同。
* 2.不可重复。
* 3.放到HashSet集合中的元素实际上是放到了HashMap集合的ket部分了。
*
*/
for (String s: strs){
System.out.println(s);
}
}
}
TreeSet
1、无序不可重复的,但是存储的元素可以自动按照大小顺序排序! 称为:可排序集合。
2、无序:这里的无序指的是存进去的顺序和成出来的顺序不同。并且没有下标。
import java.util.Set;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
Set<String> strs = new TreeSet<>();
// 添加元素
strs.add("A");
strs.add("B");
strs.add("C");
strs.add("D");
strs.add("E");
strs.add("Z");
for (String i: strs){
System.out.println(i);
}
}
}
Map
/**
* Map接口常用用方法
* Map 集合 以 key 和 value方式存储数据
* key 和 value 都是引用数据类型
* key起到主导的地位, value是key的一个附属品。
*
* Map接口常用方法:
* void value();
* boolean containsKey(Object key);
* boolean
* put (K key, V value)向ap集合中添加绿值对
*
* 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集合中键值对的个数
* Set<k> keySet() 获配 Map集合所有的key (所有的键 是一个键值集合)
* Collection<V> values() 获取Map集合中所有的value, 返回一个Collection
*
* 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]
* 2 = lisi
* 3 = wangwu
* 4 = zhaoliu
*/
理解set的entrySet一定要理解静态内部类
public class Test {
public static void main(String[] args) {
Test.InnerClass.m1();
//静态内部类对象
Test.InnerClass t1 = new Test.InnerClass();
t1.m2();
}
//内部类
private static class InnerClass {
// 静态方法
public static void m1() {
System.out.println("静态内部类的m1方法! ");
}
// 实例方法
public void m2(){
System.out.println("静态内部类的实例方法执行! ");
}
}
}
import java.util.HashSet;
import java.util.Set;
/**
* Map接口常用用方法
* Map 集合 以 key 和 value方式存储数据
* key 和 value 都是引用数据类型
* key起到主导的地位, value是key的一个附属品。
*
* Map接口常用方法:
* void value();
* boolean containsKey(Object key);
* boolean
* put (K key, V value)向ap集合中添加绿值对
*
* 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集合中键值对的个数
* Set<k> keySet() 获配 Map集合所有的key (所有的键 是一个键值集合)
* Collection<V> values() 获取Map集合中所有的value, 返回一个Collection
*
* 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]
* 2 = lisi
* 3 = wangwu
* 4 = zhaoliu ------> 这个东西是 Map.entry
*/
public class Test {
public static void main(String[] args) {
Test.InnerClass.m1();
//静态内部类对象
Test.InnerClass t1 = new Test.InnerClass();
t1.m2();
Set<MyMap.MyEntry<Integer,String>> set3 = new HashSet<>();
}
//内部类
private static class InnerClass {
// 静态方法
public static void m1() {
System.out.println("静态内部类的m1方法! ");
}
// 实例方法
public void m2(){
System.out.println("静态内部类的实例方法执行! ");
}
}
}
class MyMap{
public static class MyEntry<K,V>{
}
}
Map接口
import java.util.*;
import java.util.Map;
/**
* Map接口常用用方法
* Map 集合 以 key 和 value方式存储数据
* key 和 value 都是引用数据类型
* key起到主导的地位, value是key的一个附属品。
*
* Map接口常用方法:
* void value();
* boolean containsKey(Object key);
* boolean
* put (K key, V value)向ap集合中添加绿值对
*
* 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集合中键值对的个数
* Set<k> keySet() 获配 Map集合所有的key (所有的键 是一个键值集合)
* Collection<V> values() 获取Map集合中所有的value, 返回一个Collection
*
* 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]
* 2 = lisi
* 3 = wangwu
* 4 = zhaoliu ------> 这个东西是 Map.entry
*/
public class Test {
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");
// 通过key获取value
String value = map.get(2);
System.out.println(value); // lisi
// 获取键值对数量
System.out.println(map.size()); // 4
// 通过key删除 key-value
map.remove(2);
System.out.println(map.size()); // 3
// 判断是否包含某个key
// contains方法底层都是调用equals进行比较的
System.out.println(map.containsKey(4)); // true
System.out.println(map.containsValue("wangwu")); // true
// 获取所有value
Collection<String> values = map.values();
// foreach
for (String s: values){
System.out.println(s);
}
//zhangsan
//wangwu
//zhaoliu
}
}
通过key遍历Map集合
public class Test {
public static void main(String[] args) {
// 创建map集合对象
Map<Integer, String> map = new HashMap<>();
// 获取所有key,通过遍历key来遍历value
map.put(1,"zs");
map.put(2,"ls");
map.put(3,"ww");
map.put(4,"zl");
// 遍历Map集合
// 获取所有key
Collection<Integer> keys = map.keySet();
// 遍历key,通过key获取value
// 迭代器
Iterator<Integer> it = keys.iterator();
while (it.hasNext()){
Integer key = it.next();
String value = map.get(key);
System.out.println(key + "=" + value);
}
System.out.println("________");
//foreach
for (Integer key: keys){
System.out.println(key + "=" + map.get(key));
}
}
}
通过Map.Entry遍历Map
import java.util.*;
import java.util.Map;
/**
* Map接口常用用方法
* Map 集合 以 key 和 value方式存储数据
* key 和 value 都是引用数据类型
* key起到主导的地位, value是key的一个附属品。
*
* Map接口常用方法:
* void value();
* boolean containsKey(Object key);
* boolean
* put (K key, V value)向ap集合中添加绿值对
*
* 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集合中键值对的个数
* Set<k> keySet() 获配 Map集合所有的key (所有的键 是一个键值集合)
* Collection<V> values() 获取Map集合中所有的value, 返回一个Collection
*
* 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]
* 2 = lisi
* 3 = wangwu
* 4 = zhaoliu ------> 这个东西是 Map.entry
*/
public class Test {
public static void main(String[] args) {
// 创建map集合对象
Map<Integer, String> map = new HashMap<>();
// 获取所有key,通过遍历key来遍历value
map.put(1,"zs");
map.put(2,"ls");
map.put(3,"ww");
map.put(4,"zl");
// 遍历Map集合
// 获取所有key
Collection<Integer> keys = map.keySet();
// 遍历key,通过key获取value
// 迭代器
Iterator<Integer> it = keys.iterator();
while (it.hasNext()){
Integer key = it.next();
String value = map.get(key);
System.out.println(key + "=" + value);
}
System.out.println("________");
//foreach
for (Integer key: keys){
System.out.println(key + "=" + map.get(key));
}
System.out.println("-------2----------");
// 第二种方法:Set<Map.Entry<K,V>> entrySet()
Set<Map.Entry<Integer,String>> set = map.entrySet();
// 迭代器
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);
}
System.out.println("-----------------");
// foreach
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; // 下一个节点的内存地址
* }
* 哈希表/散列表:一维数组,这个数组中每一个元组是一个单向链表。(数组 和 链表的 结合体。
*
* HashMap 集合的初始化容量是16,默认 加载因子是0.75
* 这个默认加载因子是当HashMap容量达到75%时候,底层开始扩增的时候,数组容量开始扩容。
* 重点,记生:HashMap 集合初始化容量必须是2的倍数,这也是官方规定的,
* 这是因为为了达到散列均勻,为了提高HashMap集合的存取效率,所必须的。
public class Test {
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
}
}
图解
JDK8之后,是加在末尾的。
为什么哈希表的随机增删 和 查询效率都很高?
增删 在链表完成
查询 不需要都扫描
HashMap的集合key 会 先后调用两个方法:
一个是 hashCode()
另一个是 equals()
这两个方法 都需要重写。
HashMap的key部分特点:
无序,不可重复
为什么无序?
因为不一定挂到哪个单向链表上
不可重复 euquals方法保证HashMap集合的key不重复。
如果key重复了,value会覆盖。
同一个单向链表上所有节点的hash相同
因为他们的数组下标是一样的。
但是同一个链表 上 k 和 v的equals方法肯定返回false
因为key不一样。
散列分布均匀:
假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的,散列分布均勻的。
注意:如果一个类的equals方法重写了,那么 hashcode()方法心须重写。
并且equals方法返回如果是true,hashcode()方法返回的值必须一样。
equals 方法返回true表示两个对象相同,在同一个单向链表上比较。 那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的。 所以hashcode()方法的返回值也应该相同。
在JDK8之后,如果哈希表 单向链表中的元素超过8个,单向链表这种数据结构会变成 红黑树结构。
当元素小于6时候,会从红黑树结构变成 单向链表结构。
目的是为了提高检索效率。
复习
遍历HashMap
public class Test {
public static void main(String[] args) {
Map<Integer,String> map = new HashMap<>();
map.put(1,"zs");
map.put(2,"ls");
map.put(3,"ww");
map.put(4,"zl");
Set<Integer> keys = map.keySet();
for (Integer key: keys){
System.out.println(map.get(key));
}
System.out.println("--------------2---------------");
Set<Map.Entry<Integer,String>> set = map.entrySet();
for (Map.Entry<Integer,String> node: set){
System.out.println(node.getValue());
}
}
}
HashMap结合允许key部分为空值吗?
允许
import java.util.*;
import java.util.Map;
public class Test {
public static void main(String[] args) {
Map map = new HashMap();
map.put(null,null);
System.out.println(map.size());// 1
map.put(null,100);
System.out.println(map.size()); // 1
}
}
HashTable
是线程安全的,其中所有的方法都带有 synchrorized天键字,效率较低,
现在使用较少了,因为控制线程安全有其它更好的方法。
Hashtable key和value不允许null 。
Hashtable集合初始化容量为 11。
Hashtable集合扩容是: 原容量*2 + 1
Hashtable和 HashMap底层一样,初始化都是哈希数据结构。
Properties
import java.util.Properties;
/**
* 目前只需要 掌握properties属性对象的相关方法即可。
* Properties 是一个Map集合,继承Hashtable Properties 的key 和 value 部是String类型.
* Properties 被称为 属性类对象。
* Properties 是线程安全的。
*
* */
public class Test2 {
public static void main(String[] args) {
Properties pro = new Properties();
// 需要掌握property的两个方法,一个存,一个取。
pro.setProperty("url","jdc:mysql://localhost:3306/bjpowernode");
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); // jdc:mysql://localhost:3306/bjpowernode
}
}
背诵点
HashSet/HashMap 初始话容量为 16 初始化容量建议2的倍数。
扩容是 原容量 二倍。
TreeSet
import java.util.TreeSet;
/**
* TreeSet 集合底层实际上是一个TreeMap
* TreeMap 集合底层是一个二叉树
* 放到 TreeSet集合中的元素,等同于放到了TreeMap集合 中的 key部分。
* TreeSet集合中的元素: 无序 不可重复,可以按照元素大小自动排序。
*/
/*
数据库中有很多数据:
userid name birth
______________________________
1 zs 1980-11-11
2 ls 1980-10-11
3 ww 1981-11-11
4 zl 1979-11-11
编写程序从 数据库中 取出数据,在页面展示 用户信息的时候 按照生日升序 or 降序
可以使用 TreeSet集合,因为 TreeSet结合放进去,拿出来是有 顺序的。
*/
public class Test2 {
public static void main(String[] args) {
TreeSet<String> ts = new TreeSet<>();
ts.add("zs");
ts.add("ls");
ts.add("ws");
ts.add("zl");
ts.add("a");
for (String s: ts){
System.out.print(s + " "); // a ls ws zl zs
}
}
}
重写comparedTo 接口实现 TreeMap接口的排序方法
import java.math.BigDecimal;
import java.util.TreeSet;
/**
* TreeSet 集合底层实际上是一个TreeMap
* TreeMap 集合底层是一个二叉树
* 放到 TreeSet集合中的元素,等同于放到了TreeMap集合 中的 key部分。
* TreeSet集合中的元素: 无序 不可重复,可以按照元素大小自动排序。
*/
/*
数据库中有很多数据:
userid name birth
______________________________
1 zs 1980-11-11
2 ls 1980-10-11
3 ww 1981-11-11
4 zl 1979-11-11
编写程序从 数据库中 取出数据,在页面展示 用户信息的时候 按照生日升序 or 降序
可以使用 TreeSet集合,因为 TreeSet结合放进去,拿出来是有 顺序的。
*/
public class Test2 {
public static void main(String[] args) {
TreeSet<Person> ts = new TreeSet<>();
Person p1 = new Person(10);
Person p2 = new Person(99);
Person p3 = new Person(12);
Person p4 = new Person(13);
ts.add(p1);
ts.add(p2);
ts.add(p3);
ts.add(p4);
for (Person s: ts){
System.out.print(s + " "); // a ls ws zl zs
}
}
}
// 放在TreeSet集合中的元素 需要实现 java.long.Comparable接口
class Person implements Comparable<Person>{
int age;
public Person(int age){
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"age=" + age +
'}';
}
// 需要在这个方法里面编写比较逻辑 or 比较规则
@Override
public int compareTo(Person o) {
// this就是p1, o就是p2,p1和p2比较就是 this 和 o比较
int age1 = this.age;
int age2 = o.age;
// if (age1 == age2) {
// return 0;
// } else if (age1 > age2){
// return 1;
// }else {
// return -1;
// }
//
// return 0;
return this.age - o.age;
}
}
Reference
https://chasing987.github.io/2020/12/13/Java%E9%9B%86%E5%90%88%E6%A1%86%E6%9E%B6%20list-set-map%20/