一、集合概述:
- 集合类存放在java.util包中,是一个用来存放对象的容器。
- 集合只能存放对象。
- 集合总存放的是多个对象的引用,对象本身还是存放在堆中。
- 集合可以存放不同类型的,不限数量的数据类型。
二、Set:
- HashSet 中的方法最终都是来源于Collection接口。
- 向Set中添加的数据,其所在的类一定要重写hashCode()和equals(),以实现对象相等的规则。即“相等的对象(即equlas()判断相等)必须具有相等的散列码(即hashCode()相等)” 。
一般直接使用编译器生成的重写方法。(Alt + shift + s)
1、HashSet:
HashSet特点:
- HashSet不是线程安全的。
- 不能保证元素的排列顺序:根据hashcode值决定对象存储的位置。
- 不可重复:指的是hashcode()返回的值不相等。
- 集合元素可以存null。
- HashSet底层:数组+链表 的结构。
添加元素的过程:
HashSet几个基本操作:
import java.util.HashSet;
import java.util.Set;
public class Test {
public static void main(String[] args) {
Set set = new HashSet();
//添加元素;
set.add(1);
set.add("a");
System.out.println(set);
//移除元素;
set.remove("a");
System.out.println(set);
//判断元素是否在集合中,返回布尔值;
System.out.println(set.contains(1));
//获取集合大小;
System.out.println(set.size());
//清空;
set.clear();
System.out.println(set);
}
}
HashSet遍历集合方式:
- 使用迭代器
- for each 迭代:其实内部还是调用的迭代器。
//使用迭代器遍历集合;
Iterator it = set.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
//for each迭代集合;
for(Object obj: set) {//这个的意思是set的每一个值取出来,赋值给obj,直到循环set的所有值
System.out.println(obj);
}
泛型:
//使用泛型;即让集合存储指定类型的元素;Object类型的其实也就是所有可以存储的类型,下面两行是等价的。
Set set = new HashSet();
Set<Object> set1 = new HashSet<Object>();
Set<String> set2 = new HashSet<String>();//指定类型为String
2.LinkedHashSet:
LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据(其实就是双向链表)。
优点:对于频繁的遍历操作,可以按照添加的顺序遍历,LinkedHashSet效率高于HashSet。
3.TreeSet:
TreeSet特点:
- 自然排序 、定制排序。
- treeset必须放入同类型的对象。(使用泛型来限制)
- TreeSet和TreeMap采用红黑树的存储结构。
自然排序根据需求去重写compareTo()方法即可。
注意:自然排序中,比较两个对象是否相同的标准为:compareTo()返回0,不再是equals()。
注意:定制排序中,比较两个对象是否相同的标准为:compare()返回0,不再是equals()。
定制排序例子:
例一:
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class Test1 {
public static void main(String[] args) {
Person p1 = new Person(14,"ais");
Person p2 = new Person(17,"yukino");
Person p3 = new Person(20,"asuna");
Person p4 = new Person(16,"makisei");
Set<Person> set = new TreeSet<Person>(new Person());//new一个TreeSet对象,存储Person类型的对象
set.add(p1);
set.add(p2);
set.add(p3);
set.add(p4);
for (Person person : set) {
System.out.println(person.name + ":" + person.age);
}
}
}
class Person implements Comparator<Person>{
int age;
String name;
public Person() {//无参构造器
}
public Person(int age, String name) {//有参构造
this.age = age;
this.name = name;
}
@Override
public int compare(Person o1, Person o2) {//按年龄进行排序
if (o1.age < o2.age) {
return 1;
}else if (o1.age > o2.age) {
return -1;
}else {
return 0;
}
}
例二:传入参数的定制排序。
package review;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
public class TreeSetTest {
public static void main(String[] args) {
sort();
}
public static void sort() {
Comparator compare = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof User && o2 instanceof User) {
User u1 = (User)o1;
User u2 = (User)o2;
return Integer.compare(u1.getAge(), u2.getAge());
}else {
throw new RuntimeException("输入数据类型不匹配");
}
}
};
TreeSet<Object> set = new TreeSet<>(compare);//在这传入了定制排序的参数compare
set.add(new User("tom", 12));
set.add(new User("jery", 18));
set.add(new User("mike", 15));
set.add(new User("jack", 14));
Iterator<Object> iterator = set.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
TreeSet遍历集合方式:
与hashset相同。
三、List与ArrayList、LinkedList、Vector:
Java ArrayList 菜鸟教程
Java LinkedList 菜鸟教程
简述:
一些操作跟集合相同,并有其额外的方法。(具有索引一类的用法)
contains(Object obj):判断当前集合中是否包含obj。
注意:在判断时会调用obj对象所在类的equals()方法。
向Collection接口的实现类的对象中添加数据obj时,要求obj所在类重写equals()。
拓展:
集合----->数组:toArray()
数组----->集合:调用Arrays类的静态方法Arrays.asList()
例子:
import java.util.ArrayList;
import java.util.List;
public class Test2 {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("b");
List<String> sublist = list.subList(1, 4);//输出指定范围的元素
System.out.println(sublist);
}
}
面试题:ArrayList、LinkedList、Vector三者的异同?
相同点:三个类都是实现了List接口,存放数据的特点相同:存储有序的,可重复的数据。
不同点:
- ArrayList:作为List接口的主要实现类;现场不安全,效率高;底层使用Object[]存储
- LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList效率高;底层使用双向链表存储
- Vector:作为List的古老实现类;线程安全,效率低;底层使用Object[]存储
ArrayList源码分析:
JDK7情况下:
(1)ArrayList list = new ArrayList();底层创建了长度是10 的Object[]数组elementData。
(2)list.add(obj),如果此次的添加导致底层的elementData数组容量不够,则扩容。默认情况下,扩容为原来容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
结论:建议开发中使用带参的构造器: ArrayList list = new ArrayLIst(int capacity);
JDK8中:
(1)ArrayList list = new ArrayLIst();底层Object[] elementData初始化为{ },并没有创建长度为10的数组
(2)list.add(obj);在第一次调用add()时,底层才创建了长度为10 的数组,并将数据obj添加到elementData中。后续的添加与扩容操作与jdk 7相同。
结论:JDK7中ArrayList的对象的创建类似于单例的饿汉式,而JDK8中则类似于懒汉式,延迟了数组的创建,节省了内存。
LinkedList源码分析:
内部使⽤双向链表的结构实现存储,LinkedList有⼀个内部类作为存放元素的单元,⾥⾯有三个属性,⽤来存放元素本身以及前后2个单元的引⽤,另外LinkedList内部还有⼀个header属性,⽤来标识起始位置,LinkedList的第⼀个单元和最后⼀个单元都会指向header,因此形成了⼀个双向的链表结构。
Vector源码分析:
扩容方法不同。初始化容量同样为10,默认扩容为原来的2倍。
练习:
1.取出List中重复的元素 :
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
/**
*取出List中重复的元素
*
*/
public class ListDuplicate {
private static HashSet hashSet;
public static void main(String[] args) {
ArrayList<Object> list = new ArrayList<Object>();
list.add(1);
list.add(2);
list.add(5);
list.add(2);
list.add(3);
list.add(4);
List duplicateList = duplicateList(list);
duplicateList.forEach(System.out::println); //1,2,3,4,5
}
public static List duplicateList(List list) {
HashSet set = new HashSet();
set.addAll(list);
return new ArrayList(set);
}
}
2.判断下列代码输出,Person()类已经重写过equals()方法和hashCode()方法。考察对HashSet数据添加及删除的底层理解。
在重写equals()和hashCode()后:修改了元素的属性值,此时修改后对象的hashCode值还是原先的值。在Set中使用remove()删除的时候,根据旧索引可以找到修改后的对象,但是remove()会先计算当前对象的哈希值,根据哈希值找对应的位置;但是,由于修改了属性后,对象的哈希值变了,这会使得remove()在删除的时候找到一个空的对象的位置,所以实质上也就没有删除任何元素。
四、Map:
Map特点:
Map是接口,实现类一般为HashMap。
Map结构的理解:
(1)map中的key:无序的、不可重复的,使用Set存储所有的key。------> 要求key所在的类药重写equals()和hashCode()。(以HashMap为例)
(2)map中的value:无序的、可重复的,使用Collection存储所有的value;
(3)一个键值对构成了一个Entry对象;
(4)map中的Entry:无序的、不可重复的,使用Set存储所有的Entry。
(1)HashMap:
底层:(1)JDK7及之前:数组+链表(2)JDK8 :数组+链表+红黑树
基本方法:
import java.util.HashMap;
import java.util.Map;
public class Test3 {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("key1", 1);//put写入元素
map.put("key2", 1);
map.put("key3", 2);
map.put("key4", 3);
System.out.println(map);
System.out.println(map.get("key2"));//根据键来取值
//判断是否包含某个键或者某个值
map.containsKey("key");
map.containsValue(2);
}
}
map.keySet(); //获取map集合所有的key的集合。
map.value(); //获取集合的所有的value值。
HashMap遍历:
(1)通过获取所有的键来遍历:keySet():返回所有 key构成的Set集合。
public class Test3 {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("key1", 1);//put写入元素
map.put("key2", 1);
map.put("key3", 2);
map.put("key4", 3);
//通过获取到所有的键来遍历,此时也可以使用iterator()来遍历
Set<String> keys = map.keySet();
for (String key : keys) {
System.out.println("key:" + key + ",value:" + map.get(key));
}
}
}
(2)Collection中的 values():返回所有value构成的Collection集合。
Collection values = map.values();
for(Object obj: values){
System.out.println(obj);
}
(3)entrySet()遍历:返回所有key-value对 构成的Set集合。
public class Test3 {
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("key1", 1);//put写入元素
map.put("key2", 1);
map.put("key3", 2);
map.put("key4", 3);
//通过entrySet()来遍历, 也可以使用iterator()来遍历。
Set<Entry<String, Integer>> entrys = map.entrySet();
for (Entry<String, Integer> entry : entrys) {
System.out.println("key:" + entry.getKey() + ",value:" + entry.getValue());
}
}
}
HashMap的底层实现原理:
(1)JDK7中:
扩容时:当超出临界值且要存放的位置非空时,扩容,默扩容为原来的2倍。初始容量为16。
threshold:扩容的临界值,= 容量*加载因子: 16 * 0.75 = 12
(2)JDK8中:
无参构造器:默认加载因子0.75,并没有创建数组。
TREEIFY_THRESHOLD:bucket中链表长度大于该默认值,转换为红黑树:8。
MIN_TREEIFY_CAPACITY:桶中的Node被树化时最小的hash表容量:64。
(2)LinkedHashMap:
保证在遍历map元素时,可以按照添加的顺序实现遍历。原因:在HashMap底层结构的基础上,添加了指向前一个元素和后一个元素的两个指针。即:对于频繁的遍历操作,此类执行效率高于HashMap。
(3)HashTable:
(4)Properties:
Properties类是HashTable的子类,常用来处理配置文件。key和value都是String类型。
(5)TreeMap:
- 一般使用map集合,不会使用过于复杂的对象做key。底层使用红黑树存储。
- 向TreeMap中添加key-value,要求key必须是由同一个类创建的对象,因为要按照key进行排序:自然排序、定制排序。排序这里可以参考TreeSet。
TreeMap自然排序:
public class Test4 {
public static void main(String[] args) {
//TreeMap的自然排序是字典排序
Map<Integer, String> map = new TreeMap<Integer, String>();
map.put(4, "a");
map.put(2, "a");
map.put(5, "a");
map.put(1, "a");
System.out.println(map);//{1=a, 2=a, 4=a, 5=a}
}
}
五、操作集合的工具类:Collections
- 一些直接操作:
Collections.reverse(list);//对集合元素进行反转;集合本身被修改了!
Collections.shuffle(list);//对集合元素进行随机排列;
Collections.sort(list);//对集合元素进行升序排列;(根据元素的自然顺序进行降序或者升序排列)
Collections.swap(list, int, int);//将指定list集合中i 处元素和 j 处元素进行交换。
Collections.frequency(c, o);//返回指定集合中指定元素的出现次数。
Collections.replaceAll(list, oldVal, newVal);//使用新值替换list对象的所有旧值。
注意copy()的使用:将原集合复制到一个新的集合中,要要求新的集合的大小大于原集合的大小才能完成复制,否则会抛出异常。一般创建方法如下:
List dest = Arrays.asList(new Object[list.size()]);//一般创建新集合的方法,list是原集合
- 根据指定的comparator产生的顺序对集合进行排序:
Collections.sort(stu,new Student()); // sort方法会根据,new Student()的compare重写方法进行排序。
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Collections集合工具类
* @author Ais Wallenstein
*
*/
public class Test5 {
public static void main(String[] args) {
Student s1 = new Student(11,"张三");
Student s2 = new Student(15,"李四");
Student s3 = new Student(13,"王五");
Student s4 = new Student(12,"马六");
List<Student> stu = new ArrayList<Student>();
stu.add(s1);
stu.add(s2);
stu.add(s3);
stu.add(s4);
//遍历集合
for (Student student : stu) {
System.out.println(student.name + ":" + student.age);
}
System.out.println("-----------");
//根据指定的comparator产生的顺序对集合进行排序。
Collections.sort(stu,new Student());
for (Student student : stu) {
System.out.println(student.name + ":" + student.age);
}
}
}
class Student implements Comparator<Student>{
int age;
String name;
public Student() {//无参构造器
}
public Student(int age, String name) {//有参构造
this.age = age;
this.name = name;
}
@Override
public int compare(Student o1, Student o2) {//按年龄进行排序
if (o1.age < o2.age) {
return 1;
}else if (o1.age > o2.age) {
return -1;
}else {
return 0;
}
}
}
- 根据Comparator指定的顺序,返回集合中最大或最小的元素。
Student stu_max = Collections.max(stu, new Student());
Student stu_min = Collections.min(stu, new Student());
System.out.println(stu_max.name + ":" + stu_max.age);
System.out.println(stu_min.name + ":" + stu_min.age);
举例:
List list1 = Collections.synchronizedList(list);
此时返回的list1就是线程安全的。