集合(二)
版权声明
- 本文原创作者:清风不渡
- 博客地址:https://blog.csdn.net/WXKKang
一、List实现类
1、ArrayList
在实际的开发中,ArrayList是使用频率最高的一个集合实现类
(1)原理
ArrayList底层维护了一个Object[]用于存储对象,默认数组的长度是10。当默认的或者指定的容量不够存储对象的时候,容量自动增长为原来的容量的1.5倍。
由于底层使用数组实现,在增加和删除的时候会牵扯到数组增容和拷贝元素,所以效率比较慢。但是数组可以直接按索引查找元素,所以查找时较快。
例如,假设向数组的0下标位置添加元素,那么原来的下标位置的元素需要整体往后移,并且数组可能还要增容。一旦增容, 就需要要将老数组的内容拷贝到新数组中。所以数组的增删的效率是很低的。
(2)例子
要求:去除ArrayList集合中的重复元素。
思路:循环遍历该集合,每取出一个放置在新的集合中,但是在放置之前,需要先判断新的集合是否以包含了新的元素。这里使用contains 方法,需要对象实现equals方法。
代码实现:
package cn.com;
import java.util.ArrayList;
public class Demo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<String>();
// add方法将元素加入最后
list.add("Jack");
list.add("Lucy");
list.add("Lucy");
list.add("Bruce");
ArrayList<String> list2 = new ArrayList<String>();
for(int i=0;i<list.size();i++) {
String str = list.get(i);
if (!(list2.contains(str))) {
list2.add(str);
}
}
System.out.println(list2);
}
}
2、LinkedList
(1)原理
由于LinkedList在底层使用链表实现存储结构,需要让上一个元素记住下一个元素,所以每个元素中保存的有下一个元素的位置。虽然LinkedList中也有下标,但是查找的时候需要从头往下找,显然是没有数组查找快。但是,链表在插入新元素的时候,只需要让前一个元素记住新元素,让新元素记住下一个元素就可以了,所以插入很快。
(2)特有方法
示例如下:
package cn.com;
import java.util.Iterator;
import java.util.LinkedList;
public class Demo {
public static void main(String[] args) {
LinkedList<String> list = new LinkedList<>();
list.add("北京");
list.add("上海");
list.add("成都");
list.addFirst("甘肃");
String first = list.getFirst();
System.out.println("第一个元素:"+first);
list.addLast(null);
String last = list.getLast();
System.out.println("最后一个元素:"+last);
System.out.println("删除第一个元素!"+list.removeFirst());
System.out.println("删除最后一个元素!"+list.removeLast());
//逆序迭代
Iterator<String> descendingIterator = list.descendingIterator();
while(descendingIterator.hasNext()) {
System.out.print(descendingIterator.next()+"\t");
}
}
}
运行结果:
二、Set
1、Set
Set中元素是无序存放的,即元素存入和取出的顺序不一定相同,并且集合中元素不能重复。其中,HashSet 是一种常用的Set
(1)存储原理
和List不同的是,Set中的对象不是按元素加入到集合中的先后顺序依次摆放,因此,Set中是没有索引的,也不能按索引访问集合中的对象。
在Set内部的存储结构使用了散列算法(HASH 算法),按对象的散列值决定存放位置,也按照散列值检索对象。由于散列值的无序特点,因此Set的行为表现为一个无序的集合,图示如下:
例如:现在有两个创建好的对象要加入到Set中:
Student s1 = new Student("Tom", "男", 12);
Student s2 = new Student("Lucy", "女", 12);
现在分析Set中存放对象的内部行为:
1、最开始是一个空的集合,如下:
Set<Student> set = new HashSet<Student>();
2、现在加入s1,如下:
set.add(s1);
3、现在加入s2,如下:
set.add(s2);
小结:
1、HashSet调用对象的hashCode()方法得到对象的hashCode值,并依据hashCode值决定该对象在HashSet中存放的位置
2、Hashset判断两个对象是否相等的依据:两个对象通过equals()方法比较后结果为相等,并且两个对象的hashCode()方法得到对象的hashCode值也相等
3、规则:如果两个对象通过equals()方法比较后结果为相等,那么两个对象的hashCode()方法得到对象的hashCode值也应该是相等的
4、建议:利用IDE重写hashCode( )和equals( )方法
(2)重复元素
在Java中,重复元素使用Object类中的hashCode和equals判断。在类中重写了hashCode和equals,当两个的hashCode方法返回相同值并且两个对象的equals返回true时,两个对象是相同的。
有一些常见的重复元素情形:
1、所有null常量都是重复元素
2、相同对象的不同引用变量是重复元素
3、String 对象已重写hashCode和equals;即只要两个String 对象的个通过equals( )方法比较后结果为相等,那么这两个String对象的hashCode()方法得到对象的hashCode值也是相等的。简单地说:两个String对象的值相同时,就认为这两个String对象是重复的。
例子如下:
package cn.com;
import java.util.HashSet;
import java.util.Set;
public class Demo {
public static void main(String[] args) {
//添加null值
Set<String> set1 = new HashSet<String>();
set1.add(null);
//输出set大小
System.out.println("set1的长度:"+set1.size());
//null对象不能重复加入,这是替换
set1.add(null);
//输出长度和上面的相等
System.out.println("set1的长度:"+set1.size());
//同一对象引用多个变量
Set<Student> set2 = new HashSet<Student>();
Student student1 = new Student("Tom", "man", 12);
Student student2 = student1;
//student1成功加入
set2.add(student1);
System.out.println("set2的长度:"+set2.size());
//第二次替换第一次
set2.add(student2);
System.out.println("set2的长度:"+set2.size());
//添加相同值的String对象
Set<String> set3 = new HashSet<String>();
String str1 = "Hello";
String str2 = new String("Hello");
//通过双等号证明str1和str2不是一个对象
System.out.println("str1和str2的地址比较:"+(str1==str2));
//通过equals方法证明str1和str2内容
System.out.println("str1和str2的内容比较:"+str1.equals(str2));
//str1成功添加
set3.add(str1);
System.out.println("set3的长度:"+set3.size());
//str2没有增加到集合里
set3.add(str2);
System.out.println("set3的长度:"+set3.size());
}
}
2、HashSet
使用Set集合都是需要去掉重复元素的。如果在存储的时候逐个equals()比较,效率较低,哈希算法提高了去重复的效率,降低了使用equals()方法的次数。当HashSet调用add()方法存储对象的时候,先调用对象的hashCode()方法得到一个哈希值,然后在集合中查找是否有哈希值相同的对象,如果没有哈希值相同的对象就直接存入集合,如果有哈希值相同的对象,就和哈希值相同的对象逐个进行equals()比较,比较结果为false就存入,true 则不存入。
HashSet的常见操作如下:
package cn.com;
import java.util.HashSet;
import java.util.Iterator;
public class Demo {
public static void main(String[] args) {
//创建一个空的集合
HashSet<Student> set = new HashSet<Student>();
//创建Student对象
Student s1 = new Student("Lucy", "女", 17);
Student s2 = new Student("Tom", "男", 18);
//将对象添加至set集合中
set.add(s1);
set.add(s2);
//输出集合大小(即长度)
System.out.println(set.size());
//删除集合中的s2元素
set.remove(s2);
//使用迭代器遍历集合
Iterator<Student> iterator = set.iterator();
while(iterator.hasNext()) {
Student student = iterator.next();
System.out.println(student.getName());
}
}
}
3、TreeSet
TreeSet是SortedSet接口的唯一实现类,TreeSet可以确保集合元素处于排序状态。TreeSet支持两种排序方式,自然排序和定制排序,其中自然排序为默认的排序方式。.
(1)引入
设计一个数据结构用于保存数据,要求不能重复存储元素,并且对数据支持排序
已有数据结构分析:
ArrayList、LinkedList 不能去除重复数据。
HashSet可以去除重复,但是不支持排序。
这里引入一个新的数据结构::TreeSet。
(2)排序接口Comparable
在Java中提供了一个对象比较接口Comparable,它只有一个方法:
所有加入TreeSet的元素必须实现Comparable接口,否则会抛出异常,在TreeSet内部使用Comparable接口对元素排序。和HashSet相比,它的效率较低。
(3)示例
实现对学生排序,如果年龄相同就再比较姓名
学生类代码:
package cn.com;
public class Student implements Comparable<Student>{
private String name;
private String gender;
private Integer age;
public Student() {
super();
// TODO Auto-generated constructor stub
}
public Student(String name, String gender, Integer age) {
super();
this.name = name;
this.gender = gender;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", gender=" + gender + ", age=" + age + "]";
}
//实现Comparable中的compareTo方法
@Override
public int compareTo(Student o) {
int age1 = this.age;
int age2 = o.getAge();
if(age1>age2) {
return 1;
}
if (age1<age2) {
return -1;
}
return this.name.compareTo(o.getName());
}
}
测试类代码:
package cn.com;
import java.util.Iterator;
import java.util.TreeSet;
public class Demo {
public static void main(String[] args) {
//创建一个空的集合
TreeSet<Student> set = new TreeSet<Student>();
//创建Student对象
Student s1 = new Student("Lucy", "女", 17);
Student s2 = new Student("Tom", "男", 18);
Student s3 = new Student("Jack", "man", 17);
//将对象添加至set集合中
set.add(s1);
set.add(s2);
set.add(s3);
//使用迭代器遍历集合
Iterator<Student> iterator = set.iterator();
while(iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
运行结果:
由运行结果我们可以看到,Jack是后添加的但是它是在Tom前面,并且在与Lucy年龄相同的情况下依照姓名进行了排序
4、LinkedHashSet
LinkedHashSet是具有可预知迭代顺序的Set接口的哈希表和链接列表实现。此实现与HashSet的不同之处在于,后者维护着一个运行于所有条目的双重链接列表。此链接列表定义了迭代顺序,该迭代顺序可为插入顺序或是访问顺序,例如:
package cn.com;
import java.util.LinkedHashSet;
public class Demo {
public static void main(String[] args) {
//创建一个空的集合
LinkedHashSet<String> set = new LinkedHashSet<String>();
//添加元素
set.add("a");
set.add("a");
set.add("a");
set.add("a");
set.add("b");
set.add("c");
set.add("d");
//打印集合
System.out.println(set);
}
}