13. 集合
13.1 集合的概念
集合是由java提供的一系列类,可用于动态的存放多个对象。(集合只能够存放对象)
集合和数组之间的不同在于:集合大小可变,且元素类型不受限定,只要是引用类型即可。(集合中不能存放基本数据类型,但可以存放基本数据类型的包装类。)
集合类全部支持泛型,是一种数据安全的用法。
13.2 集合的框架图
详细描述:
整体上,集合分为两大家族:Collection 和 Map
-
Collection:单列集合(存放单个对象地址)。
-
Map:双列集合(存放键值对—[key,value])。
在框架中,还有另外的三个分支:Iterator、Comparator、Collections
- Iterator:迭代器,为服务Collection及其子类而设计。
- Comparator:比较器,在集合中储存对象时,用于对象之间的比较。
- Collections:工具类,提供静态方法,用来对Collection集合进行操作。
13.3 Collection
13.3.1 接口概述
Collection接口定义了存取对象的方法,有两个非常重要的子接口List和Set。
List接口:存放的元素有序且允许有重复的集合接口。
Set接口:存放的元素无序不允许有包含重复的集合接口。
注:
无序并不是随机,而是通过哈希值+散列算法得出的存放地址,在遍历输出时,有特定的顺序。
13.3.2 接口中的常用方法
- int size(); 返回此collection中的元素个数
- boolean isEmpty(); 判断此collection中是否包含有元素。(有:false;无:true)
- boolean contains(Object obj); 判断此collection是否包含指定的元素。
- boolean contains(Collection c); 判断此collection是否包含指定collection中的所有元素。
- boolean add(Object element); 向此collection中添加元素。
- boolean addAll(Collection c);将指定collection中的所有元素添加到此collection中
- boolean remove(Object element); 从此collection中移除指定的元素。
- boolean removeAll(Collection c); 移除此collection中那些也包含在指定collection中的所有元素。
- void clear(); 移除些collection中所有的元素。
- boolean retainAll(Collection c); 仅保留此collection中那些也包含在指定collection的元素。
- Iterator iterator(); 返回在此collection的元素上进行迭代的迭代器。
- Object[] toArray(); 把此collection转成数组。
13.3.3 List—ArrayList
底层:一维数组。
在Collection定义的方法基础上,List接口还增加定义了许多对下标的操作方法。
对Collection基础方法的实现示例:
ArrayList<String> list = new ArrayList<>(); //添加元素 list.add("aaa"); list.add("bbb"); list.add("ccc"); //获取元素的个数 System.out.println("当前集合元素的个数为:"+list.size()); ArrayList<String> list2 = new ArrayList<>(); //使用Collections工具对集合实现多添加。 Collections.addAll(list2, "111","222","333"); //在集合末尾插入新集合的所有元素 list.addAll(list2); //判断集合中是否包含某个元素 System.out.println("当前集合是否有元素eee:"+list.contains("eee")); //判断当前集合是否含有某个集合的所有元素 System.out.println("当前集合是否含有新集合的所有元素:"+list.containsAll(list2)); //清空集合 //list.clear(); System.out.println("当前集合元素的个数为:"+list.size()); list.remove("222"); //删除-去交集 LinkedList<Object> newList2 = new LinkedList<>(); Collections.addAll(newList2, "333","555"); list.removeAll(newList2); //保留交集 LinkedList<Object> newList3 = new LinkedList<>(); Collections.addAll(newList3, "111","aaa"); list.retainAll(newList3);
对List新增方法的实现示例:
ArrayList<String> list = new ArrayList<>(); //添加元素 list.add("aaa"); list.add("bbb"); list.add("ccc"); //设置指定下标上的元素 list.set(1, "ddd"); //在指定下标上插入元素 list.add(1, "eee"); ArrayList<String> list2 = new ArrayList<>(); //使用Collections工具对集合实现多添加。 Collections.addAll(list2, "111","222","333"); //在集合指定下标处插入新集合的所有元素 list.addAll(2, list2); //获取元素在集合中的下标 System.out.println("元素111在当前集合的下标为:"+list.indexOf("111")); //根据元素下标/内容删除 list.remove(4); //获取指定下标之间的元素,生成新的集合 List<String> list3 = list.subList(2, 4);
遍历方法:
System.out.println("---------------");
//迭代器遍历
Iterator<String> iterator = list3.iterator();
while(iterator.hasNext()){//判断当前迭代指向的下一个元素是否存在。
System.out.println(iterator.next()); //返回迭代中的下一个元素,并迭代指针下移。
}
System.out.println("---------------");
//foreach遍历
for (String string : list) {
System.out.println(string);
}
System.out.println("---------------");
//for循环遍历
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
Iterator迭代器:
常用方法:
hasNext(); 判断当前迭代指向的下一个位置是否存在元素。
next(); 返回迭代中的下一个元素,并迭代指针下移。
(这两个方法常用作遍历,示例见上13.3.3.)
remove();删除当前迭代对应的元素。(能够在遍历时,删除集合元素)
示例:
ArrayList<String> list = new ArrayList<>(); Collections.addAll(list, "aa","bb","cc","dd"); Iterator<String> it = list.iterator(); while(it.hasNext()){ String str=it.next(); if(str=="bb"){ it.remove(); } } for (String string : list) { System.out.println(string); } /* aa cc dd */
注:
如果在迭代过程中使用list.remove(str); 进行删除,运行将会报错。
原因:在进行迭代时,Iterator会获取到当前集合的操作数,将这个操作数赋值给内部操作数。且每次迭代时会进行比较,如果进行list.remove()方法,则集合的操作数会加1,导致与内部操作数不一致,报出异常。
而在使用Iterator中的方法remove删除(实质上还是调用的集合的remove方法),集合的操作数依旧会加1,但内部操作数也会及时更新。这样能够保证数据的一致性,不会有脏数据的出现。
ListIterator迭代器:
ListIterator迭代器:专门服务于List接口及其实现类的接口类,实现自Iterator。
常用方法:
Iterator常用方法:见上
hasPrevious(); 判断前面是否有可迭代的元素。
previous(); 返回迭代指向的上一个元素,并上移。
set(); 用该元素替换当前迭代指向的元素
add(); 在迭代位置添加该元素
listIterator(int index); 从下标处开始迭代
示例(倒叙输出List集合中的内容):
LinkedList<String> list = new LinkedList<>(); list.add("111"); list.add("222"); list.add("333"); list.add("444"); ListIterator<String> it1 = list.listIterator(list.size()); while(it1.hasPrevious()){ System.out.println(it1.previous()); } /* 444 333 222 111 */
示例(使用add和set方法)
ArrayList<String> list = new ArrayList<>(); Collections.addAll(list, "aa","bb","cc","dd"); ListIterator<String> it = list.listIterator(); while(it.hasNext()){ String str=it.next(); if(str=="bb"){ it.add("11"); } if(str=="cc"){ it.set("CC"); } } for (String string : list) { System.out.println(string); } /* aa bb 11 CC dd */
拓展1:泛型
数据安全的作法,规定集合应该存储什么样的数据类型
泛型在集合中的使用:
如上的例子,ArrayList list = new ArrayList<>(); 规定了list集合中只能装String类型对象,如果装入的对象类型不兼容,将会报错。
泛型限定的简单使用:(创建A父类,B子类:继承A类)
//?:任何类型
public ArrayList<?> method01(){
ArrayList<String> list = new ArrayList<>();
return list;
}
//? extends A
//?:表示A的子类或者A类
public ArrayList<? extends A> method02(){
//ArrayList<B> list = new ArrayList<>();
ArrayList<A> list = new ArrayList<>();
return list;
}
//? super A
//?:表示A的父类或A类
public ArrayList<? super A> method03(){
//ArrayList<Object> list = new ArrayList<>();
ArrayList<A> list = new ArrayList<>();
return list;
}
拓展2:foreach遍历
foreach遍历的底层由迭代器实现
String e;
for(Iterator<String> it = list.iterator();it.hasNext();System.out.println(e)){
e = it.next();
}
13.3.4 List—LinkedList
底层:双向链表
LinkedList在方法的使用上和ArrayList完全兼容(原因:同时实现List接口),唯一的不同为:由于底层的链表结构,LinkedList多出两种模式。
队列模式:先进先出,(removeFirst();方法)
LinkedList<String> list = new LinkedList<>(); list.add("111"); list.add("222"); list.add("333"); list.add("444"); Iterator<String> it = list.iterator(); while(it.hasNext()){ System.out.println(list.removeFirst()); } /* 111 222 333 444 */
栈模式:先进后出,(removeLast();方法)
LinkedList<String> list = new LinkedList<>(); list.add("111"); list.add("222"); list.add("333"); list.add("444"); Iterator<String> it = list.iterator(); while(it.hasNext()){ System.out.println(list.removeLast()); } /* 444 333 222 111 */
注:由于ArrayList和LinkedList底层结构的不同,在使用时如果需要多次查询,使用ArrayList,如果需要多次删除和添加,则使用LinkedList效率更高。
13.3.5 List—Vector(已弃用)
13.3.5.1 Vector(弃)
了解:Vector是元老级别的集合类,在JDK1.0开始就投入使用,后在JDK1.2才开始推出集合框架。
在方法使用上,Vector实现了List接口,能够实现List的所有方法,但也还有一些老的方法被保留下来。
如:
addElement()//添加元素 removeElement(); //删除元素 v.removeElementAt();//依据下标删除元素 //遍历 Enumeration<String> elements = v.elements(); while(elements.hasMoreElements()){ String nextElement = elements.nextElement(); System.out.println(nextElement); }
13.2.5.2 Stack(弃)
继承Vector,多出栈模式(先进后出)
使用示例:
Stack<String> stack = new Stack<>(); //添加元素 - 将元素压入栈顶 stack.push("aa"); stack.push("bb"); stack.push("cc"); stack.push("dd"); stack.push("ee"); System.out.println("距离栈顶的位置(从1开始):" + stack.search("bb")); //4 while(!stack.empty()){ //获取栈顶第一个元素,并返回 //String element = stack.peek(); //删除栈顶第一个元素,并返回 String element = stack.pop(); System.out.println(element); } /* ee dd cc bb aa */
13.3.6 Set—HashSet
底层:一维数组(哈希表)
HashSet在装入元素时,将元素的hash值+自身的散列算法,得出在哈希表中存放的地址,所以,当装入的元素相同时,计算得出的地址也相同,此时,HashSet认为这是同一个对象,将不再添加。
也正因为如此,HashSet添加的元素在数组中存放的位置不固定(对人来说),输出又是依次按照数组输出,所以其顺序也就等于无序。
使用方法(所有Collection接口中的方法):
常用示例:
HashSet<String> set = new HashSet<>(); //添加元素 set.add("aaa"); set.add("bbb"); set.add("ccc"); //获取元素的个数 System.out.println("当前集合元素的个数为:"+set.size()); HashSet<String> set2 = new HashSet<>(); //使用Collections工具对集合实现多添加。 Collections.addAll(set2, "111","222","333"); //在集合末尾插入新集合的所有元素 set.addAll(set2); //在集合指定下标处插入新集合的所有元素 //判断集合中是否包含某个元素 System.out.println("当前集合是否有元素eee:"+set.contains("eee")); //判断当前集合是否含有某个集合的所有元素 System.out.println("当前集合是否含有新集合的所有元素:"+set.containsAll(set2)); System.out.println("当前集合元素的个数为:"+set.size()); //根据元素下标/内容删除 set.remove(4); set.remove("222"); System.out.println("---------------"); //遍历集合 for (String string : set) { System.out.println(string); }
13.3.7 Set—LinkedHashSet
底层:双向链表(除了元素内容还存储有前后节点地址,Node节点类)
特点:去重+有序
在LinkedHashSet中,元素的储存方式依旧是和HashSet相同,采用哈希值+散列算法计算出存储的位置地址,但LinkedHashSet中不仅仅是储存元素,还会将元素的首尾节点一同储存到LinkedHashSet中,在进行遍历时,会从根节点出发,按照装入元素的顺序遍历出,所以为有序的。
常用方法与HashSet相同
13.3.8 Set—SorteSet—TreeSet
底层:自平衡的排序二叉树(红黑树)
注:使用TreeSet集合装入的元素,必须实现有比较方法。
常见的使用方法有两种:内置比较器和外置比较器
- 内置比较器(在需要装入TreeSet集合的类中实现接口Comparable< >,重写compareTo方法):
简单示例(学生类):
public class Student implements Comparable<Student>{ private String name; private int age; //构造方法 public Student() { } public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Student other = (Student) obj; if (age != other.age) return false; if (name == null) { if (other.name != null) return false; } else if (!name.equals(other.name)) return false; return true; } @Override public String toString() { return "Student [name=" + name + ", age=" + age + "]"; } //重写的compareTo方法,按照年龄排序 @Override public int compareTo(Student o) { return this.age-o.age; } }
注:此处重写的compareTo方法,当年龄相同时,会被TreeSet集合认为相同元素,从而不再加入,所以我们测试时使用不同的年龄看效果。
方法测试:
TreeSet<Student> set = new TreeSet<>(); set.add(new Student("小明",20)); set.add(new Student("小红",18)); set.add(new Student("小刚",27)); set.add(new Student("小南",19)); set.add(new Student("小慧",21)); for (Student student : set) { System.out.println(student); } /* Student [name=小红, age=18] Student [name=小南, age=19] Student [name=小明, age=20] Student [name=小慧, age=21] Student [name=小刚, age=27] */
外置比较器(在使用TreeSet集合时,在外部创建匿名内部类重写compare方法):
简单示例:
//创建集合对象 TreeSet<Student> set = new TreeSet<>(new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { //此处按照年龄倒序排序 return o2.getAge()-o1.getAge(); } }); set.add(new Student("小明",20)); set.add(new Student("小红",18)); set.add(new Student("小刚",27)); set.add(new Student("小南",19)); set.add(new Student("小慧",21)); set.add(new Student("小章",21)); //认为的“相同元素”,集合不再添加 for (Student student : set) { System.out.println(student); } /* Student [name=小刚, age=27] Student [name=小慧, age=21] Student [name=小明, age=20] Student [name=小南, age=19] Student [name=小红, age=18] */
说明:外置比较器的优先级是要大于内置比较器!!
资源拓展(理解二叉树的网站):https://www.cs.usfca.edu/~galles/visualization/Algorithms.html
13.4 Map
13.4.1 HashMap
底层:一维数组
特点:存入键值对(key—value),去重+无序(添加元素时,同样是利用哈希值和散列算法求出存放地址,所有在数组中依次遍历的时候,是无序的。)
注:
计算地址只通过Key计算,与value无关,所有value值能够重复,而Key不能重复。
Map没有迭代器,需要遍历时:
1.可以通过keySet();方法,将所有的key抽取到Set集合中,再通过迭代器获取value值。
2.可以使用entrySet()方法返回Set<Entry<String, Integer>>对象再遍历。
常用使用方法示例:
HashMap<String ,Integer> map = new HashMap<>(); //添加元素 map.put("aa", 21); map.put("bb", 22); map.put("cc", 23); map.put("dd", 21); map.put("ee", 20); map.put("ff", 21); //将新集合添加到集合中 HashMap<String, Integer> map2 = new HashMap<>(); map2.put("gg", 10); map2.put("hh", 10); map2.put("ii", 10); map2.put("jj", 10); map.putAll(map2); //添加元素:如果有该key就不添加,返回集合中的value System.out.println(map.putIfAbsent("aa", 20)); //21 System.out.println(map.putIfAbsent("adc", 20)); //null //清空集合 // map.clear(); //通过key获取对应的value Integer integer2 = map.get("aa"); System.out.println(integer2); //21 System.out.println(""+map.getOrDefault("sf", 404));//如果没有key则返回默认设置的value //判断集合中是否存在元素 System.out.println(map.isEmpty());//存在元素:false ;不存在:true //删除元素 map.remove("abc");//通过key值 map.remove("bb", 22); //通过key+value判断 map.remove("cc", 22); //通过key+value判断 //替换 //替换成功就返回替换前的value值 System.out.println("替换1:"+map.replace("aa", 100)); //替换不成功就返回null System.out.println("替换2:"+map.replace("ggb", 100)); //put替换:如果有相同key,就替换value,并返回原来的value。 System.out.println("替换3:"+map.put("ff", 100)); /* 替换1:21 替换2:null 替换3:21 */ //获取集合的元素个数 System.out.println("集合元素个数:"+map.size()); //获取所有的value值 Collection<Integer> values = map.values(); System.out.println(values); //遍历1——keySet() //将所有的key抽取到Set集合中,在通过迭代器获取value值 System.out.println("-------------------"); Set<String> set = map.keySet(); for (String key : set) { Integer integer = map.get(key); System.out.println(key+"----"+integer); } System.out.println("-------------------"); //遍历2 -entrySet() Set<Entry<String, Integer>> entrySet = map.entrySet(); for (Entry<String, Integer> entry : entrySet) { System.out.println(entry); } /* dd=21 ff=100 hh=10 jj=10 aa=100 cc=23 ee=20 gg=10 ii=10 adc=20 */
13.4.2 LinkedHashMap
底层:双向链表
特点:存入键值对(key—value),去重+有序
(对照ArrayList和LinkedList)
13.4.3 ConcurrentHashMap
底层:一维数组
特点:存入键值对(key—value),去重+无序+线程安全(效率高—局部加锁)
使用方法和HashMap相同,但具有线程安全的特性。
13.4.4 Hastable(已弃用)
底层:一维数组
特点:存入键值对(key—value),去重+无序+线程安全(效率低—方法加锁)
使用方法和HashMap相同,虽然具有线程安全的特性,但效率低,已经被遗弃。
13.4.5 Properties
作用:配置文件
使用示例:
- 创建后缀为.properties的文件,放入src文件夹下。文件中可以编辑用户名和密码。
username=root password=123456
注:字符串也不需要加" "。
- 在类中的使用:
public static void main(String[] args) throws IOException { //创建配置文件的对象 Properties p = new Properties(); //将配置文件加载到对象中 p.load(Test_2.class.getClassLoader().getResourceAsStream("DBconfig.properties")); String username=p.getProperty("username"); String password=p.getProperty("password"); System.out.println(username+"----"+password); } /* root----123456 */
13.4.6 TreeMap
使用示例(Student类见上13.3.8):
调用内部比较器
//内部比较器见Student类中,排序方法使用年龄排序 TreeMap<Student, Integer> map = new TreeMap<>(new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o2.getAge()-o1.getAge(); } }); map.put(new Student("小明",20),20); map.put(new Student("小红",18),20); map.put(new Student("小刚",27),20); map.put(new Student("小南",19),20); map.put(new Student("小慧",21),20); Set<Entry<Student,Integer>> entrySet = map.entrySet(); for (Entry<Student, Integer> entry : entrySet) { System.out.println(entry); } /* Student [name=小红, age=18]=20 Student [name=小南, age=19]=20 Student [name=小明, age=20]=20 Student [name=小慧, age=21]=20 Student [name=小刚, age=27]=20 */
调用外部比较器
//调用外部比较器(比较器采用年龄倒序排序) TreeMap<Student, Integer> map = new TreeMap<>(new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o2.getAge()-o1.getAge(); } }); map.put(new Student("小明",20),20); map.put(new Student("小红",18),20); map.put(new Student("小刚",27),20); map.put(new Student("小南",19),20); map.put(new Student("小慧",21),20); Set<Entry<Student,Integer>> entrySet = map.entrySet(); for (Entry<Student, Integer> entry : entrySet) { System.out.println(entry); } /* Student [name=小刚, age=27]=20 Student [name=小慧, age=21]=20 Student [name=小明, age=20]=20 Student [name=小南, age=19]=20 Student [name=小红, age=18]=20 */
Map集合之间的综合比较:
HashMap:存储键值对,无序,允许存null键,线程不安全
LinkedHashMap:存储键值对,有序,允许存null键,线程不安全
Hashtable:存储键值对,无序,不允许存null键,线程安全(直接在方法中上锁,效率低),已弃用
ConcurrentHashMap:存储键值对,无序,不允许存null键,线程安全(局部加锁,效率高)