目录
一、集合的概述
- 集合实际上就是一个容器、一个载体,可以用来容纳其他类型的数据,属于引用数据类型;
- 集合当中不能直接存储基本数据类型,也不能直接存储java对象,集合当中存储的都是java对象的内存地址(及存储的都是引用);
- 集合在java.util.*包下
二、集合与数组的区别
1、长度区别
- 数组长度固定,定义过大会造成内存空间的浪费,定义过小不够用。
- 集合大小可以变,用多少空间拿多少空间。
2、内容区别
- 数组可以存储基本数据类型和引用数据类型
- 集合中能存储引用数据类型(存储的为对象的内存地址)
3、元素区别
- 数组中只能存储同一种基本数据类型元素
- 集合中可以存储不同类型数据(一般情况下也只存储同一种类型的数据)
4、集合结构
- java中每个不同的集合,底层对应的都是不同的数据结构(数据存储的结构:数组、链表、二叉树等)
三、集分的分类
1.Java中集合分为两类:
- 一类是以单个方式存储元素(单列集合),超级父类接口为:java.util.Collection;
- 一类是以键值对方式存储元素(双列集合) ,超级父类接口为:java.util.Map;
四、集合结构图
1、Conllection集合结构图
总结:
(1)、List集合存储元素特点:有序不可重复
- ArrayList:底层是数组;
- LinkedList:底层是双向列表;
- Vector:底层是数组,线程安全的,效率较低,使用较少;
(2)、Set(对应Map)集合存储元素特点:无序不可重复
- HashSet:底层是HashMap,放到HashMap集合中的元素等同于放到HashMap集合中的key部分;
(3)、SortedSet(对应SortedMap)集合存储元素特点:无序不可重复,但是SortedSet集合中的元素是可排序的,且Set集合中的元素没有下标
- TreeSet:底层是TreeMap,放到TreeMap集合中的元素等同于放到了TreeMap集合中的Key部分;
Map集合的key,就是一个Set集合,在Set集合中存放数据,实际上放到Map集合总的Key部分
2、Map集合结构图
总结:
(1)、Map集合存储元素特点:以键值对方式存储元素,特点是无序不可重复。Map集合的key与Set集合存储元素特点相同
- HashMap:底层是哈希表;
- Hashtable:底层是哈希表,底层是线性安全的,效率较低,使用较少;
- Properties:是线性安全的,并且key和value只能存储字符串String;
(2)、SortedMap集合存储元素特点:无序不可重复,但是放入SortedMap集合key部分的元素会按照大小顺序排序
- TreeMap:底层是二叉树,TreeMao集合中的key可以自动按照大小顺序排序。
<以下为详解部分 >
五、Interator迭代器
1、迭代器Interator作用
- 对集合进行遍历,是所有Collection遍历的一种方式(Set集合中不能使用)
2、迭代器Interator常用方法
方法名 | 说明 |
boolean hasNext() | 如果仍有元素,则返回true |
Objict next() | 返回迭代的下一个元素 |
void remove() | 删除迭代的元素 |
3、迭代器注意事项
- 当集合结构发生改变,迭代器必须重新获取;若迭代器没有重新获取时,调用next()方法时,就会发生异常;
- 在迭代集合元素删除的过程中,不能直接调用集合自身的remove()方法删除元素,否则会发生异常
- 在迭代元素的过程中,一定要使用迭代器对象Interator的remove()方法,删除元素
六、Collection(接口)集合常用方法
方法名 | 说明 |
boolean add(E e) | 添加元素到集合的末尾(追加) |
int size() | 返回集合中元素个数 |
void clear() | 清空集合中的元素(底层调用equles) |
boolean contains(Object o) | 判断集合中是否包含某个元素(底层调用equles) |
boolean isEmpty() | 判断集合是否为空 |
Object[] toArray() | 将集合转为数组 |
boolean remove(Object o) | 删除指定的元素(底层调用equles) |
附加:
- Conllection集合是一个接口
- contains()/remove()方法,底层调用的是equals()方法
- 存放在一个集合中的元素,需要重写equals()方法,
- equals()方法比较的是内容,“==”比较的是地址
七、List集合(接口)
-
List集合是一个接口,继承自Collection,需要通过实现类来进行操作。
-
特点:集合中的元素“有序可重复”(存进去的元素顺序与取出来的元素顺序一致,且元素可以重复)
1、List接口特有方法
boolean add(E e,int index) | 向列表中指定位置添加元素 |
Object get(int index) | 获取类表中指定位置的元素 |
int indexOf(Object o) | 获取列表中第一次出现元素的索引 |
int lastIndeOxf(Object o) | 获取列表中出现元素最后的位置 |
Object remove(int index) | 删除指定位置元素 |
Object set(int index,Object element) | 用指定元素替换列表中的指定位置的元素 |
2、List接口中实现类
2.1 、ArrayList集合
- ArrayList集合底层为一个Object[ ]数组,非线性安全;(数组优点,查找(检索)元素效率较高;数组缺点:随机增删元素效率较低,末尾增删元素效率不受影响)
- ArrayList集合在未添加元素之前,底层数组长度为0,添加第一个元素时,数组会进行扩容,默认初始化容量为10;
- 扩容为原容量的1.5倍,建议给定一个初始化容量,减少数组的扩容次数(ArrayList集合优化策略)
2.2、LinkedList集合
- LinkedList集合底层是双向链表形式,非线性安全;(链表优点:随机增删效率较高,因为实则不能增删元素需要位移大量元素;链表缺点:查找元素效率较低,因为每次查找都要从头结点开始)
2.3、Vector 集合
- Vetor集合底层为一个数组,初始化容量为0,添加第一个元素时默认初始化容量为10,扩容之后是原容量的2倍;
- Vector中所有的方法都是线程同步的,都带有synchronized关键字,是线程安全的,效率较低,使用较少;
- 将一个非线程安全的集合转换成线程安全的集合:
-
使用集合工具类:java.util.Collections; java.util.Collection; // 集合接口 java.util.Collections; // 集合工具类 Collections.synchronizedList(非线性安全集合); // 将集合转换为线程安全的集合
八、Set集合(接口)
-
Set集合也是一个接口,继承自Collection,与List类似,都需要通过实现类来进行操作;
-
特点:“无序不可重复”(存进去的元素与取出来的元素顺序不一样),且元素没有下标;(不能使用for循环进行遍历)
1、HashSet集合
- HashSet底层是:数组+链表+红黑树的结构
- Hashset集合的无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的
- 不可重复性:保证添加的元素按照equals()判断时,不能返回true.。即:相同的元素只能添加一个,是通过equlas和hashcode值共同来判断是否重复,如果都返回true则是重复元素;
- HashSet扩容:底层也是数组,初始容量为16,当如果使用率超过0.75(16*0.75=12),就会扩大容量为原来的2倍。(16扩容为32,依次为64,128等)
2、TreeSet集合
- TreeSet集合底层实际上是一个TreeMap集合,往TreeSet集合中添加元素时实际上将数据放入到TreeMap集合中的Key部分去了。TreeMap集合底层采用了二叉树结构;
- 由于TreeSet集合继承于接口SoretSet,及继承与Set接口中元素存储的特点,无序不可重复,但是存放中的元素可以自动进行排序;
- 对于自定义的类无法排序,因为类中对象之间没有比较规则,不知道谁大谁小。
(1)、对于自定义的类无法排序原因,举例说明:
/*
对于自定义的类型TreeSet可以自动排序吗?
以下程序对于Person类型来说,无法排序。因为没有指定Person对象之间的比较规则
谁打谁小并没有说明
以下程序运行的时候出现了这个异常:类型转换异常
java.lang.ClassCastException: com.javase.TreeSet.Person
cannot be cast to java.lang.Comparable
出现这个异常的原因是:
Person类没有实现java.lang.Comparable接口
*/
public class TreeSetTest03 {
public static void main(String[] args) {
Person p1 = new Person(31);
// System.out.println(p1);
Person p2 = new Person(20);
Person p3 = new Person(30);
Person p4 = new Person(11);
TreeSet<Person> persons = new TreeSet<>();
persons.add(p1);
persons.add(p2);
persons.add(p3);
persons.add(p4);
// 遍历
for (Person s : persons){
System.out.println(s);
}
}
}
class Person {
int age;
public Person(int age) {
this.age = age;
}
// 重写toString
public String toString(){
return "Person[sge=" + age + "]";
}
}
(2)、TreeSet集合元素可排序第一种方式:
- 需要继承Comparator接口,举例:
public class TreeSetTest04 {
public static void main(String[] args) {
Customer c1 = new Customer(31);
// System.out.println(p1);
Customer c2 = new Customer(20);
Customer c3 = new Customer(30);
Customer c4 = new Customer(11);
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);
}
}
}
// 放在TreeeSet集合中的元素需要实现java.long.Comparable接口
// 并且实现compareTo方法,equals可以不写
class Customer implements Comparable<Customer>{
int age;
public Customer(int age) {
this.age = age;
}
public String toString(){
return "age=" + age ;
}
/* 需要在这个方法中编写比较的逻辑,或者说比较的规则,按照什么进行比较
k.compareT0(t.key)
拿着参数k和集合中的每一个元k进行比较,返回值可能是>0 <0 =0;
比较器最终由程序员自己指定:例如按照年龄升序,或者按照年龄降序*/
@Override
public int compareTo(Customer c) {// c1.compareTo(c2);
// this 是c;
// 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 age1 - age2; // >0 <0 =0
}
}
/* 结果:
age=11
age=20
age=30
age=31
*/
注意:
(1)、 放在TreeeSet集合中的元素需要实现java.long.Comparable接口
(2)、并且实现compareTo()方法,equals可以不写
- TreeSet集合中元素可排序的第二种方式:使用比较器的方式
public class TreeSetTest06 {
public static void main(String[] args) {
// 创建TreeSet集合的时候,需要使用这个比较器
// TreeSet<WuGui> WUGUI = 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(234));
wuGuis.add(new WuGui(245));
wuGuis.add(new WuGui(645));
wuGuis.add(new WuGui(4213));
// 排序
for(WuGui w : wuGuis){
System.out.println(w);
}
}
}
class WuGui{
int age;
public WuGui(int age){
this.age = age;
}
@Override
public String toString() {
return "小乌龟{" +
"age=" + age +
'}';
}
}
(3)、最终结论:
- 放到TreeSet或者集合TreeMap集合key部分的元素要想做到排序,包括两种方式:
- 第一种:放在集合中的元素实现java.lang.Comparable接口
- 第二种:在构造TreeSet或者TreeMap集合的时候给他传一个比较器对象(比较器实现java.util.Comparator接口)
(4)、Comparable和Comparator怎么选择呢?
- 当比较规则不会发生改变的时候,或者说当比较器规则只有一个的时候,建议实现Comparable接口
- 如果比较规则有多个的时候,并且需要多个比较规则之间频繁切换,建议使用comparator接口
- Comparator接口的设计符合OCP原则
九、Map集合(接口)
- Map集合为双列集合:以键值对方式存储数据
- 双列集合:
(1)、interface Map<K,V> : K(key)键 ,V(value)值
(2)、将键映射到值的对象,不能出现重复的键,每个键最多可以映射到一个值
注:
- Map和Collection没有继承关系;
- Map集合以key和value的方式存储数据:键值对方式;
- key和value都是引用数据类型;
- key和value都是存储对象的内存地址;
- key起到主导的地位,value是key的一个附属品;
例如:
学号(key) | 姓名(value) |
Stu001 | 张三 |
Stu002 | 李四 |
Stu003 | 王五 |
1、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 |
V remove(Object key) | 通过Key删除键值对 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 获取集合中键值对个数 |
Collection<V> value() | 获取集合中所有的value,返回一个Collection集合 |
Set<K> keySet() | 获取Map集合中所有的Key(所有的键是一个Set集合) |
Set<Map.Entry<k,values>> entrySet() | 将Map集合接转换为Set集合 |
注:Map.Entry -> 静态内部类:Map中的Entry方法
以上部分方法详解
(1)、Set<Map.Entry<k,values>> entrySet() 将Map集合接转换为Set集合
Map<Integer, String> map = new HashMap<>();
map.put(01, "zhangsan");
map.put(02, "lisi");
map.put(03, "wangmazi");
map.put(04, "zhaoliu");
map.put(04, "king"); // key重复的时候value会自动覆盖
// 遍历map集合(无序不可重复)
// 方法一:
Set<Map.Entry<Integer, String>> set = map.entrySet(); // 将Map集合转化为Set集合
for (Map.Entry<Integer, String> entry : set) { // 进行遍历
System.out.println(entry.getKey() + "=" + entry.getValue());
}
/* 结果:
1=zhangsan
2=lisi
3=wangmazi
4=king
*/
// 方法二:
Iterator<Map.Entry<Integer, String>> node = set.iterator(); // 迭代器
while (it.hasNext()) {
Map.Entry<Integer, String> node = it.next();
Integer key = node.getKey();
String valus = node.getValue();
System.out.println(key + "=" + valus);
}
/* 结果:
1=zhangsan
2=lisi
3=wangmazi
4=king
*/
2、HashMap集合
- HashMao集合底层是哈希表/散列表的数据结构
- 数组 + 链表 + 红黑树实现的一种数据结构(在JDK8之后,如果哈希表中元素超过8个牡丹香链表这种数据结构会变成红黑树数据结构,当红黑树上的节点数量小于6时没回重新把红黑树树变成单向链表数据结构)
- 哈希表将以上两种数据结构融合在一起,充分发挥他们各自的优点
-
HashMap集合底层的源代码:
public class HashMap{
Node<K,V>[] table; // HashMap底层实际上就是一个数组。(一维数组)
static class Node<K,V>{// 静态的内部类HashMao.Node
final int hash; // 哈西值 (哈西值是Key的hashCode()方法的执行结果。hash值通过哈希函数/算法,可以转换成数组的下标)
final K key; // 存储在Map集合中的那个Key
V value; // 存储在Map集合中的那个value
Node<K,V> next; // 下一个节点的内存地址
}
}
// 哈希表/散列表:一维数组,这个数组中的第一个元素是一个单项列变(数组和列表的结合体)
- HashMap集合Key部分特点:
无序不可重复复
无序: 因为不一定挂在哪个单项列表上
不可重复: equals方法来保证HashMap集合的key不可重复,如果key重复了,value会覆盖
- HashMap集合的默认初始化容量是16,默认加载因子是0.75(当HashMap集合底层数组容量达到75%的时候,数组开始扩容)
- 重点,HashMap集合的初始化容量必须是2的倍数,这也是官方推荐的。这是因为达到散列均匀,为了提高HashMap集合的存储效率,所必需的
- 重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode() + equals()方法 (具体原因待究)
- HashMap集合遍历(含两种方法):
public class CollectionTest01 { public static void main(String[] args) { // 创建HashMap集合 Map<Integer,String> map = new HashMap<>(); // 添加元素 map.put(1,"zhangsan"); map.put(2,"lisi"); map.put(3,"wangwu"); map.put(4,"zhaoliu"); map.put(4,"king"); map.put(6,"main"); // 获取元素个数 System.out.println(map.size()); // 5 // 取key是2的元素 System.out.println(map.get(2)); // lisi // 遍历Map集合 // 第一种方式,获取所有的key,通过key获取value Set<Integer> set = map.keySet(); for (Integer key:set) { System.out.println(key + "=" + map.get(key)); } /* 结果: 1=zhangsan 2=lisi 3=wangwu 4=king 6=main */ // 第二种方式:将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()); } /* 结果: 1=zhangsan 2=lisi 3=wangwu 4=king 6=main */ } }
3、Properties集合
- Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型。 Properties被称为属性类对象。 Properties是线程安全的。
- 这里不做详解,简单了解即可。直接上代码:
public class PropertiesTest01 {
public static void main(String[] args) {
// 创建一个properties对象
Properties pro = new Properties();
// 需要掌握Properties中两个方法:存、取
pro.setProperty("url", "jdbc:mysql//localhost:3350/bjpowernode");
pro.setProperty("driver","co.mysql.jdbc.Driver");
pro.setProperty("username", "root");
pro.setProperty("psaaword", "123");
// 通过key获取value
String ur1 = pro.getProperty("url");
String driver = pro.getProperty("driver");
String username = pro.getProperty("username");
String password = pro.getProperty("password");
System.out.println(ur1);
System.out.println(driver);
System.out.println(username);
System.out.println(password);
}
}
/* 结果:
jdbc:mysql//localhost:3350/bjpowernode
co.mysql.jdbc.Driver
root
null
*/
4、TreeMap集合
- TreeSet集合底层创建了一个TreeMap集合,底层是一个二叉树;
- 放到TreeSet集合中的元素吗等同于放到TreeMap集合中的key部分;
- 放到TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序,称为:可排序集合;
- 具体详解已在上述TreeSet集合中说明;
十、总结(可忽略)
- 每个集合的创建(new)
- 向集合中添加元素
- 从集合中取出某个元素
- 遍历集合
- 主要的集合类
(1)、ArrayList
(2)、LinkedList
(3)、HashSet(HashSet的key,存储在HashMap集合key的元素需要同时重写hashCode + equals)
(4)、Properties
(5)、TreeMap