一、集合概述
java.util.*;包下
1、什么是集合
集合实际上就就是一个容器,可以来容纳其他类型的数据,例如数组就是一个容器,一个集合
集合在开发中使用较多,可以一次容纳多个对象,
注意:集合不能直接存储基本数据类型,另外集合也不能直接存储java对象,集合当中存储的都是java对象的内存地址。(或者说集合中存储的是引用。) 任何时候存储的都是引用
2、Java中不同的集合,底层对应不同的数据结构
往不同的集合中存储元素,等于将数据放到不同的数据结构当中
3、Java中集合分为两大类
1)一类为单个方式存储元素:
单个方式存储元素,这一类集合中超级父接口:java. util . Collection;
2)一类为以键值对的方式存储元素:
以键值对的方式存储元素,这一类集合中超级父接口:java. util .Map;
4、集合继承结构图(其中的类为常用的一些) 单个方式存储
5、Map集合继承结构图(其中的类是常用的一些) 键值对方式存储
二、单个方式存储元素的集合
1、java.util.Collection接口中常用方法
a)boolean add(Object e)向集合中添加元素
// 因为接口无法实例化,用ArrayList做例子
Collection c = new ArrayList();
c.add(1200); // 自动装箱
c.add(3.14);
c.add(new Object());
c.add(new Student());
c.add(true); // 自动装箱
b)int size()获取集合中元素的个数
注意:在迭代集合过程中,不能调用集合对象的remove方法,删除元素,改变了基本结构,会出现异常
c.size();
c)void clear();清空集合
c.clear();
d)boolean contains(Object o);集合中是否包含某一个元素
c.add("hello");
c.add("world");
c.add(1);
boolean flag = c.contains("hello");
System.out.println(flag); // true
// 底层调用的是equal方法
e)boolean remove();删除集合中某个元素
c.remove(1);
c.remove("hello");
// 底层调用了equal方法
f)boolean isEmpty();判断该集合中元素个数是否为0
System.out.println(c.isEmpty()); // false
g)Object[] toArray() 调用这个方法可以把集合转换成数组
Object[] objs = c.toArray();
for(int i = 0; i < objs.length; i++){
Object o = objs[i];
System.out.println(o);
}
h)集合迭代Iterator迭代器(重点)
hasNext、next、remove
注意:迭代器刚开始并没有指向第一个元素。这个迭代器在Collection中是通用的,Map中不能用
一定注意:集合结构只要发生改变,迭代器也必须重新获取
在迭代集合过程中,不能调用集合对象的remove方法,但是迭代器有一个remove()方法,可以使用迭代器的方法
迭代器对象:获取迭代器对象,迭代器用来遍历集合,此时相当于对当前集合的状态拍了一个快照
迭代器去删除的时候,会自动更新迭代器,并且更新集合
public class CollectionTest02 {
public static void main(String[] args) {
// 注意:以下讲解的遍历方式/迭代方式,是所有Collection通用的一种方式。
// 在Map集合中不能用。在所有的Collection以及子类中使用。
// 创建集合对象
Collection c = new ArrayList();
// 添加元素
c.add("abc");
c.add("def");
c.add(100);
c.add(new Object());
// 对集合Collection进行遍历/迭代
// 第一步:获取集合对象的迭代器对象Iterator
Iterator it = c.iterator();
// 第二步:通过以上获取的迭代器对象开始迭代/遍历集合。
/*
以下两个方法是迭代器对象Iterator中的方法:
boolean hasNext()如果仍有元素可以迭代,则返回 true。
Object next() 返回迭代的下一个元素。
*/
while(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
/*
while(it.hasNext()){
Object obj = it.next();
it.remove();
System.out.println(obj);
}
*/
}
}
2、List接口中特有的常用方法
1)void add(int index, Object element)在列表的指定位置插入指定元素
2)Object set(int index, Object element)修改指定位置的元素
3)Object get(int index)根据下标获取元素
4)int indexOf(Object o)获取指定对象第一次出现处的索引
5)int lastIndexOf(Object o) 获取指定对象最后一次出现处的索引
6)Object remove(int index)删除指定下标位置的元素
public class ListTest01 {
public static void main(String[] args) {
// 创建List类型的集合。
List myList = new ArrayList();
// 添加元素
myList.add("A"); // 默认都是向集合末尾添加元素。
myList.add("C");
myList.add("D");
//在列表的指定位置插入指定元素(第一个参数是下标)
myList.add(1, "sss");
// 根据下标获取元素
Object firstObj = myList.get(0);
System.out.println(firstObj);
// 因为有下标,所以List集合有自己比较特殊的遍历方式
// 通过下标遍历。List集合特有的方式,Set没有。
for(int i = 0; i < myList.size(); i++){
Object obj = myList.get(i);
System.out.println(obj);
}
// 获取指定对象第一次出现处的索引。
System.out.println(myList.indexOf("C"));
// 获取指定对象最后一次出现处的索引。
System.out.println(myList.lastIndexOf("C"));
// 删除指定下标位置的元素
// 删除下标为0的元素
myList.remove(0);
// 修改指定位置的元素
myList.set(2, "srr");
// 遍历集合
for(int i = 0; i < myList.size(); i++){
Object obj = myList.get(i);
System.out.println(obj);
}
}
}
3、ArrayList类常用方法
1)初始化容量为10,底层是Object类型的数组
2)构造方法
new ArrayList();初始化 容量10
new ArrayList(20);指定初始化容量
3)ArrayList集合的扩容
增长到原容量的1.5倍
public class ArrayListTest01 {
public static void main(String[] args) {
// 默认初始化容量是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);
System.out.println(list1.size());
// 再加一个元素
list1.add(11);
System.out.println(list1.size());
}
}
4、链表
对于链表数据结构来说,基本的单元是节点Node
1)单向链表
a)对于单向链表来说
每个节点都有Node都有两个属性,一个属性:存储的数据,另一个属性:是下一个节点的内存地址
b)链表优点
随机增删元素效率较高(增删元素不涉及到大量元素位移)
c)链表缺点
查询效率较低,每一次查找某个元素的时候都需要从头节点开始往下遍历
2)双向链表
a)对于双向链表来说
每个节点都有Node都有三个属性,一个属性:前一个节点的内存地址,中间属性:存储的数据,另一个属性:是下一个节点的内存地址
b)链表优点
由于链表上的元素在空间存储上内存地址不连续。所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用LinkedList
c)链表缺点
不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,直到找到为止。所以 LinkedList集合检索/查找的效率较低
ArrayList:把检索发挥到极致(末尾添加元素效率还是很高的。)
LinkedList:把随机增删发挥到极致
加元素都是往末尾添加,所以ArrayList用的比LinkedList多
3)LinkedList常用方法
和ArrayList差不多,
5、Vector
1)
底层是一个数组
2)初始化容量
10
3)扩容
扩容之后是原来的两倍
4)将一个线程不安全的ArrayList集合转换为线程安全的
使用集合工具类:java.util.Collections;
List myList = new ArrayList(); // 非线程安全的
// 变成线程安全的
Collections.synchronizedList(myList);
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(11);
Iterator it = vector.iterator();
while(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
}
}
6、HashSet
public class HashSetTest01 {
public static void main(String[] args) {
Set<String> strs = new HashSet<>();
strs.add("hello3");
strs.add("hello4");
strs.add("hello1");
strs.add("hello2");
strs.add("hello3");
strs.add("hello3");
// 放到HashSet集合中的元素实际上是放到HashMap集合的key部分了。
for(String s : strs){
System.out.println(s);
}
}
}
7、TreeSet
public class TreeSetTest01 {
public static void main(String[] args) {
// 创建集合对象
Set<String> strs = new TreeSet<>();
// 添加元素
strs.add("A");
strs.add("B");
strs.add("Z");
strs.add("Y");
strs.add("Z");
strs.add("K");
strs.add("M");
// 遍历
/*
A
B
K
M
Y
Z
从小到大自动排序!
*/
for(String s : strs){
System.out.println(s);
}
}
}
三、以键值对的方式存储元素
Map和Collection没有继承关系
1、java.util.Map接口中常用的方法
1)V put(K key, V value) 向Map集合中添加键值对
Map<Integer, String> map = new HashMap<>();
// 向Map集合中添加键值对
map.put(1, "zhangsan"); // 1自动装箱
map.put(2, "lisi");
map.put(3, "wangwu");
2)V get(Object key) 通过key获取value
// 通过key获取value
String value = map.get(2);
System.out.println(value);
3)void clear() 清空Map集合
// 清空map集合
map.clear();
System.out.println("键值对的数量:" + map.size());
4)boolean containsKey(Object key) 判断Map中是否包含某个key
// 判断是否包含某个key
System.out.println(map.containsKey(new Integer(4)));
5)boolean containsValue(Object value) 判断Map中是否包含某个value
// 判断是否包含某个value
System.out.println(map.containsValue(new String("wangwu")));
6)boolean isEmpty() 判断Map集合中元素个数是否为0
// 判断是否为空
System.out.println(map.isEmpty());
7)V remove(Object key) 通过key删除键值对
// 通过key删除key-value
map.remove(2);
8)int size() 获取Map集合中键值对的个数
// 获取键值对的数量
System.out.println("键值对的数量:" + map.size());
9)Collection<V> values() 获取Map集合中所有的value,返回一个Collection
// 获取所有的value
Collection<String> values = map.values();
// foreach
for(String s : values){
System.out.println(s);
}
10)Set<K> keySet() 获取Map集合所有的key(所有的键是一个set集合)
// 获取所有的key,所有的key是一个Set集合
Set<Integer> keys = map.keySet();
11)Set<Map.Entry<K,V>> entrySet()将Map集合转换成Set集合
注意:Map集合通过entrySet()方法转换成的Set集合,Set集合中元素的类型是Map.Entry<K,V>
假设现在有一个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
12)Map集合的遍历
// 在map集合中放入数据
Map<Integer, String> map = new HashMap<>();
map.put(1, "zhangsan");
map.put(2, "lisi");
map.put(3, "wangwu");
map.put(4, "zhaoliu");
a)第一种方式(先获取key,再用key获取value)
// 先获取所有的key,所有的key是一个Set集合
// 然后遍历key获取value
Set<Integer> keys = map.keySet();
// 迭代器获取
/*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));
}
b)第二种方式(先转换成Set集合,然后通过Set集合获取) 推荐使用
// 第二种方式:Set<Map.Entry<K,V>> entrySet()
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());
}
2、HashMap
1)HashMap集合概述
HashMap集合底层是哈希表/散列表的数据结构
哈希表/散列表:一维数组,这个数组中每一个元素是一个单向链表。(数组和链表的结合体。)
2)HashMap数据结构
哈希表是一个数组和单向链表的结合体。数组:在查询方面效率很高,随机增删方面效率很低。单向链表:在随机增删方面效率较高,在查询方面效率很低
哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点
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)HashMap两个方法
-
map.put(k,v)实现原理
-
1:先将k,v封装到Node对象当中
-
2:底层会调用k的hashCode0方法得出hash值,然后通过哈希函数/哈希算法,将hash值转换成数组的下标,
-
如果下标位置上如果没有任何元素,就把Node添加到这个位置上
-
如果说下标对应的位置上有链表,此时会拿着k和链表上每一个节点中的k进行equals ,如果所有的equals方法返回都是false ,那么这个新节点将会被添加到链表的末尾。如果其中有一个equals返回了true,那么这个节点的value将会被覆盖
-
-
-
v = map.get(k)实现原理
-
先调用k的hashCode0方法得出哈希值,通过哈希算法转换成数组下标,通过数组下标快速定位到某个位置
上,
-
如果这个位置上什么也没有,返回null
-
如果这个位置上有单向链表,那么会拿着参数k和单向链表上的每个节点中的k进行equals ,如果所有equals方法返回false ,那么get方法返回null ,只要其中有一个节点的k和参数k equals的时候返回true ,那么此时这个节点的value就是我们要找的value , get方法最终返回这个要找的value
-
-
5)HashMap集合的key的特点:
无序,不可重复。
重点:放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode和equals方法。
注意:同一个单向链表上所有节点的hash相同,因为数组下标都是一样的
6)默认初始化
HashMap集合的默认初始化容量是16,默认加载因子是0.75,这个默认加载因子是当HashMap集合底层数组的容量达到75%的时候,数组开始扩容
重点:HashMap集合初始化容量必须是2的倍数,这也是官方推荐的,
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());
// 遍历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());
}
}
}
7)同时重写hashCode和equal方法
因为在HashMap集合中如果equals方法返回是true,那么hashCode方法返回的值必须一致,equals方法返回true表示两个对象相同,在同一个单向链表上比较的话,对于同一个单向链表上的节点来说,他们的哈希值都是相同的,所以hashCode方法的返回值也是相同的
8)HashMap集合的key值允许为空
// 但是注意:null也是只能有一个
Map map = new HashMap();
map.put(null,null);
map.put(null,"123");
System.out.println(map.get(null));// 123
9)HashMap和Hashtable的区别
a)key和value是否可以为null
Hashtable的key和value都是不能为null的。
HashMap集合的key和value都是可以为null的。
b)Hashtable和HashMap一样,底层都是哈希表数据结构
c)Hashtable默认容量
Hashtable的初始化容量是11,默认加载因子是:0.75f。Hashtable的扩容是:原容量 * 2 + 1
public class HashtableTest01 {
public static void main(String[] args) {
Map map = new Hashtable();
//map.put(null, "123");
map.put(100, null);
}
}
3、Properties属性类
1)Properties概述
Properties是一个Map集合,继承Hashtable,Properties的key和value都是String类型
Properties被称为属性类对象
Properties是线程安全的
2)相关方法
- setProperty
- getProperty
public class PropertiesTest01 {
public static void main(String[] args) {
// 创建一个Properties对象
Properties pro = new Properties();
// 需要掌握Properties的两个方法,一个存,一个取。
pro.setProperty("url", "jdbc:mysql://localhost:3306/nvrg");
pro.setProperty("driver","com.mysql.jdbc.Driver");
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);
System.out.println(driver);
System.out.println(username);
System.out.println(password);
}
}
4、TreeSet
1)TreeSet概述
TreeSet集合底层实际上是一个TreeMap
TreeMap集合底层是一个二叉树
放到TreeSet集合中的元素,等同于放到TreeMap集合key部分了
TreeSet集合中的元素:无序不可重复,但是可以按照元素的大小顺序自动排序。称为:可排序集合
2)TreeSet对自定义的类型是否可以排序
a)没有指定自定义对象之间的比较的规则无法排序
以下程序运行的时候出现了这个异常:
java.lang.ClassCastException:
class com.bjpowernode.javase.collection.Person
cannot be cast to class java.lang.Comparable
出现这个异常的原因是:
Person类没有实现java.lang.Comparable接口
b)自定义比较规则,实现java.lang.Compareble接口
public class TreeSetTest04 {
public static void main(String[] args) {
Customer c1 = new Customer(32);
Customer c2 = new Customer(20);
Customer c3 = new Customer(30);
// 创建TreeSet集合
TreeSet<Customer> customers = new TreeSet<>();
// 添加元素
customers.add(c1);
customers.add(c2);
customers.add(c3);
// 遍历
for (Customer c : customers){
System.out.println(c);
}
}
}
// 放在TreeSet集合中的元素需要实现java.lang.Comparable接口
class Customer implements Comparable<Customer>{
int age;
public Customer(int age){
this.age = age;
}
/* compareTo方法很重要
返回0表示相同,value会覆盖
返回>0,会继续在右子树上找
返回<0,会继续在左子树上找
*/
// 需要在这个方法中编写比较的逻辑,或者说比较的规则,按照什么进行比较
// k.compareTo(t.key)
// 拿着参数k和集合中的每一个k进行比较,返回值可能是>0 <0 =0
// 比较规则最终还是由程序员指定的:例如按照年龄升序或者按照年龄降序
@Override
public int compareTo(Customer c) { // c1.compareTo(c2);
//return this.age - c.age; // =0 >0 <0
return c.age - this.age;
}
public String toString(){
return "Customer[age="+age+"]";
}
}
c)创建比较器,实现java.util.Comparator接口
// 乌龟类
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;
}
}
public class TreeSetTest06 {
public static void main(String[] args) {
// 给构造方法传递一个比较器
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);
}
}
}
四、Collections工具类
// 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集合中元素排序,需要保证List集合中的元素实现了:Comparable接口。
//传入比较器对象也可以Collections.sort(list集合, 比较器对象);
List<WuGui2> wuGuis = new ArrayList<>();
wuGuis.add(new WuGui2(1000));
wuGuis.add(new WuGui2(8000));
wuGuis.add(new WuGui2(500));
Collections.sort(wuGuis);
for(WuGui2 wg : wuGuis){
System.out.println(wg);
}
// 对Set集合排序
// 将Set集合转换成List集合
Set<String> set = new HashSet<>();
set.add("king");
set.add("kingsoft");
set.add("king2");
set.add("king1");
List<String> myList = new ArrayList<>(set);
Collections.sort(myList);
for(String s : myList) {
System.out.println(s);
}