集合即容器!
开发和学习中需要时刻和数据打交道,如何组织这些数据是我们编程中重要的内容。我们一般通过“容器”来容纳和管理数据。事实上,我们前面所学的数组就是一种容器,可以在其中放置对象或基本类型数据。
1. 数组
数组的优势:是一种简单的线性序列,可以快速地访问数组元素,效率高。如果从效率和类型检查的角度讲,数组是最好的。
数组的劣势:不灵活。容量需要事先定义好,不能随着需求的变化而扩容。比如:我们在一个用户管理系统中,要把今天注册的所有用户取出来,那这样的用户有多少个?在写程序时是无法确定的。因此,在这里就不能使用组。
==> 基于数组并不能满足我们对于“管理和组织数据的需求”,所以我们需要一种更强大、更灵活、容量随时可扩的容器来装载我们的对象。 这就是我们今天要学习的容器。容器(Collection)也称之为集合。
集合:
存放对象的容器
1. 只能用于存放对象
Java中每一种基本数据类型都有对应引用类型
2. 集合存放的是多个对象的引用,对象本身还是存放在堆中
3. 集合可以存放不同类型,不限数量的数据类型
2. 容器的结构
2.1 结构图
单例集合: 将数据一个一个的进行存储。
双例集合:基于Key与Value的结构存储数据。
3. Collection接口
Collection是单例集合类的顶级接口。它提供对集合对象进行基本操作的通用接口方法。它的直接继承接口有List,Set和Queue.
Collection 接口中的抽象方法
方法 | 说明 |
---|---|
boolean add(Object element) | 增加元素到容器中 |
boolean remove(Object element) | 从容器中移除元素 |
boolean contains(Object element) | 容器中是否包含该元素 |
int size | 容器中元素的数量 |
boolean isEmpty() | 容器是否为空 |
void clear | 清空容器中所有元素 |
Iterator iterator() | 获得迭代器,用于遍历所有元素 |
boolean containsAll(Collection c) | 本容器是否包含c容器中的所有元素 |
boolean addAll(Collection c) | 将容器c中所有元素增加到本容器 |
boolean removeAll(Collection c) | 移除本容器和容器c中都包含的元素 |
boolean retainAll(Collection c) | 取本容器和容器中都包含的元素,移除非交集元素 |
Object[] toArray() | 转化成Object 数组 |
由于List、Set是Collection的子接口,意味着所有List、Set的实现类都有上面的方法。
4. List 接口介绍
4.1 List 的特点
- 有序:有序(元素存入集合的顺序和取出的顺序一致)。List中每个元素都有索引标记。可以根据元素的索引标记(在List中的位置)访问元素,从而精确控制这些元素。
- 可重复:List允许加入重复的元素。更确切地讲,List通常允许满足e1.equals(e2) 的元素重复加入容器。
4.1 List 的常用方法
除了Collection接口中的方法,List多了一些跟顺序(索引)有关的方法,参见下表:
方法 | 说明 |
---|---|
void add (int index, Object element) | 在指定位置插入元素,以前元素全部后移一位 |
Object set (int index,Object element) | 修改指定位置的元素 |
Object get (int index) | 返回指定位置的元素 |
Object remove (int index) | 删除指定位置的元素,后面元素全部前移一位 |
int indexOf (Object o) | 返回第一个匹配元素的索引,如果没有该元素,返回-1. |
int lastindexOf (Object o) | 返回最后一个匹配元素的索引,如果没有该元素,返回-1 |
4.3 ArrayList 容器类
ArrayList 是 List 接口的实现类。是 List 存储特征的具体实现。所以能够实现Collection和List接口的方法。 ArrayList底层是用数组实现的存储。
特点:查询效率高,增删效率低,线程不安全!
Vector 线程是安全的(不推荐使用)
package day04;
import java.util.ArrayList;
import java.util.List;
public class Test3 {
public static void main(String[] args) {
//直接new一个ArrayList类
List<String> list = new ArrayList<String>();
//往容器添加元素
list.add("5");
list.add("2");
list.add("0");
System.out.println(list.get(2));//允许使用重复元素,可通过索引来访问指定位置的元素
//默认按元素添加位置设置元素索引
//按索引在指定位置添加:list.add(0, "6");
list.add(0, "6");
System.out.println(list);
List<String> l = new ArrayList<String>();
l.add("123");
l.add("456");
//插入集合
list.addAll(l);
//指定位置插入集合:list.addAll(2, l);
list.addAll(2, l);
System.out.println(list);
System.out.println(list.indexOf("5"));//第一个下标:list.indexOf("5")
System.out.println(list.lastIndexOf("5"));//最后一个下标:list.lastIndexOf("5")
list.set(1, "你好");//修改指定元素:list.set(1, "你好");
System.out.println(list);
//截取指定位置元素:List<String> sub = list.subList(1, 2);
List<String> sub = list.subList(1, 2);
System.out.println(sub);
System.out.println(list.size());//指定数组的长度
}
}
4.5 Vector 容器类
- Vector底层是用数组实现的,相关的方法都加了同步检查,因此“线程安全效率低”比如,indexOf方法就增加了synchronized 同步标记。
- Vector的使用与Arraylist是相同的,因为他们都实现了List 接口,对 List 接口中的抽象方法做了具体实现。
4.5.1.Stack 容器
- Stack栈容器是Vector的一个子类它实现了一个标准的后进先出(LIFO:Last In Frist Out)的栈。
- Stack 特点是后进先出。它通过5个操作方法对 Vector进行扩展,允许将向量视为堆栈。
4.6 LinkedList 容器类
LinkedList底层用双向链表实现的存储。特点:查询效率低,增删效率高,线程不安全。
双向链表也叫双链表,是链表的一种,它的每个数据节点中都有两个指针,分别指向前一个节点和后一个节点。 所以,从双向链表中的任意一个节点开始,都可以很方便地找到所有节点。
class Node<E>{
E item;
Node<E> next;
Node<E> prev;
}//定义双向链表
LinkedList的使用(非 List标准)
专门用来操作双向链表
方法 | 说明 |
---|---|
void addFirst(E e) | 将指定元素插入到开头 |
void addLast(E e) | 将指定元素插入到结尾 |
getFirst() | 返回此列表的第一个元素 |
getLas() | 返回此列表的最后一个元素 |
removeFirst() | 移除此列表中的第一个元素,并返回这个元素 |
removeLast() | 移除此列表中的最后一个元素,并返回这个元素 |
E pop() | 从此列表所表示的堆栈处弹出一个元素,等效于 removeFirst |
void push(E e) | 将元素推入此列表所表示的堆栈这个等效于addFisrt(E e) |
boolean isEmpty() | 判断列表是否包含元素,如果不包含元素则返回true |
5. Set 接口介绍
Set接口继承自Collection ,Set 接口中没有新增方法,方法和 Collection 保持完全一致。
5.1 Set 接口特点
Set特点:无序不可重复。无序指Set中的元素没有索引,我们只能遍历查找;不可
重复指不允许加入重复的元素。更确切地讲,新元素如果和Set中某个元素通过equals()
方法对比为true,则只能保留一个。
Set常用的实现类有:HashSet、TreeSet等,我们一般使用HashSet。
5.2 HashSet存储特征分析
HashSet是一个不保证元素的顺序且没有重复元素的集合,是线程不安全的。HashSet允许有null元素。
无序:
在HashSet中底层是使用HashMap存储元素的。HashMap 底层使用的是数组与链表实现元素的存储。元素在数组中存放时,并不是有序存放的也不是随机存放的,而是对元素的哈希值进行运算决定元素在数组中的位置
不重复:
当两个元素的哈希值进行运算后得到相同的在数组中的位置时,会调用元素的equals()方法判断两个元素是否相同。如果元素相同则不会添加该元素,如果不相同则会使用单向链表保存该元素。
总结就是以下几点:
- 不能保证元素的排列顺序
- 调用hashcode()获得hashcode值来确定在集合的位置
-
不可重复
指的是hashcode()返回的值不相同
- 不是线程安全的
- 集合元素可以使用null
-
判断元素相等的标准
equals()方法比较相等,并且两个对象的hashCode()方法返回值也相等。
如果两个对象通过equals()方法返回true,这两个对象的 hashCode 值也应该相同。
5.3 TreeSet 容器类
TreeSet是一个可以对元素进行排序的容器。底层实际是用 TreeMap 实现的,内部维
持了一个简化版的TreeMap通过key来存储Set的元素。TreeSet内部需要对存储的元
素进行排序,因此,我们需要给定排序规则。
排序规则实现方式:
==>通过元素自身实现比较规则。
==>通过比较器指定比较规则。
//自然排序 排数字:Set<Integer> set = new TreeSet<Integer>();
// 比较器指定:对人按年龄排序:Set<Person> e = new TreeSet<Person>(new Person());
package day04;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
import sun.reflect.generics.tree.Tree;
public class Test2 {
public static void main(String[] args) {
// Set<Integer> set = new TreeSet<Integer>();
// set.add(1);
// set.add(6);
// set.add(2);
// set.add(3);
//
// //迭代输出
// Iterator<Integer> it = set.iterator();
// while(it.hasNext()) {
// System.out.println(it.next());
// }
Person p1 = new Person("张三",18);
Person p2 = new Person("李四",15);
Person p3 = new Person("王五",38);
Person p4 = new Person("梁六",26);
Person p5 = new Person("何七",21);
Set<Person> e = new TreeSet<Person>(new Person());
e.add(p1);
e.add(p2);
e.add(p3);
e.add(p4);
e.add(p5);
//迭代输出
for(Person p :e) {
System.out.println("姓名:" + p.name + " " + "年龄:" + p.age );
}
}
}
//通过比较器比较要实现Comparator接口
class Person implements Comparator<Person>{
int age;
String name;
public Person() {
}
public Person(String name,int age) {
this.name = name;
this.age = age;
}
@Override
public int compare(Person args0, Person args1) {
if(args0.age < args1.age) {
return 1;
}else if(args0.age > args1.age) {
return -1;
}else {
return 0;
}
}
}
6. Map 接口介绍
Map接口定义了双例集合的存储特征,它并不是 Collection 接口的子接口。双例集合的存储特征是以key与value结构为单位进行存储。体现的是数学中的函数y=f(x)感念。
Map 与 Collecton 的区别:
- Collection中的容器,元素是孤立存在的(理解为单身),向集合中存储元素采用一个个元素的方式存储。
- Map中的容器,元素是成对存在的(理解为现代社会的夫妻)。每个元素由键与值两部分组成,通过键可以找对所对应的值。
- Collection中的容器称为单列集合,Map中的容器称为双列集合。
- Map中的集合不能包含重复的键,值可以重复;每个键只能对应一个值。
- Map 中常用的容器为 HashMap,TreeMap等。
6.1 Map的常用方法
方法 | 说明 |
---|---|
V put (K key, V value) | 把key 与 value 添加到Map集合中 |
void putll(Map m) | 从指定Map中将所有映射关系复制到此Map中 |
V remove (Object key) | 删除key 对应的 value |
V get(Object key) | 根据指定的key,获取对应的value |
boolean containsKey(Object key) | 判断容器中是否包含指定的key |
boolean containsValue(Object value) | 判断容器中是否包含指定的value |
Set keySet() | 获取Map集合中所有的key,存储到Set集合中 |
Set<Map.Entry<K,V>>entrySet() | 返回一个Set基于Map.Entry类型包含Map中所 有映射。 |
void clear() | 删除Map中所有的映射 |
6.2 HashMap容器类
HashMap是Map接口的接口实现类,它采用哈希算法实现,是 Map 接口最常用的实现类。由于底层采用了哈希表存储数据,所以要求键不能重复,如果发生重复,新的值会替换旧的值。 HashMap在查找删除修改方面都有非常高的效率。
package day04;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
//import java.util.TreeMap;
/**
*注解部分是另外一个举例
*/
public class Test4 {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("a", 5);//添加元素
map.put("b", 2);
map.put("c", 0);
System.out.println(map);
System.out.println(map.get("b"));//根据Key取值
map.remove("c");//移除
System.out.println(map);
System.out.println(map.size());//长度
System.out.println(map.containsKey("b"));//判断集合是否包含指定的Key
System.out.println(map.containsValue(5));//判断集合是否包含指定的Value
//①通过map.keySet()遍历集合
Set<String> keys = map.keySet();//获取集合所有的Key值
map.values();//获取集合所有Value值
//遍历集合
for(String key : keys ) {
System.out.println("Key:" + key + ",values:" + map.get(key));
}
//②通过map.entrySet()遍历
Set<Entry<String, Integer>> entrys = map.entrySet();
//遍历
for(Entry<String, Integer> en : entrys) {
System.out.println("Key:" + en.getKey() + ",values:" + en.getValue());
}
}
}
6.3 TreeMap 容器类
TreeMap 和 HashMap 同样实现了 Map 接口,所以,对于 API 的用法来说是没有区别的。HashMap效率高于TreeMap;TreeMap是可以对键进行排序的一种容器,在需要对键排序时可选用TreeMap。TreeMap底层是基于红黑树实现的。
在使用TreeMap时需要给定排序规则:
元素自身实现比较规则。
通过比较器实现比较规则。
public class Test5 {
public static void main(String[] args) {
Map<Integer,String> map = new TreeMap<Integer, String>();//数字排序
map.put(2, "2");
map.put(1, "5");
map.put(6, "0");
System.out.println(map);
Map<String,String> map1 = new TreeMap<String, String>();//字符串自然排序:字典排序
}
}
7. Iterator 迭代器
7.1 Iterator 迭代器接口介绍
Collection接口继承了Iterable接口,在该接口中包含一个名为iterator的抽象方法,所有实现了Collection接口的容器类对该方法做了具体实现。iterator方法会返回一个Iterator接口类型的迭代器对象,在该对象中包含了三个方法用于实现对单例容器的迭代处理
7.2 Iterator接口方法
booleanhasNext();//判断游标当前位置是否有元素,如果有返回true,否则返回false;
Object next(); //获取当前游标所在位置的元素,并将游标移动到下一个位置
void remove(); //删除游标当前位置的元素,在执行完next后该操作只能执行一次;
7.3 集合的遍历
//迭代器的举例
Iterator<Object> it = set.iterator();
//用while迭代
while(it.hasNext())
{System.out.println(it.next());}
//用for each迭代
for(Object obj : set)
{//把set每一个值取出来,赋值给obj,直到循环set的所有值
System.out.println(obj);
}
System.out.println(set.size());//输出数组长度:
8 操作集合的工具Collections工具类
Collections是一个工具类,它提供了对SetListMap进行排序、填充、查找元素的辅助方法。该类中所有的方法都为静态方法。
8.1 常用方法
常用方法 | 作用 |
---|---|
void sort(List) | 对List容器内的元素排序,排序的规则是按照升序进行排序 |
void shuffle(List) | 对List容器内的元素进行随机排列。 |
void reverse(List) | 对 List 容器内的元素进行逆续排列 |
void fill(List Object) | 用一个特定的对象重写整个List 容器。 |
int binarySearch(List Object) | 对于顺序的List容器,采用折半查找的方法查找特定对象 |
8.2 举例
package day04;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
public class Test6 {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("i love you");
list.add("Me too!");
list.add("really?");
list.add("yes!");
list.add("yes!");
System.out.println(list);
//用来操作集合
Collections.reverse(list);//反转元素
System.out.println(list);
Collections.shuffle(list);//随机排序
System.out.println(list);
Collections.sort(list);//自然顺序按升序排列
System.out.println(list);
//查找指定集合中元素出现的次数
System.out.println(Collections.frequency(list, "yes!"));
//替换
Collections.replaceAll(list, "yes!", "of course!");
System.out.println(list);
Collections.swap(list, 1,2 );//指定元素交换
System.out.println(list);
System.out.println(Collections.max(list));//元素自然顺序找最大最小值
System.out.println(Collections.min(list));
/**
*下面的是另一个举例
*/
Students s1 = new Students(14,"张三");
Students s2 = new Students(18,"李四");
Students s3 = new Students(12,"王五");
List<Students> stus = new ArrayList<Students>();
stus.add(s1);
stus.add(s2);
stus.add(s3);
//按Comparator中指定顺序返回元素
//第一个参数是要排序的集合。第二个是对象
Students s = Collections.max(stus, new Students());
System.out.println(s.name + s.age);
for(Students stu : stus) {
System.out.println("姓名" + stu.name + " " + "年龄" + stu.age);
}
System.out.println("-------------------");
Collections.sort(stus, new Students());
for(Students stu : stus) {
System.out.println("姓名" + stu.name + " " + "年龄" + stu.age);
}
}
}
//创键一个student类
class Students implements Comparator<Students>{
int age;
String name;
public Students () {
}
public Students(int age,String name) {
this.age = age;
this.name = name;
}
@Override//重写方法比较
public int compare(Students arg0, Students arg1) {
if(arg0.age > arg1.age) {
return 1;
}else if(arg0.age < arg1.age) {
return -1;
}else {
return 0;
}
}
}
最后:同步控制
Collections类中提供了多个synchronizedXxx()方法,该方法可使将指定集合包装成线程同步的集合,从而可以解决多线程并发访问集合时的线程安全问题。
发际线不保,再见!