16-1,集合框架Vector集合
1,Vector是向量,底层为数组结构,可以通过其elements()方法获取一个Enumeration对象,这个对象的功能于Iterator的功能相同,Iterator比他多了移除的操作,所以现在一般不用Vector,都用Iterator。
通过Enumeration迭代Vector集合的元素示例:
public class VectorDemo {
public static void main(String[] args) {
Vector v = new Vector();
v.addElement("abc1");
v.addElement("abc2");
v.addElement("abc3");
v.addElement("abc4");
Enumeration en = v.elements();
while(en.hasMoreElements()) {
System.out.println("nextElements:" + en.nextElement());
}
Iterator it = v.iterator();
while(it.hasNext()) {
System.out.println("next:" + it.next());
}
}
}
用Iterator替代Enumeration是因为Enumeration和里面的方法名字太长,简化书写。
16-2,LinkedList集合
1,基本操作
addFirst():向头部添加一个元素。
getFirst():获取第一个元素但不删除。
removeFirst():获取第一个元素并删除。
2,示例:
public class Demo {
public static void main(String[] args) {
LinkedList link = new LinkedList();
link.addFirst("abc1");
link.addFirst("abc2");
link.addFirst("abc3");
link.addFirst("abc4");
System.out.println(link);
System.out.println(link.getFirst());//abc4
System.out.println(link.getFirst());//abc4,不删除
//System.out.println(link.removeFirst());//abc4
//System.out.println(link.removeFirst());//abc3
while(!link.isEmpty()) {
System.out.println(link.removeLast());//abc1,abc2,abc3,abc4
}
}
}
16-3,LinkedList练习-模拟栈和队列
需求:请使用LinkedList来模拟一个堆栈或者队列数据结构。
堆栈:先进后出。
队列:先进先出。
我们应该描述一个这样的容器,给使用者提供一个容器对象完成这两种结构中的一种。
public class LinkedTest {
public static void main(String[] args) {
DuiLie d1 = new DuiLie();
d1.myAdd("abc1");
d1.myAdd("abc2");
d1.myAdd("abc3");
d1.myAdd("abc4");
while(!d1.isNull()) {
System.out.println(d1.myGet());
}
}
}
class Duilie {
private LinkedList list;
public DuiLie() {
list = new LinkedList();
}
//队列的添加元素功能
public void myAdd(Object obj) {
list.addLast(obj);
}
public Object myGet() {
return list.removeFirst();
}
public boolean isNull() {
return list.isEmpty();
}
}
实现了队列。
16-4,ArrayList存储自定义对象
例如存储Person对象。
Person类:
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
}
ArrayList存储自定义对象Person类。
public class ArrayListTest {
public static void main(String[] args) {
ArrayList al = new ArrayList();
al.add(new Person("lisi1",21));
al.add(new Person("lisi2",22));
al.add(new Person("lisi3",23));
al.add(new Person("lisi4",24));
Iterator it = al.iterator();
while(it.hasNext()) {
//System.out.println(((Person)it.next()).getName()+"::"+((Person)it.next()).getAge());
/*
上面这种方法会出现错误结果,在SOP中,每next一次,就想下走一个对象,也就是说,
第一个next读的是lisi1的name,然后跳转至下一个对象,后面的next读的是lisi2的age,
所以出现lisi1,22,lisi3,24的结果;
下面的方法,先创建一个对象把读取的对象赋给p,再SOP就不会出错了。
*/
Person p = (Person)it.next();
System.out.println(p.getName()+"---"+p.getAge());
}
}
}
16-5,HashSet集合
1,Set集合:元素不可以重复,取出元素的顺序和存入的顺序不一定一致,是无序的。无序是因为HashSet中的元素的地址使用系统的哈希算法算出来的,哈希算法算出的位置是无序的,故无序。
Set接口中的方法和Collection一致。
HashSet内部数据结构是哈希表,是不同步的,集合中的元素是唯一的。
示例:
public class HashSetDemo {
public static void main(String[] args) {
HashSet hs = new HashSet();
hs.add("hehe");
hs.add("heihei");
hs.add("haha");
hs.add("xixi");
//hs.add("hehe");//如果有两个hehe,则最后只打印一个,因为不许重复
Iterator it = hs.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
此程序的输出结果是无序的。
16-6,哈希表
1,哈希表是哈希算法,是内核的一种存储数据的算法,查找起来非常快,其底层为数组结构,哈希算法可以根据数据的属性来算出相应的在数组中的位置,直接将数据存储进去,取得时候不用遍历,将这个数据调用hashCode方法,算出位置,去相应的位置有没有该元素,就不用找其他位置的元素是否匹配了。
2,哈希表确定元素是否相同的步骤:
(1)判断的是两个元素的哈希值是否相同,如果相同,再判断两个对象的内容是否相同。
(2)判断哈希值相同,其实判断的是对象的hashCode方法,判断内容相同,用的是equals方法。
注意:如果哈希值不同,是不需要判断equals的。
16-7,HashSet存储自定义对象
1,如何保证HashSet的元素的唯一性呢?
是通过对象的hashCode方法和equals方法来完成确定对象的唯一性的。
如果对象的hashCode值不同,那么不用判断equals方法,就直接存储到哈希表中。
如果对象的hashCode值相同,那么要再次判断对象的equals方法是否为true,如果为true,视为相同元素,就进行存储。
记住:如果元素要存储到HashSet集合中,必须覆盖hashCode方法和equals方法。
一般情况下,如果定义的类会产生很多对象,比如:人,学生,书,通常都需要覆盖equals,hashCode方法。
这是判断对象是否相同的依据。
2,存储自定义对象实例:
public class Person {
private String name;
private int age;
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return this.age;
}
public void setAge(int age) {
this.age = age;
}
//重写hashCode和equals方法
@Override
public int hashCode() {
return name.hashCode() + age * 27;
}
@Override
public boolean equals(Object obj) {
if(this == obj) {
return true;
}
if(!(obj instanceof Person)) {
throw new ClassCastException("类型错误");
}
Person p = (Person)obj;
return this.name.equals(p.name) && this.age == p.age;
}
}
存储自定义对象类:
//往HashSet集合中存储Person对象,如果姓名和年龄相同,视为同一个人,相同元素。
public class HashSetTest {
public static void main(String[] args) {
HashSet hs = new HashSet();
/*
HashSet结合数据结构是哈希表,所以存储元素的时候,
使用元素的hashCode方法来确定位置,如果位置相同,
再通过元素的equals方法来确定是否相同。
*/
hs.add(new Person("lisi4",24));
hs.add(new Person("lisi7",27));
hs.add(new Person("lisi1",21));
hs.add(new Person("lisi9",29));
hs.add(new Person("lisi7",27));//若不重写hashCode和equals方法,这里也会输出lisi7,27,不保证唯一性
Iterator it = hs.iterator();
while(it.hasNext()) {
Person p = (Person)it.next();
System.out.println(p.getName()+"..."+p.getAge());
}
}
}
16-8,集合框架-练习
需求:取出ArrayList中的重复元素。
public class ArrayListTest{
public static void main(String[] args) {
ArrayList al = new ArrayList();
al.add("abc1");
al.add("abc2");
al.add("abc2");
al.add("abc1");
al.add("abc");
System.out.println(al);
al = getSingleElement(al);
System.out.println(al);
}
public static ArrayList getSingleElement(ArrayList al) {
//定义一个临时容器
ArrayList temp = new ArrayList();
//迭代al集合
Iterator it = al.iterator();
while(it.hasNext()) {
Object obj = it.next();
//判断被迭代到的元素是否在临时容器中存在。
if(!temp.contains(obj)) {
temp.add(obj);
}
}
return temp;
}
}
结果:abc1,abc2,abc2,abc1,abc
abc1,abc2,abc
16-9,LinkedHashSet集合
特点:具有可迭代顺序的Set接口的哈希表,和链接列表实现。
说白了,此集合可以保证对象唯一且有序。而HashSet是无序的。
例如:
public class LinkedHashSetDemo {
public static void main(String[] args) {
HashSet hs = new LinkedHashSet();
hs.add("haha");
hs.add("hehe");
hs.add("heihei");
hs.add("xixi");
Iterator it = hs.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
结果:haha hehe heihei xixi
输入和输出顺序是相同的。
16-10,TreeSet集合
1,这个集合可以对Set集合中的元素进行指定顺序的排序(按自然顺序进行排序)。
例如:
TreeSet ts = new TreeSet();
ts.add("zhangsan",22);
ts.add("lisi",23);
ts.add("wangwu",27);
迭代...
发现第二个ts.add报错。
原来,TreeSet要进行排序,就必须要具备排序这个功能,就Person来说,排序功能如何实现呢?
应该用java.lang中的Comparable接口,Person要想具备排序功能,实现Comparable接口即可。
Comparable接口的描述:
此接口强行对实现它的每一个类的对象进行整体排序,这种排序被称为类的自然排序,类的compareTo方法被称为他的自然比较方法。
2,TreeSet判断对象唯一性的方式,就是根据比较方法的返回结果是否为0,是0,就是相同元素,不存进去。在示例中,如果Person实现了Comparable接口并重写了compareTo方法,并且其返回0,则再运行时不会报错,但是只会打印出存的第一个元素,即:zhangsan,22,因为要存第二个的时候,会调用compareTo方法判断是否与上一个元素相同,由于返回结果是0,即表示相同,所以后面的元素就都不往里存了,所以只打印第一个元素。
3,以Person对象的年龄进行从小到大的排序。
将上面的Person类实现Comparable接口,然后在Person中覆写compareTo方法。
//若要按照从大到小排序,把1变为-1,把-1变为1即可,或把this变为p,把p变为this.
public int compareTo(Object obj) {
Person p = (Person)obj;
if(this.age > p.age)
return 1;
if(this.age < p.age)
return -1;
return 0;
}
但是如果年龄相同,则后一个就打印不出来了,即:打印时少了一个Person。
这时应该加条件,如果主要条件相同,则应该比较次要条件。
修改后的compareTo方法:
public int compareTo(Object obj) {
Person p = (Person)obj;
if(this.age > p.age)
return 1;
if(this.age < p.age)
return -1;
else
/*
String类中的compareTo方法也来自于Comparable接口,
String类实现了Comparable接口
*/
return this.name.compareTo(p.name);
}
但是这么写烂透了...,优化:
public int compareTo(Object obj) {
Person p = (Person)obj;
int temp = this.age - p.age;
return temp == 0 ? this.name.compareTo(p.name) : temp;
}
之前在TreeSet中存String类的对象,可以进行排序,是因为String类实现了Comparable接口。
总结:TreeSet对元素进行排序的方式之一:
让元素自身具备比较功能,元素需要实现Comparable接口,覆盖compareTo方法。
16-11,TreeSet集合,Comparator比较器
1,如果不要按照对象中具备的自然顺序,如果对象中不具备自然顺序,怎么办?
可以使用TreeSet的第二种排序方式:让集合自身具备比较功能。
2,TreeSet的构造器中有一个带有Comparator参数的构造器,在创建TreeSet对象时传入此Comparator的对象,该TreeSet可按指定的Comparator比较器进行排序。
3,java.util
接口:Comparator<T>
强行对某个对象Collection进行整体排序的比较函数。
4,单独创建一个Java文件,创建一个根据Person类的name进行排序的比较器。
public class ComparatorsByName implements Comparator {
public int compare(Object o1,Object o2) {
Person p1 = (Person)o1;
Person p2 = (Person)o2;
int temp = p1.getName().compareTo(p2.getName());
return temp == 0 ? p1.getAge() - p2.getAge() : temp;
}
}
然后,在创建TreeSet集合的时候,直接:
TreeSet ts = new TreeSet(new ComparatorsByName());
即可。
5,如果Person对象本身实现了Comparable接口,覆盖了compareTo方法,而且把Person对象存入了已经实现了比较器的TreeSet中,那么以谁的比较功能为主呢?
以比较器的功能为主,且实际开发中比较器比较常用。
实际上Java中的很多类都实现了Comparable接口,使其对象具备比较功能。
6,使集合自身具备比较功能的步骤:
定义一个类实现Comparator接口,覆盖compare方法,将该类对象作为参数传递个TreeSet集合的构造函数。
16-12,TreeSet-二叉树
1,TreeSet排序功能在底层是如何实现的呢?
他的排序功能在底层是通过二叉树来实现的。
以下列TreeSet的添加为例,用图表示(根据Person的age排序),
第一个进去的元素不用进行比较,以后进去的元素都要与前面的元素比较。
步骤:
第一个28进来以后,发现前面没有元素,不做比较,直接放在第一个位置。
21进来后,跟28比较,比28小,放在28的左边。
29进来后先跟28比较,比28大,放在28的右边,就不用和21比较了。
25进来后先跟28比较,比28小,到左边,再跟21比较,比21大,则放在21的右边。
......
在树中,每个节点都有自己的属性,以值为21的节点举例:
21有三个引用,分别记录与之链接的父节点,以及左子节点和右子节点,以记录整个集合各个元素的位置,但28没有父,19没有左和右。
但是树的规模一大,就涉及到效率问题,如何解决呢?
其实,只要树一排完,该集合就已经是一个有序的集合了,有序集合的查找,就可以想到用二分查找法。
其实每次向里面添加元素的时候,都会对已有的元素进行折半,二分查找,以确定新元素的位置,效率较高。
2,如何做到怎么存就怎么取的呢?
直接在比较器中返回1就行了,这样可以保证后进来的元素经比较后总是大于前面的元素,如果要倒序取出的话,直接返回一个-1即可。
public class ComparatorsByName implements Comparator {
public int compare(Object o1,Object o2) {
return 1;
}
}
16-13,TreeSet练习
需求:按照字符串的长度排序。
public class TreeSetTest {
public static void main(String[] args) {
TreeSet ts = new TreeSet(new ComparatorsByLength());
ts.add("aaaaaa");
ts.add("zz");
ts.add("nabq");
ts.add("cba");
ts.add("abc");
Iterator it = ts.iterator();
while(it.hasNext()) {
System.out.println(it.next());
}
}
}
//比较器
public class ComparatorsByLength implements Comparator {
public int compare(Object o1,Object o2) {
String s1 = (String)o1;
String s2 = (String)o2;
int temp = s1.length() - s2.length();
return temp == 0 ? s1.compareTo(s2) : temp;
}
}