欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java学习笔记 集合

程序员文章站 2024-02-16 10:37:52
...

一,集合
1.集合和数组的区别
数组长度是固定的,集合长度是可变的;数组中可以窜出基本数据类型,集合只能存储对象;数组中存储数据类型是单一的,集合中可以存储任意类型的对象。
2.集合框架体系
java学习笔记 集合

java学习笔记 集合

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 "aaa@qq.com:" + 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();

使用一张图可能会更好地理解
java学习笔记 集合

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);
    }
}
/*
[西游记, 三国演义, 石头记, 水浒传]
西游记
三国演义
石头记
水浒传
水浒传
石头记
三国演义
西游记
[西游记, 三国演义, 石头记, 水浒传]
石头记
[西游记, 三国演义, 石头记, 平凡的世界, 水浒传]
*/

集合未完……