目录
一、集合概念
●举个例子:
存储一个班学员信息,假定一个班容纳20名学员
当我们需要保存一组一样(类型相同)的元素的时候,我们应该使用一个容器来存储,数组就是这样一个容器。
但是数组有缺点,比如:数组一旦定义,长度将不能再变化。
●数组特点:
①一组数据类型相同的元素集合
②创建数组时,必须给定长度, 而且一旦创建长度不能变.
③ 一旦数组装满元素,需要创建一个新的数组,将元素复制过去
●不方便:
①判断是否装满了,如果装满了,数组复制
②如果我们从数组中间删除一个元素或者添加一个元素,需要移动后面的元素.
●两个需求:
①然而在我们的开发实践中,经常需要保存一些变长的数据集合,于是,我们需要一些能够动态增长长度的容器来保存我们的数据。
②我们需要对数据的保存的逻辑可能各种各样,于是就有了各种各样的数据结构。java中对于各种数据结构的实现,就是我们用到的集合。
java中为了解决数据存储单一的情况,就提供了许多不同结构的集合类,让我们可以根据不同的场景进行数据存储选择,提供了数组实现的集合,链表实现的集合,哈希结构,树结构...使用起来就非常方便。
二、集合 API
●集合体系概述
Java的集合框架是由很多接口、抽象类、具体类组成的,都位于java.util包中。
三、Collection接口(单列集合)
定义了存取一组对象的方法,其子接口Set和List分别定义 了存储方式。
● Set 中的数据对象不可以重复。
● List 中的数据对象有顺序(添加顺序)且可以重复。
四、List 接口及实现类(有重复元素)
List继承了Collection接口,有三个实现的类
● ArrayList 数组列表
①ArrayList实现了长度可变的数组,底层有一个数组,可以动态扩展数组长度,并提供一个一系列方法操作
②有索引,查询快,遍历元素和随机访问元素的效率比较高
③将对象放在连续的位置中,最大的缺点就是添加,删除元素速度慢,涉及到元素的移动
public class ArrayListDemo1 {
public static void main(String[] args) {
ArrayList arrayList = new ArrayList();
arrayList.add("a");
arrayList.add(1);
arrayList.add("b");
arrayList.add("b");
arrayList.remove("a");
arrayList.remove(1);
System.out.println(arrayList);//输出:[1, b]
}
}
集合容器中默认可以添加Object类型, 但是一般还是习惯一个集合对象只保存同一种类型 因为保存多种类型,后期处理时,涉及到类型转换问题,比较麻烦
一个集合只保存一个类型,可以使用泛型(在声明类型时,自定义参数类型ArrayList<E>)
比如:ArrayList<String>
public class ArrayListDemo2 {
public static void main(String[] args) {
//多种类型Object
ArrayList arrayList = new ArrayList();
arrayList.add("abc");
arrayList.add(1);
arrayList.add("bc");
arrayList.add(true);
arrayList.add(4.0);
for (Object object : arrayList) {
//默认都是Object类型
if(object instanceof String){
String s = (String)object;//向下转型
System.out.println(s.length());
}
if (object instanceof Integer){
// 每种类型都要判断,都要涉及类型转换,有点麻烦
}
}
//声明为String类型
ArrayList<String> arrayList1 = new ArrayList();
arrayList1.add("a");
arrayList1.add("bc");
for (String s : arrayList1){
System.out.println(s.length());
}
//声明为Integer类型
ArrayList<Integer> arrayList2 = new ArrayList();
arrayList2.add(1);
arrayList2.add(2);
}
}
当我们添加元素时,默认的数组长度为10,add()方法内部首先会检查数组是否能够放得下,如果添加元素已经超过数组长度,就会调用grow()方法自己扩容,扩容到原来的1.5倍,然后进行将旧数组的元素复制到新数组中
add()方法在ArrayList类中的部分源码如下:
add()方法源码如下:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // 检查数组是否能够放得下
elementData[size++] = e;
return true;
}
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);//最初默认长度为10,扩容到原来的1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);//数组复制
}
ArrayList的常用方法:
add(E element)
add(int index, E element)
get(int index)
indexOf(Object o)
lastIndexOf(Object o)
remove(int index) 删除并返回指定位置元素
set(int index, E element)
下例中包含了ArrayList类中的一些常用方法:
public class ArrayListDemo3 {
public static void main(String[] args) {
/*
add(E e) ;默认向末位添加元素
add(0,"e");向指定位置添加元素
*/
ArrayList<String> arrayList = new ArrayList();
arrayList.add("a");
arrayList.add("b");
arrayList.add("c");
arrayList.add("d");
arrayList.add("d");
arrayList.add("d");
arrayList.add("d");
arrayList.add("d");
arrayList.add("d");
arrayList.add(0,"a");
System.out.println(arrayList.remove("d"));//根据内容删除匹配的第一个元素,删除成功后返回true,否则返回false
System.out.println(arrayList.remove(1));//删除并返回指定位置上的元素
System.out.println(arrayList.get(1));//获取指定位置上的元素
System.out.println(arrayList.indexOf("d"));//获取指定元素第一次出现的正序
System.out.println(arrayList.lastIndexOf("d"));//获取指定元素第一次出现的倒序
System.out.println(arrayList.set(2,"a"));//替换并返回指定位置上的元素
// arrayList.clear();//清空集合中的元素
System.out.println(arrayList.isEmpty());//判断集合中的元素是否为空 为空返回true 否则返回false
System.out.println(arrayList.contains("a"));//判断是否包含指定的元素
System.out.println(arrayList.size());//返回集合中元素的个数
System.out.println(arrayList);
for (int i = 0; i < arrayList.size(); i++) {
System.out.println(arrayList.get(i));//对已有的元素进行遍历
}
}
}
● LinkedList 链表
①底层是一个链表结构,将对象存放在独立的空间中,而且在每个空间还保存着下一个链接的索引
②缺点就是查询慢,查找非常麻烦,要从第一个索引开始
③增加,删除数据速度快,只需记录前后项即可,元素不用移动
LinkedList的常用方法:
add(int index,Object element)
addFirist(Object element)
addLast(Object element)
get(int index)
removeFirst()
removeLast()
remove(int index)
getFirst()
getLast()
下例中包含了LinkedList中的一些常用方法:
public class LinkedListDemo {
public static void main(String[] args) {
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("a");
linkedList.add("b");
linkedList.add("c");
linkedList.add("d");//向链末尾添加元素
linkedList.add(1,"d");//向指定位置添加元素
System.out.println(linkedList.get(2));//获取指定位置上的元素
System.out.println(linkedList.remove("d"));
System.out.println(linkedList.remove(1));
System.out.println(linkedList.remove());
System.out.println(linkedList.removeFirst());//删除并返回第一个元素
//队列 先进先出
linkedList.addFirst("a");
linkedList.removeLast();
// 栈 先进后出
linkedList.removeLast();
linkedList.addLast("a");
System.out.println(linkedList);
linkedList.pop();//出栈
linkedList.clear();
System.out.println(linkedList.size());
System.out.println(linkedList.isEmpty());
System.out.println(linkedList.contains("a"));
System.out.println(linkedList);
}
}
● Vector 数组列表
①Vector和ArrayList都是用数组的方式存储数据,索引数据快,插入数据慢
②Vector使用了sychronized方法(线程安全),所以在性能上比ArrayList要差些
public class VectorDemo {
public static void main(String[] args) {
Vector<String> vector = new Vector();
vector.add("a");
vector.add("b");
vector.add("c");
vector.add("d");
System.out.println(vector.size());
System.out.println(vector);
}
}
五、List接口集合迭代
● for循环遍历
● 增强for循环的遍历
● 迭代器遍历(Iterator)
以ArrayList为例:
for循环和增强for循环
public class ArrayListDemo4 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("a");
arrayList.add("a");
arrayList.add("a");
arrayList.add("b");
arrayList.add("c");
arrayList.add("d");
/*
List接口实现类/List集合遍历方式1:for 循环
允许操作(删除)元素,注意索引的变化和与元素位置的移动
*/
for (int i = 0; i < arrayList.size(); i++) {
if ("a".equals(arrayList.get(i))) {
arrayList.remove("a");
i--;
}
}
System.out.println(arrayList);
/*
List接口实现类/List集合遍历方式2:增强for 循环
不允许修改(删除,添加)元素
*/
for (String s:arrayList) {
/*arrayList.remove(s);
arrayList.add("a");*/ //不能进行元素的删除修改操作
System.out.println(s);
}
System.out.println(arrayList);
}
}
迭代器
public class ArrayListDemo5 {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("a");
arrayList.add("a");
arrayList.add("a");
arrayList.add("b");
arrayList.add("c");
arrayList.add("d");
/*
List接口实现类/List集合遍历方式3:使用迭代器遍历
*/
//获得集合对象的迭代器
Iterator<String> it = arrayList.iterator();
while (it.hasNext()){
String s = it.next();//获取到下一个元素
if(s.equals("a"));{
it.remove(); //使用迭代器对象删除元素
}
}
}
}
六、Set 接口(不可以有重复元素)
● Set接口继承了Collection接口
Set中所存储的元素是不重复的,是无序的, Set中的元素是没有索引的
● Set接口有两个实现类
● HashSet
①元素不能重复
②元素是无序的
③元素没有索引
④不能进行for循环
⑤只有一个remove()方法,只能根据内容删除
⑥不能调用get()方法
⑦自定义存储的对象类中必须重写equals和Hashcode方法
public class HashSetDemo1 {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("a");
set.add("a");
set.add("b");
set.add("c");
set.add("s");
set.add("x");
set.add("d");
set.clear();
set.contains("c");
set.remove("s");//没有索引,只有一个remove()方法,只能根据内容删除
set.size();
set.iterator();
System.out.println(set);
//Set没有索引,不能进行for循环,也不能调用get()方法
}
}
!!! HashSet在添加元素时,add()是如何判断这个元素在里面已经存在(重复)
当我们向集合中添加一个元素时,如果每次都使用equals()比较内容是否相等效率会很低(比如当元素的内容相当长的时候)
具体步骤:
①在底层先调用HashCode() Object中的hashCode返回的是对象的地址(不调用这个)
会调用类中重写的hashcode(),返回的是根据内容设计的哈希值
②遍历时,会用哈希值先比较是否相等,会提高比较的效率
但是哈希值也会存在问题,内容不相同,哈希相同
③此种情况下,再调用equals()比较内容,这样设计既提高判断效率,又保证安全
public class HashSetDemo2 {
public static void main(String[] args) {
HashSet<String> set = new HashSet<>();
set.add("a");
set.add("a");
set.add("b");
set.add("c");
set.add("s");
set.add("x");
set.add("d");
System.out.println(set);
}
}
输出:
[a, b, c, s, d, x]
举个其他例子吧,新建一个Student类,定义相关属性,name和num,必须重写equals和Hashcode方法
public class Student {
private String name;
private int num;
public Student(String name, int num) {
this.name = name;
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
@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(name, num);
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", num=" + num +
'}';
}
}
public class HashSetDemo3 {
public static void main(String[] args) {
HashSet<Student> set = new HashSet<>();
Student s1 = new Student("张三1",1);
Student s2 = new Student("张三2",2);
Student s3 = new Student("张三3",3);
Student s4 = new Student("张三4",4);
Student s5 = new Student("张三1",1);
set.add(s1);
set.add(s2);
set.add(s3);
set.add(s4);
set.add(s5);
System.out.println(set);
}
}
输出:
[Student{name='张三1', num=1}, Student{name='张三2', num=2}, Student{name='张三3', num=3}, Student{name='张三4', num=4}]
● TreeSet
①不能存储重复元素
②可以根据值进行排序,底层使用了树形结构,树结构本来就是有序的③元素没有索引
④不能进行for循环
⑤不能调用get()方法
⑥自定义存储的对象类中必须实现Comparable接口
!!! 向树形结构添加元素时,add()是如何判断元素大小以及元素是否重复
具体步骤:
①向TreeSet中添加的元素类型必须实现Comparable接口②类中重写compareTo()方法
每次添加元素时,调用compareTo()进行元素大小判断(小于0放在左子节点,等于0表示重复,舍去,大于0放在右子节点)
public class TreeSetDemo1 {
public static void main(String[] args) {
TreeSet<Integer> treeSet = new TreeSet<Integer>();
treeSet.add(2);
treeSet.add(1);
treeSet.add(3);
treeSet.add(3);
treeSet.add(4);
treeSet.clear();
treeSet.isEmpty();
treeSet.contains("c");
treeSet.first();
treeSet.last();
System.out.println(treeSet);
}
}
举个其他例子吧,还是Student类,定义相关属性,name和num,该类必须实现Comparable接口,重写compareTo()方法
public class Student implements Comparable<Student> {
private String name;
private int num;
public Student(String name, int num) {
this.name = name;
this.num = num;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", num=" + num +
'}';
}
@Override
public int compareTo(Student o) {
return this.num - o.num;
}
}
public class TreeSetDemo2 {
public static void main(String[] args) {
Student s1 = new Student("张三1",1);
Student s2 = new Student("张三2",2);
Student s3 = new Student("张三3",3);
Student s4 = new Student("张三4",4);
Student s5 = new Student("张三1",1);
TreeSet<Student> treeSet = new TreeSet<>();
treeSet.add(s3);
treeSet.add(s1);
treeSet.add(s4);
treeSet.add(s5);
treeSet.add(s2);
System.out.println(treeSet);
}
}
输出结果:
[Student{name='张三1', num=1}, Student{name='张三2', num=2}, Student{name='张三3', num=3}, Student{name='张三4', num=4}]
七、Set 接口集合迭代
● 遍历方式
Set接口的元素没有索引不能进行for循环
增强for循环
for (Integer a :treeSet){
System.out.println(a);//增强for()循环
}
迭代器遍历
Iterator<Integer> iterator =treeSet.iterator();//迭代器
八、Map 接口(双列集合:键 值)
● Map接口概述
①数据存储是以键:值的形式存储的
②将键映射到值的对象,通过键可以找到值
③一个映射不能包含重复的键(键不能重复),键若相同则会覆盖值
④每个键最多只能映射到一个值(值可以重复)
⑤一个键只能映射到一个值
● Map接口常用方法
V put(K key,V value)
V remove(Object key)
void clear()
boolean containsKey(Object key)
boolean containsValue(Object value)
boolean isEmpty()
int size()
V get(Object key)
Collection values()
Set keySet()
Set<Map.Entry> entrySet()
● HashMap(重点)
①元素的键不能重复
②键是无序的,排列顺序是不固定的
③可以存储一个为null的键
④HashSet的底层实现就是HashMap
⑤可以存储为null的键,键和值都允许为空
public class HashMapDemo1 {
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<>();
map.put("a","aa");
map.put("b","bb");
map.put("w","ww");
map.put("c","ww");
map.put("a","aaa");
System.out.println(map);
}
}
输出结果:
{a=aaa, b=bb, c=ww, w=ww}
下例中包含了HashMap中的一些常用方法:
public class HashMapDemo1 {
public static void main(String[] args) {
HashMap<String,String> map = new HashMap<>();
map.put("a","aa");//向Map中添加一组 键 值 对
System.out.println(map.remove("b"));//删除指定的键值,返回对应的值
map.clear();//清空键值对
System.out.println(map.isEmpty());//判断map中键值对的个数是否为空
System.out.println(map.containsKey("b"));
System.out.println(map.containsValue("bb"));
System.out.println(map.get("a"));
System.out.println(map.size());
System.out.println(map);
}
}
HashMap中的put()方法处理流程和源代码如下所示:
1.底层使用一个长度默认为16的数组,用来确定元素的位置,每次用key计算出哈希值,用哈希值%数组长度,确定元素位置,将元素放在哈希表中指定的位置
2.后来继续添加元素,如果出现相同位置且不重复的元素,那么将后来的元素添加到之前元素的next节点
3.当链表长度等于8时,且哈希数组长度大于64 链表才会转为红黑树
4.哈希表负载因子为0.75,当哈希表使用数组的0.75时,会自动扩容为原来数组长的2倍
//先调用哈希值
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
HashMap.Node<K,V>[] tab; HashMap.Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
HashMap.Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof HashMap.TreeNode)
e = ((HashMap.TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
● TreeMap
①TreeMap中所有的元素都保持着某种固定的顺序,如果需要得到一个有序的Map就应该使用TreeMap(键可以排序)
②key值所在类必须实现Comparable接口,重写compare To()
③底层使用树形结构存储键值
public class TreeMapDemo {
public static void main(String[] args) {
TreeMap<Integer,String> treeMap = new TreeMap<>();
treeMap.put(2,"aa");
treeMap.put(1,"bb");
treeMap.put(3,"cc");
treeMap.put(3,"ccc");
treeMap.put(4,"dd");
treeMap.containsKey(1);
treeMap.isEmpty();
treeMap.size();
treeMap.get(1);
System.out.println(treeMap);
}
}
输出结果:
{1=bb, 2=aa, 3=ccc, 4=dd}
● HashTable
①HashTable和HashMap很相似
②使用了sychronized方法(线程安全),实现了同步
③不能存储为null的键,键和值都不允许为空
④底层实现也是用到key的哈希值,计算位置,判断元素是否重复
public class HashtableDemo {
public static void main(String[] args) {
Hashtable<String,String> map = new Hashtable<>();
map.put("b","bb");
map.put("a","aa");
map.put("c","cc");
map.put("b","bbb");
System.out.println(map);
}
}
● Map集合遍历
•方式1:根据键找值
• 获取所有键的集合
• 遍历键的集合,获取到每一个键
• 根据键找值
public class HashtableDemo {
public static void main(String[] args) {
Hashtable<String,String> map = new Hashtable<>();
map.put("b","bb");
map.put("a","aa");
map.put("c","cc");
map.put("b","bbb");
System.out.println(map);
Set<String> keySet = map.keySet();
for (String key:keySet){
System.out.println(key+":"+map.get(key));
}
}
}
输出结果:
{b=bbb, a=aa, c=cc}
b:bbb
a:aa
c:cc
•方式2:根据键值对对象找键和值(更推荐)
• 获取所有键值对对象的集合
• 遍历键值对对象的集合,获取到每一个键值对对象
• 根据键值对对象找键和值
public class HashtableDemo {
public static void main(String[] args) {
Hashtable<String,String> map = new Hashtable<>();
map.put("b","bb");
map.put("a","aa");
map.put("c","cc");
map.put("b","bbb");
System.out.println(map);
Set<Map.Entry<String, String>> entries = map.entrySet();
for (Map.Entry entry : entries){
System.out.println(entry.getKey()+":"+entry.getValue());
}
}
}
输出结果:
{b=bbb, a=aa, c=cc}
b:bbb
a:aa
c:cc
● TreeMap
适用于按自然顺序或自定义顺序遍历键(key)。
TreeMap根据key值排序,key值需要实现Comparable接口,重写compareTo方法。TreeMap根据compareTo的逻辑,对 key进行排序。
键是红黑树结构,可以保证键的排序和唯一性
九、Collections类
● Collections是集合类的工具类,与数组的工具类Arrays类似
addAl l(Col lection c, T... elements);将指定的可变长度参数数据添加到指定集合中
binarySearch(List> list, T key);二分查找
sort(List list );默认升序
sort(List list, Comparator c);自定义排序规则
swap(List list, int i, int j);交换位置
copy(List dest, List src) ; 集合复制,将源集合复制到目标集合,注意目标集合 size需大于等于源集合size
fi l l(List list, T obj);用指定的值,替换到集合中所有元素
max(Col lection col l);
min(Col lection col l);
replaceAl l(List list, ToldVal, newVal);用新的值替代原来旧的值
reverse(List list);逆序
shuffle(List list) 随机排序
public class CollectionsDemo1 {
public static void main(String[] args) {
/*
①addAl l(Col lection c, T... elements);//将指定的可变长度参数数据添加到指定集合中
*/
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
Collections.addAll(list,3,4,7,6,5);
/*
②sort(List list);
*/
Collections.sort(list);//默认是升序的
/*
③sort(List list, Comparator c)自定义排序规则,比如倒序
*/
Collections.sort(list, new Comparator<Integer>() {//创建了一个Comparator接口的匿名内部类对象,省去创建一个类,为了简化语法
@Override
public int compare(Integer o1, Integer o2) {
return o2.intValue()-o1.intValue();
}
});
System.out.println(Collections.binarySearch(list,2));
System.out.println(list);
}
}
public class CollectionsDemo2 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
ArrayList<Integer> list1 = new ArrayList<>();
list1.add(5);
list1.add(6);
list1.add(7);
list1.add(8);
list1.add(9);
Collections.swap(list,0,1);
Collections.copy(list1,list);
System.out.println(Collections.max(list));
System.out.println(Collections.min(list));
Collections.shuffle(list);
Collections.replaceAll(list,7,4);
Collections.reverse(list);
System.out.println(list);
System.out.println(list1);
}
}