一、前言
<font face= 黑体 > 在 Java 汇合(一)中咱们曾经讲了 Collection 汇合接口 、Iterator 迭代器 和泛型,明天咱们来讲 Set 汇合、List 汇合 和 Collections 工具类。
二、Set 汇合
<font face= 黑体 >Set 接口继承自 Collection 接口,它与 Collection 接口中的办法基本一致,并没有对 Collection 接口进行性能上的扩大,只是比 Collection 接口更加严格了,与 List 汇合不同的是,Set 汇合不容许存储反复的元素,而且 Set 汇合是没有索引的。
<font face= 黑体 >Set 汇合有多个子类,这里咱们介绍其中的 HashSet 与 LinkedHashSet 这两个汇合。
<font face= 黑体 >Set 汇合取出元素的形式能够采纳:迭代器、加强 for。
2.1、HashSet 汇合
<font face= 黑体 >HashSet 汇合实现了 Set 接口,首先 Set 汇合有的特点它都有,同时它还有以下特点:
- <font face= 黑体 > 是一个无序的汇合,存储元素和取出元素的程序有可能不统一。
- <font face= 黑体 > 底层是一个哈希表构造,查问的速度十分的快。
<font face= 黑体 >HashSet 汇合代码演示如下所示:
public class SetDemo01 {public static void main(String[] args) {Set<Integer> set = new HashSet<>();
// 应用 add() 办法增加元素
set.add(1);
set.add(3);
set.add(2);
set.add(1);
// 应用迭代器遍历汇合
Iterator<Integer> iterator = set.iterator();
while (iterator.hasNext()) {System.out.println(iterator.next());
}
// 应用加强 for 循环遍历
for (Integer integer : set) {System.out.println(integer);
}
}
}
2.2、哈希值
<font face= 黑体 > 哈希值是一个十进制的整数,由零碎随机给出,实际上就是对象的地址值,是一个逻辑地址,是模仿进去的地址,不是数据理论存储的物理地址。在 Object 类有一个办法 hashCode(),能够获取对象的哈希值。
<font face= 黑体 >hashCode() 办法源码如下:(native 代表该办法调用的是本地操作系统的办法)
public native int hashCode();
<font face= 黑体 >toString() 的源码如下:
public String toString() {return getClass().getName() + "@" + Integer.toHexString(hashCode());
}
<font face= 黑体 > 能够看出 toString() 也调用了 hashCode() 并将其转化为十六进制。
<font face= 黑体 > 哈希值代码演示如下所示:
public class Person extends Object {public static void main(String[] args) {Person p1 = new Person();
int h1 = p1.hashCode();
System.out.println(h1); // 1163157884
Person p2 = new Person();
int h2 = p2.hashCode();
System.out.println(h2); // 1956725890
/**
* toString() 的源码
* public String toString() {* return getClass().getName() + "@" + Integer.toHexString(hashCode());
* }
*/
System.out.println(p1); // com.zjgsu.Set.Person@4554617c
System.out.println(p2); // com.zjgsu.Set.Person@74a14482
/**
* String 类的哈希值
* String 类重写了 hashCode() 所以上面两个哈希值是一样的
*/
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
}
}
2.3、哈希表
<font face= 黑体 > 在 JDK1.8 之前 哈希表 = 数组 + 链表 ,然而在JDK1.8 之后, 哈希表 = 数组 + 链表 + 红黑树(进步查问效率)。具体如下图所示:
2.4、Set 汇合存储元素不反复的原理
<font face= 黑体 > 咱们先来执行以下上面的代码:
public class SetDemo02 {public static void main(String[] args) {HashSet<String> set = new HashSet<String> ();
String s1 = new String("abc");
String s2 = new String("abc");
set.add(s1);
set.add(s2);
set.add("重地");
set.add("通话");
set.add("abc");
System.out.println(set); // [重地, 通话, abc]
}
}
<font face= 黑体 > 打印后果如下所示:(能够看到 “abc” 只有一个)
<font face= 黑体 > 咱们依据代码来剖析一下:
- <font face= 黑体 >HashSet<String> set = new HashSet<String> ():当初咱们曾经晓得 hashSet 底层是一个哈希表,所以这句代码会创立一个哈希表。
- <font face= 黑体 >set.add(s1):add() 办法会调用 s1 的 hashCode() 办法。计算字符串“abc”的哈希值,哈希值是 96354,在汇合中找有没有 96354 这个哈希值的元素,发现 没有 , 就把 s1 存储到汇合中。
- <font face= 黑体 >set.add(s2):add() 办法会调用 s2 的 hashCode() 办法。计算字符串“abc”的哈希值,哈希值是 96354,在汇合中找有没有 96354 这个哈希值的元素,发现 有(哈希抵触),s2 会调用 equals() 办法和哈希值雷同的元素进行比拟,s2.equals(s1) 返回 true,两个元素雷同,就不会把 s2 存储到汇合中。
- <font face= 黑体 >set.add(“ 重地 ”);:add() 办法会调用 “ 重地 ” 的 hashCode() 办法。计算字符串“重地”的哈希值,哈希值是 1179395,在汇合中找有没有 1179395 这个哈希值的元素,发现 没有 , 就把 ” 重地 ” 存储到汇合中。
- <font face= 黑体 >set.add(“ 通话 ”);:add() 办法会调用 “ 通话 ” 的 hashCode() 办法。计算字符串“通话”的哈希值,哈希值是 1179395,在汇合中找有没有 1179395 这个哈希值的元素,发现 有(哈希抵触),” 通话 ” 会调用 equals() 办法和哈希值雷同的元素进行比拟,“ 通话 ”.equals(“ 重地 ”) 返回 false,两个元素不同,就把 ” 通话 ” 存储到汇合中。
<font face= 黑体 > 最初哈希表中的元素如下所示:
<font face= 黑体 > 通过上述步骤的剖析,曾经很明了的阐明了 Set 汇合元素不反复的原理,前提就是存储的元素必须重写 hashCode() 办法 和 equals() 办法。
2.5、HashSet 汇合存储自定义类型元素
<font face= 黑体 > 给 HashSet 中寄存自定义类型元素时,须要重写对象中的 hashCode() 办法和 equals() 办法,建设本人的比拟形式,能力保障 HashSet 汇合中的对象惟一。咱们来看个例子,如下所示:
public class 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 String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
public static void main(String[] args) {HashSet<Student> set = new HashSet<>();
Student s1 = new Student("张三", 18);
Student s2 = new Student("张三", 18);
Student s3 = new Student("张三", 30);
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
set.add(s1);
set.add(s2);
set.add(s3);
System.out.println(set);
}
}
<font face= 黑体 > 打印后果如下所示:
<font face= 黑体 > 能够看到,如果没有重写 hashCode() 办法 和 equals() 办法,那么三个人是都会打印进去的,因为这时候他们的哈希值是不同的。
<font face= 黑体 > 给 Student 类重写 hashCode() 办法和 equals() 办法,具体如下所示:
@Override
public int hashCode() {return Objects.hash(name, age);
}
@Override
public boolean equals(Object o) {if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
return age == student.age &&
Objects.equals(name, student.name);
}
<font face= 黑体 > 打印后果如下所示:
<font face= 黑体 > 能够看到,重写了 hashCode() 办法 和 equals() 办法之后,就把反复的 Student 对象去掉了。
2.6、LinkedHashSet 汇合
<font face= 黑体 > 咱们晓得 HashSet 保障元素惟一,可是元素寄存进去是没有程序的,那么咱们要保障有序,怎么办呢?在 HashSet 上面有一个子类 LinkedHashSet,它是链表和哈希表组合的一个数据存储构造,它多了一条链表用来记录元素的存储程序,所以 LinkedHashSet 是有序的。
<font face= 黑体 >LinkedHashSet 汇合代码演示如下所示:
public class LinkedHashSetDemo01 {public static void main(String[] args) {HashSet<String> set = new HashSet<>();
set.add("abc");
set.add("www");
set.add("zz");
set.add("qq");
set.add("it");
System.out.println(set);
LinkedHashSet<String> linkedSet = new LinkedHashSet<>();
linkedSet.add("abc");
linkedSet.add("www");
linkedSet.add("zz");
linkedSet.add("qq");
linkedSet.add("it");
System.out.println(linkedSet);
}
}
三、List 汇合
3.1、List 接口介绍
<font face= 黑体 >List 接口继承自 Collection 接口,咱们会将实现了 List 接口的对象称为 List 汇合。在 List 汇合中容许呈现反复的元素,所有的元素是以一种线性的形式进行存储的,在程序中能够通过索引来拜访汇合中的指定元素。另外,List 汇合还有一个特点就是元素有序,即元素的存储和取出程序统一。
3.2、List 接口罕用办法
<font face= 黑体 >List 岂但继承了 Collection 接口的全副办法,而且还减少了一些依据元素索引来操作汇合的特有办法,如下:
- <font face= 黑体 >public void add(int index, E element):将指定的元素增加到指定地位上。
- <font face= 黑体 >public E get(int index):返回汇合中指定地位的元素。
- <font face= 黑体 >public E remove(int index):将指定地位上的元素移除并返回该元素。
- <font face= 黑体 >public E set(int index, E element):用指定元素替换汇合中指定地位的元素,并返回被替换的元素。
<font face= 黑体 >List 接口罕用办法代码演示如下所示:
public class ListDemo01 {public static void main(String[] args) {
// 创立一个 List 汇合对象
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("a");
System.out.println(list);
// public void add(int index, E element)
// 在 c 和 d 之间增加一个 Test
list.add(3, "Test");
System.out.println(list);
// public E remove(int index)
// 移除 c 元素
String removeE = list.remove(2);
System.out.println(removeE);
System.out.println(list);
// public E set(int index, E element)
// 把最初一个 a 替换成 A
String setE = list.set(4, "A");
System.out.println(setE);
System.out.println(list);
// public E get(int index)
// List 汇合遍历有三种形式
// 1、应用一般 for 循环
for (int i = 0; i < list.size(); i++) {System.out.print(list.get(i) + ",");
}
System.out.println("\n");
System.out.println("---------------------------- 分割线 --------------------------");
// 2、应用迭代器循环
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {System.out.print(iterator.next() + ",");
}
System.out.println("\n");
System.out.println("---------------------------- 分割线 --------------------------");
// 3、应用加强 for 循环
for (String s : list) {System.out.print(s + ",");
}
}
}
<font face= 黑体 >$\\color{red}{留神}$:操作索引的时候肯定要搁置索引越界异样。
3.3、List 接口的子类
3.3.1、ArrayList 汇合
<font face= 黑体 >ArrayList 汇合的底层数据结构是 数组构造 ,其特点是 元素增删慢,查问快,因为日常开发中应用最多的性能就算查问数据,所以 ArrayList 是最罕用的汇合。然而呢,咱们不能再开发中随便的应用 ArrayList 实现任何需要,只有当 ArrayList 适宜咱们的开发需要时(查问多,增删少),咱们才去应用它。
3.3.2、LinkedList 汇合
<font face= 黑体 >LinkedList 汇合的底层数据结构是 链表构造 ,其特点是 元素增删快,查问慢,然而链表构造的首尾元素查找速度跟数组的查找是一样快的,所以 LinkedList 中蕴含了大量操作首尾元素的办法。所以当咱们须要实现的需要增删操作很多,查问很少或者查问很多但都是查问手尾的时候,咱们就能够应用 LinkedList 汇合。
<font face= 黑体 >LinkedList 汇合操作首尾元素办法代码演示如下所示:
public class LinkedListDemo02 {public static void main(String[] args) {// show01();
// show02();
show03();}
// 减少元素
private static void show01() {
// 创立 LinkedList 汇合对象
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("a");
linkedList.add("b");
linkedList.add("c");
System.out.println(linkedList);
// addFirst()
linkedList.addFirst("www");
System.out.println(linkedList);
// push() 等效于 addFirst()
linkedList.push("ccc");
System.out.println(linkedList);
// addLast() 等效于 add()
linkedList.addLast("com");
System.out.println(linkedList);
}
// 获取元素
private static void show02() {
// 创立 LinkedList 汇合对象
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("a");
linkedList.add("b");
linkedList.add("c");
System.out.println(linkedList);
if (!linkedList.isEmpty()) {String first = linkedList.getFirst();
System.out.println(first);
String last = linkedList.getLast();
System.out.println(last);
}
}
// 移除元素
private static void show03() {
// 创立 LinkedList 汇合对象
LinkedList<String> linkedList = new LinkedList<>();
linkedList.add("a");
linkedList.add("b");
linkedList.add("c");
System.out.println(linkedList);
// pop() 相当于 removeFirst()
String first = linkedList.removeFirst();
System.out.println(first);
String last = linkedList.removeLast();
System.out.println(last);
System.out.println(linkedList);
}
}
3.3.3、Vector 汇合
<font face= 黑体 >Vector 汇合的底层数据结构也是 数组构造,然而与 List 和 LinkedList 不同的是,Vector 汇合是单线程的,速度慢,目前曾经被 ArrayList 所取代。
四、Collections 汇合工具类
4.1、罕用性能
<font face= 黑体 >Collections 是汇合工具类,用来对汇合进行操作,局部办法如下:
- <font face= 黑体 >public static < 泛型 > boolean addAll(Collections<T>, c, T...elements):往汇合中增加一些元素。
- <font face= 黑体 >public static void shuffle(List<?> list):打乱汇合程序。
- <font face= 黑体 >public static < 泛型 > void sort(List<?> list):将汇合中元素依照默认规定排序。
- <font face= 黑体 >public static < 泛型 > void sort(List<?> list, Comparator<? super T>):将汇合中元素依照指定规定排序。
4.2、sort(List<?> list) 办法
<font face= 黑体 >sort(List<?> list) 办法的应用前提是被排序的汇合外面存储的元素必须实现 Comparable 接口,并重写接口中的 compareTo() 办法定义排序的规定。
<font face= 黑体 >sort(List<?> list) 办法代码演示如下所示:
public class CollectionsDemo01 {public static void main(String[] args) {ArrayList<Student> list = new ArrayList<>();
list.add(new Student("张三", 18));
list.add(new Student("李四", 20));
list.add(new Student("王五", 15));
System.out.println(list);
Collections.sort(list);
System.out.println(list);
}
static 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 String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public int compareTo(Student o) {
// 自定义比拟规定
// 比拟两个人的年龄
return this.getAge() - o.getAge(); // 依照年龄升序
//return o.getAge() - this.getAge(); // 依照年龄降序
}
}
}
4.3、sort(list, Comparator) 办法
<font face= 黑体 >Comparator 和 Comparable 的区别:
- <font face= 黑体 >Comparable 是排序接口,若一个类实现了 Comparable 接口,就意味着“该类反对排序”。
- <font face= 黑体 >Comparator 是比拟器,咱们若须要管制某个类的秩序,能够建设一个“该类的比拟器”来进行排序。
- <font face= 黑体 >Comparable 相当于“外部比拟器”,而 Comparator 相当于“内部比拟器”。
一个对象不反对本人和本人比拟(没有实现 Comparable 接口),然而又想对两个对象进行比拟
<font face= 黑体 >sort(list, Comparator) 办法代码演示如下所示:
public class ComparatorDemo01 {public static void main(String[] args) {ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(3);
list.add(2);
System.out.println(list);
Collections.sort(list, new Comparator<Integer>() {
// 重写比拟的规定
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2; // 升序
//return o2 - o1; // 降序
}
});
System.out.println(list);
ArrayList<CollectionsDemo01.Student> list2 = new ArrayList<>();
list2.add(new CollectionsDemo01.Student("张三", 18));
list2.add(new CollectionsDemo01.Student("李四", 20));
list2.add(new CollectionsDemo01.Student("b 王五", 15));
list2.add(new CollectionsDemo01.Student("a 李六", 15));
System.out.println(list2);
Collections.sort(list2, new Comparator<CollectionsDemo01.Student>() {
@Override
public int compare(CollectionsDemo01.Student o1, CollectionsDemo01.Student o2) {int result = o1.getAge() - o2.getAge();
// 如果两个人的年龄是一样的,就用姓名的第一字比拟规定
if (result == 0) {return o1.getName().charAt(0) - o2.getName().charAt(0);
}
return result;
}
});
System.out.println(list2);
}
static class 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 String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}
五、小结
<font face= 黑体 > 汇合第二局部中咱们讲了 List 汇合 、Set 汇合 和 Collections 汇合工具类 ,下一节咱们讲 Map 汇合 和一个综合案例。
六、源码
<font face= 黑体 > 文章中用到的所有源码已上传至 github,有须要的能够去下载。