一,集合
1.集合和数组的区别
数组长度是固定的,集合长度是可变的;数组中可以窜出基本数据类型,集合只能存储对象;数组中存储数据类型是单一的,集合中可以存储任意类型的对象。
2.集合框架体系
List:有序存储,可重复
——ArrayList:数组实现,查找块,增删慢
——LinkedList:链表实现,增删块,查找慢
——Vector:和ArrayList原理相同,但线程安全,效率略低
Set:无存储顺序,不可重复
——HashSet:按Hash算法存储集合中的元素,有很好的存取和查找性能,不同步
——LinkedHashSet:根据元素的hashCode决定元素存储位置,同时使用链表维护元素的次序,按照元素添加顺序访问集合元素
——TreeSet:采用红黑树的数据结构存储集合元素,根据元素实际值的大小进行排序
——EnumSet:以枚举值在Enum类内的定义顺序决定集合元素的顺序,在内部以位向量的形式存储
3.总结,什么时候该使用什么样的集合
List:我们需要存储顺序,并且保留重复元素;如果查询较多,使用ArrayList;如果存取较多,使用LinkedList;如果需要线程安全,使用Vector
Set:不需要保留存储顺序,并且需要去掉重复元素;如果需要将元素排序,使用TreeSet;如果不需要排序,使用HashSet,HashSet比TreeSet效率高;如果需要保留存储顺序,又要过滤重复元素,使用LinkedHashSet
二,集合类
1.Collection接口共性方法
package Collection;
import java.util.*;
public class CollectionTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
// 增加,输出
// [Spring, Summer, Autumn]
// [Winter, Spring, Summer, Autumn, January]
Collection list=new ArrayList();
list.add("Spring");
list.add("Summer");
list.add("Autumn");
System.out.println(list);
Collection list2=new ArrayList();
list2.add("Winter");
list2.addAll(list);
list2.add("January");
System.out.println(list2);
// 删除,输出
// true
// [Winter, Spring, Summer, Autumn]
// true
// [Winter]
boolean remove=list2.remove("January");
System.out.println(remove);
System.out.println(list2);
boolean removeAll=list2.removeAll(list);
System.out.println(removeAll);
System.out.println(list2);
// 修改
// [Winter, Spring, Summer, Autumn, January]
// []
System.out.println(list2);
list2.clear();
System.out.println(list2);
// 判断
// false
// true
boolean empty=list.isEmpty();
System.out.println(empty);
Boolean contains=list2.contains("January");
System.out.println(contains);
// 获取长度
System.out.println(list2.size());
}
}
注意:以上代码时对增加、删除、修改、判断和获取等方法的示例,实际运行时需要注释掉部分代码。
2.List
判断两个对象相等,只要通过equals方法比较返回true即可
如下代码,仅以增加作为示例
package Collection;
import java.util.*;
/*
* list集合特有的方法
* 1.增加:void add(int index,E element)指定位置添加元素
* boolean addAll(int index,Collection c)指定位置添加集合
* 2.删除:E remove(int index)删除至东京位置元素
* 3.修改:E set(int index,E element)返回的是要替换的集合中的元素
* 4.查找:E get(int index)
* int indexOf(Object o) 找不到返回-1
* lastIndexOf(Object o)
* 5.求子集合:List<E> subList(int fromIndex, int toIndex)不包含toIndex
*/
public class ListTest {
public static void main(String [] args){
List list=new ArrayList();
list.add("Spring");
list.add("Summer");
list.add("Autumn");
System.out.println(list);//[Spring, Summer, Autumn]
list.add(0,"Winter");
System.out.println(list);//[Winter, Spring, Summer, Autumn]
List list2=new ArrayList();
list2.add("January");
list2.add("February");
list2.add("March");
System.out.println(list2);//[January, February, March]
boolean addAll=list2.addAll(1,list);
System.out.println(addAll);//true
// [January, Winter, Spring, Summer, Autumn, February, March]
System.out.println(list2);
}
}
3.ArrayList:数组实现,查找快,增删慢
数组的内存空间地址是连续的,ArrayList底层维护了一个Object[]用于存储对象,默认数组长度是10,可以通过new ArrayList(20)显式指定用于存储对象的数组的长度。数组可以直接按索引查找,所以较快;在增删时会牵扯到数组增容以及拷贝元素,所以慢。
Eg:去除ArrayList集合中重复元素
注意:自定义对象要进行复写toString和equals方法,因为Object是Person的父类,Object中toString返回的是哈希值,equals比较的是对象的地址值
原理:循环遍历该集合,每取出一个放置在行的集合中,放置之前先判断新集合是否已经包含了新的元素
package Collection;
import java.util.*;
class Person {
private String name;
private int age;
public Person() {
}
public Person(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 int hashCode() {
return this.name.hashCode() + age * 37;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Person)) {
return false;
}
Person p = (Person) obj;
return this.name.equals(p.name) && this.age == p.age;
}
@Override
public String toString() {
return "Person@name:" + this.name + " age:" + this.age;
}
}
public class ArrayListTest {
public static void main(String [] args){
ArrayList arr = new ArrayList();
Person p1 = new Person("jack", 20);
Person p2 = new Person("rose", 18);
Person p3 = new Person("rose", 18);
arr.add(p1);
arr.add(p2);
arr.add(p3);
System.out.println(arr);
ArrayList arr2 = new ArrayList();
for(int i=0;i<arr.size();i++){
Object obj=arr.get(i);
Person p=(Person)obj;
if(!(arr2.contains(p))){
arr2.add(p);
}
}
System.out.println(arr2);
}
}
4.LinkedList:链表实现,增删块,查找慢
package Collection;
import java.util.*;
/*
* 特有的方法
* addFirst(E e) addLast(E e) getFirst() getLast()
* removeFirst() removeLast()
* 如果集合中没有元素,获取或者删除元素会抛出异常NoSuchElementException
* 数据结构
* 栈:先进后出push() pop()
* 队列:先进先出offer() poll()
* descendingIterator() 返回逆序的迭代器对象
*/
public class LinkedListTest {
public static void main(String [] args){
LinkedList list = new LinkedList();
list.add("西游记");
list.add("三国演义");
list.add("石头记");
list.add("水浒传");
list.add("全球通史");
list.addFirst("史记");
list.addLast("呐喊");
// list.addFirst(null);
// list.addLast(null);
System.out.println(list);
// 获取指定位置处的元素。
String str = (String) list.get(0);
System.out.println(str);
// 返回此列表的第一个元素。
String str2 = (String) list.getFirst();
System.out.println(str2);
System.out.println(str.equals(str2));
// 获取指定位置处的元素。
String str3 = (String) list.get(list.size() - 1);
// 返回此列表的最后一个元素。
String str4 = (String) list.getLast();
System.out.println(str3.equals(str4));
// 获取但不移除此列表的头(第一个元素)。
Object element = list.element();
System.out.println(element);
int size = list.size();
System.out.println(size);
}
}
/*
[史记, 西游记, 三国演义, 石头记, 水浒传, 全球通史, 呐喊]
史记
史记
true
true
史记
7
*/
练习:使用集合模拟队列(先进先出)或者堆栈(后进先出)数据结构,以堆栈为例
package Collection;
import java.util.*;
public class ZhanTest {
public static void main(String [] args){
LinkedList list = new LinkedList();
// 压栈,先进后出
list.push("西游记");
list.push("三国演义");
list.push("石头记");
list.push("水浒传");
System.out.println(list);
// 弹栈
String str1 = (String) list.pop();
System.out.println(str1);
String str2 = (String) list.pop();
System.out.println(str2);
String str3 = (String) list.pop();
System.out.println(str3);
String str4 = (String) list.pop();
System.out.println(str4);
System.out.println(list.size());// 0
System.out.println(list); //[]
}
}
/*
[水浒传, 石头记, 三国演义, 西游记]
水浒传
石头记
三国演义
西游记
0
[]
*/
5.Vector:描述的是一个线程安全的ArrayList
package Collection;
import java.util.*;
public class VectorTest {
public static void main(String [] args){
Vector v = new Vector();
v.addElement("aaa");
v.addElement("bbb");
v.addElement("ccc");
System.out.println( v );
System.out.println( v.elementAt(2) ); // ccc
// 遍历Vector遍历
Enumeration ens = v.elements();
while ( ens.hasMoreElements() )
{
System.out.println( ens.nextElement() );
}
}
}
/*
[aaa, bbb, ccc]
ccc
aaa
bbb
ccc
*/
6.Set:用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复
如果想要两个不同的Person对象视为相等的,必须覆盖Object继承下来的hashCode方法和equals方法。
判断两个元素相等的标准是,两个对象通过equals方法比较相等且hashCode返回值也相等。
7.HashSet:Set接口的典型实现,不能保证元素的排列顺序,不是同步的,线程不安全,存取速度快
HashSet存储元素的顺序并不是按照存入时的顺序,是按照哈希值来存的,所以取数据也是按照哈希值取的。
HashSet不能存入重复元素,那么HashSet如何检查重复?
当你试图把对象加入HashSet时,HashSet会使用对象的hashCode来判断对象加入的位置。同时也会与其他已经加入的对象的hashCode进行比较,如果没有相等的hashCode,HashSet就会假设对象没有重复出现。简单一句话,如果对象的hashCode值是不同的,那么HashSet会认为对象是不可能相等的。因此我们自定义类的时候需要重写hashCode,来确保对象具有相同的hashCode值。如果元素(对象)的hashCode值相同,是不是就无法存入HashSet中了? 当然不是,会继续使用equals 进行比较.如果 equals为true 那么HashSet认为新加入的对象重复了,所以加入失败。如果equals 为false那么HashSet 认为新加入的对象没有重复.新元素可以存入.
总结概括起来就是:元素的哈希值是通过元素的hashcode方法 来获取的, HashSet首先判断两个元素的哈希值,如果哈希值一样,接着会比较equals方法 如果 equls结果为true ,HashSet就视为同一个元素。如果equals 为false就不是同一个元素。
8.LinkedHashSet
HashSet的一个子类,根据元素的hashCode来决定元素的存储位置,但它同时使用链表维护元素的次序,这样使得元素看起来是以插入的顺序保存的。也就是说,当遍历LinkedHashSet集合里的元素时,LinkedHashSet会按元素的添加顺序来访问集合里的元素。
LinkedHashSet需要维护元素的插入顺序,因此性能略低于HashSet,但在迭代访问Set里的全部元素时有很好的性能,因为它以链表来维护内部顺序。
package Collection;
import java.util.*;
public class LinkedHashSetTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
LinkedHashSet books = new LinkedHashSet();
books.add("疯狂Java讲义");
books.add("Spring实战");
System.out.println(books);
// 删除 疯狂Java讲义
books.remove("疯狂Java讲义");
// 重新添加 疯狂Java讲义
books.add("疯狂Java讲义");
System.out.println(books);
}
}
/*
[疯狂Java讲义, Spring实战]
[Spring实战, 疯狂Java讲义]
*/
9.TreeSet:采用红黑树的数据结构存储集合元素,根据元素实际值的大小进行排序
package Collection;
import java.util.*;
public class TreeSetTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
TreeSet nums = new TreeSet();
// 向TreeSet中添加四个Integer对象
nums.add(5);
nums.add(2);
nums.add(10);
nums.add(-9);
// 输出集合元素,看到集合元素已经处于排序状态
System.out.println(nums);
// 输出集合里的第一个元素
System.out.println(nums.first()); // 输出-9
// 输出集合里的最后一个元素
System.out.println(nums.last()); // 输出10
// 返回小于4的子集,不包含4
System.out.println(nums.headSet(4)); // 输出[-9, 2]
// 返回大于5的子集,如果Set中包含5,子集中还包含5
System.out.println(nums.tailSet(5)); // 输出 [5, 10]
// 返回大于等于-3,小于4的子集。
System.out.println(nums.subSet(-3 , 4)); // 输出[2]
}
}
/*
[-9, 2, 5, 10]
-9
10
[-9, 2]
[5, 10]
[2]
*/
TreeSet支持两种排序方法:自然排序和定制排序,默认情况下,采用自然排序。
(1)自然排序,元素自身具备比较性,元素需要实现Comparable接口,覆盖compareTo方法,集合元素按照升序排序。这也就是为什么TreeSet存入字符串时,字符串默认输出是按升序排列的,因为字符串实现了Comparable接口并重写了compareTo方法,所以String对象具备了比较性。
package test;
import java.util.*;
class Person implements Comparable{
private String name;
private int age;
private String gender;
public Person() {
}
public Person(String name, int age, String gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
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;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int hashCode() {
return name.hashCode() + age * 37;
}
public boolean equals(Object obj) {
System.err.println(this + "equals :" + obj);
if (!(obj instanceof Person)) {
return false;
}
Person p = (Person) obj;
return this.name.equals(p.name) && this.age == p.age;
}
public String toString() {
return "Person [name=" + name + ", age=" + age + ", gender=" + gender
+ "]";
}
public int compareTo(Object obj) {
Person p = (Person) obj;
System.out.println(this+" compareTo:"+p);
if (this.age > p.age) {
return 1;
}
if (this.age < p.age) {
return -1;
}
return this.name.compareTo(p.name);
}
}
public class TreeSetTest{
public static void main(String[] args) {
// TODO Auto-generated method stub
TreeSet ts = new TreeSet();
ts.add(new Person("aa", 20, "男"));
ts.add(new Person("bb", 18, "女"));
ts.add(new Person("cc", 17, "男"));
ts.add(new Person("dd", 17, "女"));
ts.add(new Person("dd", 15, "女"));
ts.add(new Person("dd", 15, "女"));
System.out.println(ts);
System.out.println(ts.size()); // 5
}
}
(2)定制排序,当元素自身不具备比较性,或者元素吱声比较性不是所需的时候,使用定制排序。定义一个类实现Comparator接口,覆盖compare方法,并将该接口的子类对象作为参数传递给TreeSet集合的构造函数。由于Comparator是一个函数式接口,因此可以用Lambda表达式代替Comparator对象。
package Collection;
import java.util.*;
class M
{
int age;
public M(int age)
{
this.age = age;
}
public String toString()
{
return "M[age:" + age + "]";
}
}
public class TreeSetTest4
{
public static void main(String[] args)
{
// 此处Lambda表达式的目标类型是Comparator
TreeSet ts = new TreeSet((o1 , o2) ->
{
M m1 = (M)o1;
M m2 = (M)o2;
// 根据M对象的age属性来决定大小,age越大,M对象反而越小
return m1.age > m2.age ? -1
: m1.age < m2.age ? 1 : 0;
});
ts.add(new M(5));
ts.add(new M(-3));
ts.add(new M(9));
System.out.println(ts);
}
}
/*
[M[age:9], M[age:5], M[age:-3]]
*/
小练习:将字符串中的数值进行排序,可以使用TreeSet完成,因为TreeSet自身具备排序功能。
package Collection;
import java.util.*;
public class TreeSetEx {
public static void main(String[] args) {
// TODO Auto-generated method stub
String str = "8 10 15 5 2 7";
String[] strs = str.split(" ");
TreeSet ts = new TreeSet();
for (int x = 0; x < strs.length; x++) {
int y = Integer.parseInt(strs[x]);
ts.add(y);
}
System.out.println(ts);
}
}
/*
[2, 5, 7, 8, 10, 15]
*/
三,迭代器
为了方便处理集合中的元素,Java中出现了一个对象,该对象提供了一些方法专门处理集合中的元素,例如删除和获取集合中的元素,该对象就叫做迭代器(Iterator)。
1.Iterable
jdk1.5之后添加的新接口,Collection的父接口,实现了Iterable的类就是可迭代的,并支持增强for循环。该接口只有一个方法即获取迭代器的方法iterable(),可以获取每个容器自身的迭代器Iterator。
2.Iterator
Iterator主要用于遍历(即迭代访问)Collection集合中的元素,该接口隐藏了各种Collection实现类的底层细节,向应用程序提供了遍历Collection集合元素的同一编程接口,Iterator接口定义了如下4个方法:
boolean hasNext(): 如果被迭代的集合元素还没有被遍历完,返回true
Object next(): 返回集合里的下一个元素
void remove(): 删除集合里上一次next方法返回的元素
void forEachRemaining(Consumer action): java8为Iterator新增的默认方法,该方法可使用Lambda表达式遍历集合元素
3.迭代器遍历的几个例子
while循环遍历集合
package Collection;
import java.util.*;
/*
* while循环
*/
public class IteratorWhile {
public static void main(String []args){
ArrayList list=new ArrayList();
list.add("计算机网络");
list.add("现代操作系统");
list.add("java编程思想");
list.add("java核心技术");
list.add("java语言程序设计");
System.out.println(list);
Iterator it=list.iterator();
while(it.hasNext()){
String next=(String)it.next();
System.out.println(next);
}
}
}
/*
[计算机网络, 现代操作系统, java编程思想, java核心技术, java语言程序设计]
计算机网络
现代操作系统
java编程思想
java核心技术
java语言程序设计
*/
利用for循环遍历集合
package Collection;
import java.util.*;
/*
* for循环
* 需要去除全部元素时,可以通过循环,java建议使用for循环,因为可以对内存进行一下优化
*/
public class IteratorWhile {
public static void main(String []args){
ArrayList list=new ArrayList();
list.add("计算机网络");
list.add("现代操作系统");
list.add("java编程思想");
list.add("java核心技术");
list.add("java语言程序设计");
System.out.println(list);
for(Iterator it=list.iterator();it.hasNext();){
String next=(String)it.next();
System.out.println(next);
}
}
}
/*
[计算机网络, 现代操作系统, java编程思想, java核心技术, java语言程序设计]
计算机网络
现代操作系统
java编程思想
java核心技术
java语言程序设计
*/
使用迭代器清空集合,注意两个细节:
(1)如果迭代器的指针已经指向了集合的末尾,那么如果再调用next()会返回NoSuchElementException异常
(2)如果调用remove之前没有调用next是不合法的,会抛出IllegalStateException异常,remove方法会返回上次调用next方法时返回的元素,如果想要删除指定位置上的元素,需要越过这个元素,例如,下面是如何删除字符串集合中第一个元素的方法
Iterator<String> it=c.iterator();
it.next();
it.remove();
如果想要删除两个相邻的元素,不能直接这样
it.remove();
it.remove();//Error!
相反的,必须先调用next越过将要删除的元素
it.remove();
it.next();
it.remove();
使用一张图可能会更好地理解
package Collection;
import java.util.*;
public class IteratorTest {
public static void main(String[] args) {
// TODO Auto-generated method stub
ArrayList list=new ArrayList();
list.add("Spring");
list.add("Summer");
list.add("Autumn");
list.add("Winter");
System.out.println(list);
Iterator it=list.iterator();
while(it.hasNext()){
it.next();
it.remove();
}
System.out.println(list);
// String next=(String)it.next();
// System.out.println(next);
// while(it.hasNext()){
// String next2=(String)it.next();
// System.out.println(next2);
// }
}
}
4.List特有的迭代器ListIterator
如果是List集合,想要在迭代中操作元素可以使用List集合特有的迭代器ListIterator,该迭代器支持在迭代过程中,添加元素和修改元素。
package Collection;
import java.util.*;
/*
* 特有的方法
* add 将制定的元素插入列表,该元素直接插入到next返回的下一个元素的前面
* set 用制定元素替换next或previous返回的最后一个元素
* hasPrevious 逆向遍历列表
* previous 返回列表的前一个元素
*/
public class LinkedListTest {
public static void main(String [] args){
LinkedList list = new LinkedList();
list.add("西游记");
list.add("三国演义");
list.add("石头记");
list.add("水浒传");
System.out.println(list);
ListIterator lit=list.listIterator();
while(lit.hasNext()){
String next=(String)lit.next();
System.out.println(next);
}
while(lit.hasPrevious()){//倒序遍历
String previous=(String)lit.previous();
System.out.println(previous);
}
System.out.println(list);
lit.next();
lit.next();
System.out.println(lit.next());
lit.add("平凡的世界");
//add方法将指定元素插入 列表,该元素直接插入到next返回的元素后面
System.out.println(list);
}
}
/*
[西游记, 三国演义, 石头记, 水浒传]
西游记
三国演义
石头记
水浒传
水浒传
石头记
三国演义
西游记
[西游记, 三国演义, 石头记, 水浒传]
石头记
[西游记, 三国演义, 石头记, 平凡的世界, 水浒传]
*/
集合未完……