Java集合之单列集合

  • 什么是集合

集合、数组都是对多个数据进行存储操作的结构,简称Java容器。
说明:此时的存储,主要指的是内存层面的存储,不涉及到持久化的存储(.txt,.jpg,.avi,数据库中)

  • 集合和数组的区别

数组在存储多个数据方面的特点

> 一旦初始化以后,其长度就确定了。
> 数组一旦定义好,其元素的类型也就确定了。我们也就只能操作指定类型的数据了。比如:String[] arr;int[] arr1;Object[] arr2;只能存储对于定义的类型元素

数组在存储多个数据方面的缺点(集合可以很好的解决)

> 一旦初始化以后,其长度就不可修改。
> 数组中提供的方法非常有限,对于添加、删除、插入数据等操作,非常不便,同时效率不高。
> 获取数组中实际元素的个数的需求,数组没有现成的属性或方法可用
> 数组存储数据的特点:有序、可重复。对于无序、不可重复的需求,不能满足。

总结

>数组是固定长度的;集合可变长度的。
>数组可以存储基本数据类型,也可以存储引用数据类型;集合只能存储引用数据类型。
>数组存储的元素必须是同一个数据类型;集合存储的对象可以是不同数据类型(其实集合一般存储的数据也是同一类型的)。

  • 集合框架(常用集合类)

Collection 单列集合,用来存储一个一个的对象
|——- List 接口:元素按进入先后有序保存,可重复(”动态“数组)
|—————- LinkedList 接口实现类, 底层是链表实现的,增和删比较快,查找和修改比较慢,没有同步, 线程不安全
|—————- ArrayList 接口实现类, 底层是数组实现的, 随机访问,查找和修改快,增和删比较慢, 没有同步,线程不安全
|—————- Vector 接口实现类 底层是数组实现的,同步,线程安全
|———————- Stack 是Vector类的实现类
|——- Set接口:存储无序的、不可重复的数据   -->高中讲的“集合”
|—————- HashSet 使用hash表(数组)存储元素
|————————- LinkedHashSet 链表维护元素的插入次序,可以保证存储的元素唯一
|—————-TreeSet 底层是二叉树算法实现,元素排好序

Map接口:双列集合,用来存储一对(key - value)一对的数据   -->高中函数:y = f(x)
|———- Hashtable 接口实现类, 同步, 线程安全
|———- HashMap 接口实现类 ,没有同步, 线程不安全-
|—————–- LinkedHashMap 双向链表和哈希表实现
|—————–- WeakHashMap
|——–- TreeMap 红黑树对所有的key进行排序
|———- IdentifyHashMap

  • Collection接口中的方法的使用

在集合末尾添加元素——boolean add(E e)

@Test
    public void test1(){
        Collection coll = new ArrayList();
        coll.add("aaa");
        coll.add(123);//自动装箱
        coll.add(new Date());
        System.out.println(coll);
    }

获取添加的元素的个数——int size()

System.out.println(coll.size());//3

将集合中的元素添加到当前的集合中——boolean addll(Collection c)

Collection coll1 = new ArrayList();
coll1.add("bbb");
coll.addAll(coll1);
System.out.println(coll);//[aaa, 123, Fri Jun 10 15:30:17 CST 2022, bbb]

清空集合中所有的元素——void clear()

coll.clear();
System.out.println(coll);//[]

判断当前集合是否为空——boolean isEmpty()

System.out.println(coll.isEmpty());//true

判断集合中是否包含某元素——boolean contains(Object o)   

Collection coll = new ArrayList();
coll.add(123);
coll.add(new String("嘻戏i"));
coll.add(new Person("嘻戏i",31));
System.out.println(coll.contains(123));//true
System.out.println(coll.contains(new String("嘻戏i")));//true
//我们在判断时会调用obj对象所在类的equals()。Person类未重写equals方法则该处为false,反之为true
System.out.println(coll.contains(new Person("嘻戏i",31)));//false -->true

判断某一集合中的所有元素是否都存在于当前集合中——boolean containsAll(Collection c)

Collection coll1 = Arrays.asList(123,"嘻戏i");
System.out.println(coll.containsAll(coll1));//true

从集合中移除某一元素——boolean remove(Object o)

coll.remove(123);
System.out.println(coll);//[嘻戏i, Person{name='嘻戏i', age=31}]

从集合中移除某一集合中所有的元素(差集)——boolean removeAll(Collection c)

coll.removeAll(coll1);
System.out.println(coll);//[Person{name='嘻戏i', age=31}]去除和coll1相同的元素

获取当前集合和另一集合的交集,并返回给当前集合boolean retainAll(Collection c)

Collection coll = new ArrayList();
coll.add(123);
coll.add("嘻戏i");
coll.add(new Person("嘻戏i",31));
coll.add(false);

Collection coll1 = Arrays.asList(123,"嘻戏i","aaa");
coll.retainAll(coll1);
System.out.println(coll);//[123, 嘻戏i]
System.out.println(coll1);//[123, 嘻戏i, aaa]

判断当前集合和形参集合是否相同——boolean equals(Object o)

boolean equals = coll.equals(coll1);
//要想返回true 需要当前集合和形参集合的元素都相同。(因为我们这里使用的是collection子类ArrayList是有序的,所有元素的位置也需要一致)
System.out.println(equals);

返回当前对象的哈希值——int hashcode()

int i = coll.hashCode();
System.out.println(i);

集合转数组——Object[] toArray()

Collection coll = new ArrayList();
coll.add("嘻戏i");
coll.add(123);
coll.add(new Person("嘻戏i",31));

Object[] objects = coll.toArray();
for (int i = 0; i < objects.length; i++) {
    System.out.println(objects[i]);
}

数组转集合——调用Arrays类的静态方法asList()

List list = Arrays.asList(new String[]{"aa","bb","cc"});
System.out.println(list);//[aa, bb, cc]

//基本数据类型的数组转化成集合时,会将整个数组默认一个整体
List list1 = Arrays.asList(new int[]{111,222,333});
System.out.println(list1);//[[I@3d82c5f3]

List list2 = Arrays.asList(new Integer[]{111,222,333});
System.out.println(list2);//[111, 222, 333]

遍历集合元素:迭代器——Iterator iterator()

public void test5() {
    Collection coll = new ArrayList();
    coll.add("嘻戏");
    coll.add(123);
    coll.add(new Person("嘻戏", 28));

    Iterator iterator = coll.iterator();//获取迭代器
    while (iterator.hasNext()) { //hasNext():判断是否还有下一个元素
        System.out.println(iterator.next()); //next():①指针下移 ②将下移以后集合位置上的元素返回
    }
}
  •  遍历集合元素的方法:

使用迭代器Iterator

public void test5() {
Collection coll = new ArrayList();
coll.add("嘻戏");
coll.add(123);
coll.add(new Person("嘻戏", 28));

Iterator iterator = coll.iterator();//获取迭代器
//方式一:不推荐
//        for (int i = 0; i < coll.size(); i++) {
//            System.out.println(iterator.next());
//        }
//方式二:推荐
while (iterator.hasNext()) { //hasNext():判断是否还有下一个元素
    System.out.println(iterator.next()); //next():①指针下移 ②将下移以后集合位置上的元素返回
}

使用迭代器Iterator的错误方式

下面三种错误方式代码用到的集合

Collection coll = new ArrayList();
coll.add("嘻戏");
coll.add(123);
coll.add(new Person("嘻戏", 28));
错误方式1: 报异常:NoSuchElementException
//方式1.1
Iterator iterator1 = coll.iterator();
while (iterator1.next() != null){
    System.out.println(iterator1.next());
}
//方式1.2
System.out.println(iterator.next());
System.out.println(iterator.next());
//报异常:NoSuchElementException
System.out.println(iterator.next());
错误方式2 一直无限循环下去
//集合对象每次调用iterator()方法都得到一个全新的迭代器对象,默认游标都在集合的第一个元素之前。
while (coll.iterator().hasNext()){
    System.out.println(coll.iterator().next());
}
//错误方式3 关于Iterator中的remove()
//如果还未调用next()或在上一次调用 next 方法之后已经调用了 remove 方法,再调用remove都会报IllegalStateException。
Iterator iterator2 = coll.iterator();
while (iterator2.hasNext()){
//            iterator2.remove(); //报异常:IllegalStateException
    //正确操作:删除集合中的new Person("嘻戏",28)
    Object obj = iterator2.next();
    if (new Person("嘻戏",28).equals(obj)){
        iterator2.remove();
    }
}
iterator2 = coll.iterator();
while (iterator2.hasNext()){
    System.out.println(iterator2.next());
}

增强for循环遍历集合元素

Collection coll = new ArrayList();
coll.add(123);
coll.add("嘻戏");
coll.add(new Person("嘻戏",28));

//for(集合元素的类型 局部变量 : 集合对象)
//内部仍然调用了迭代器。
for (Object obj: coll) {
    System.out.println(obj);
}

System.out.println("*****************");
//for循环和增加for循环
String[] arr = new String[]{"MM","MM","MM"};
for (int i = 0; i < arr.length; i++) {
//           arr[i] = "GG";//改变了字符串arr
}

for (String s :
        arr) {
    s = "GG";//只改变了局部变量s,未改变字符串arr
//            System.out.println(s);
}
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}
  • List接口框架

|----Collection接口:单列集合,用来存储一个一个的对象
          |----List接口:存储有序的、可重复的数据。  -->“动态”数组,替换原有的数组
              |----ArrayList:作为List接口的主要实现类;线程不安全的,效率高;底层使用Object[] elementData存储
              |----LinkedList:对于频繁的插入、删除操作,使用此类效率比ArrayList高;底层使用双向链表存储
              |----Vector:作为List接口的古老实现类;线程安全的,效率低;底层使用Object[] elementData存储

 ArrayList的源码分析

1 jdk 7情况下
       ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
       list.add(123);//elementData[0] = new Integer(123);

       list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
       默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
 
       结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
2 jdk 8中ArrayList的变化:
      ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组
      list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]

      后续的添加和扩容操作与jdk 7 无异。
3 小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。

LinkedList的源码分析

LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
      list.add(123);//将123封装到Node中,创建了Node对象。

      其中,Node定义为:体现了LinkedList的双向链表的说法

private static class Node<E> {
             E item;
             Node<E> next;
             Node<E> prev;

             Node(Node<E> prev, E element, Node<E> next) {
             this.item = element;
             this.next = next;
             this.prev = prev;
             }
         }

Vector的源码分析

jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。在扩容方面,默认扩容为原来的数组长度的2倍。

总结

ArrayList、LinkedList、Vector三者的异同:

同:三个类都是实现了List接口,存储数据的特点相同:存储有序的、可重复的数据

异:通过上面的源代码分析

 List接口中的常用方法

增:

在index位置插入元素 ——void add(int index, Object ele)

list.add(1,456);

从index位置开始将另外集合中的所有元素添加进来 ——boolean addAll(int index, Collection eles)

List list1 = Arrays.asList(1,2,3);
//从list索引2的位置开始将list1中的所有元素添加进来
list.addAll(2,list1);
//从list末尾的位置开始将list1中的所有元素当做一个整体添加进来
list.add(list1);

删:

 移除指定索引位置的元素,并返回此元素——Object remove(int index) / boolean remove(Object obj)

//删除list集合中索引为2的元素,并返回该元素
Object Obj = list.remove(2);
System.out.println(Obj);
//删除list集合中list1集合,有就返回true,没有返回false
boolean obj1 = list.remove(list1);
System.out.println(obj1);

改:

 设置指定索引位置的元素为其他元素——Object set(int index, Object ele)

Object obj2 = list.set(3, 33);
System.out.println(obj2);

查:

获取指定索引位置的元素 ——Object get(int index)

Object obj3 = list.get(3);
System.out.println(obj3);

获取集合长度——int size()

int size = list.size();

返回obj在集合中首次出现的位置(集合中不存在查询的元素返回-1)——int indexOf(Object obj)

int i = list.indexOf(123);

返回obj在当前集合中末次出现的位置——int lastIndexOf(Object obj)

int i2 = list.lastIndexOf(123);

返回从fromIndex到toIndex位置的子集合(左闭右开)——List subList(int fromIndex, int toIndex)

List list2 = list.subList(1, 4);

List集合遍历

Iterator迭代器方式

Iterator iterator = list.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());
}

增强for循环

for (Object obj : list) {
    System.out.println(obj);
}

普通for循环(List集合有索引,结合get方法,可以用普通for循环)

for (int j = 0; j < list.size(); j++) {
    System.out.println(list.get(j));
}
  • Set接口的框架

|----Collection接口:单列集合,用来存储一个一个的对象
          |----Set接口:存储无序的、不可重复的数据   -->高中讲的“集合”
              |----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
                  |----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
                                      对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
              |----TreeSet:可以按照添加对象的指定属性,进行排序。

说明

1. Set接口中没有额外定义新的方法,使用的都是Collection中声明过的方法。

 2.1 要求:向Set(主要指:HashSet、LinkedHashSet)中添加的数据,其所在的类一定要重写hashCode()和equals()

2.2要求:重写的hashCode()和equals()尽可能保持一致性:相等的对象必须具有相等的散列码    重写两个方法的小技巧:对象中用作 equals() 方法比较的 Field,都应该用来计算 hashCode 值。

 Set集合存储元素的特征

存储无序的、不可重复的数据 

 1. 无序性:不等于随机性。存储的数据在底层数组中并非按照数组索引的顺序添加,而是根据数据的哈希值决定的。

 2. 不可重复性:保证添加的元素按照equals()判断时,不能返回true.即:相同的元素只能添加一个。

Set集合添加元素的过程分析(以HashSet为例) 

我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
        此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断
        数组此位置上是否已经有元素:
            如果此位置上没有其他元素,则元素a添加成功。 --->情况1
            如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
                如果hash值不相同,则元素a添加成功。--->情况2
                如果hash值相同,进而需要调用元素a所在类的equals()方法:
                       equals()返回true,元素a添加失败
                       equals()返回false,则元素a添加成功。--->情况3 

对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
        jdk 7 :元素a放到数组中,指向原来的元素。(七上)
        jdk 8 :原来的元素在数组中,指向元素a(八下)

HashSet底层:数组+链表的结构。

LinkedHashSet

LinkedHashSet作为HashSet的子类,在添加数据的同时,每个数据还维护了两个引用,记录此数据前一个数据和后一个数据。
优点:对于频繁的遍历操作,LinkedHashSet效率高于HashSet 

public void test2(){
        Set set = new LinkedHashSet();
        set.add(456);
        set.add(123);
        set.add(123);
        set.add("AA");
        set.add("CC");
        set.add(new User("Tom",12));
        set.add(new User("Tom",12));
        set.add(129);

        Iterator iterator = set.iterator();
        while(iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }

TreeSet

说明

1.可以按照添加对象的指定属性,进行排序。但向TreeSet中添加的数据,要求是相同类的对象。

//添加失败:不能添加不同类的对象
TreeSet set = new TreeSet();
set.add(123);
set.add("aaa");
set.add(new Person("嘻戏",28));

2.两种排序方式:自然排序(实现Comparable接口) 和 定制排序(Comparator)

3.自然排序中,比较两个对象是否相同的标准为:compareTo()返回0.不再是equals().
4.定制排序中,比较两个对象是否相同的标准为:compare()返回0.不再是equals().

自然排序

person类:

public class Person implements Comparable {
    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 String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && Objects.equals(name, person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }


    //按照姓名从大到小排列,年龄从小到大排列
    @Override
    public int compareTo(Object o) {
       if (o instanceof Person){
           Person p = (Person) o;
           int compareNum = -this.name.compareTo(p.name);
           if (compareNum != 0){
               return compareNum;
           }else {
               return Integer.compare(this.age,p.age);
           }
       }else {
           throw new RuntimeException("输入的类型不匹配");
       }
    }
}

TreeSet集合: 

TreeSet set = new TreeSet();
set.add(new Person("嘻戏",33));
set.add(new Person("张三",28));
set.add(new Person("李四",28));
set.add(new Person("王二",55));
set.add(new Person("嘻戏",26));

Iterator iterator = set.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());//java.lang.ClassCastException
}

定制排序

Comparator comparator = new Comparator() {
    //按照年龄从小到大排列
    @Override
    public int compare(Object o1, Object o2) {
        if (o1 instanceof Person && o2 instanceof Person){
            Person p1 = (Person) o1;
            Person p2 = (Person) o2;
            return Integer.compare(p1.getAge(),p2.getAge());
        }else {
            throw new RuntimeException("输入的数据类型不匹配");
        }
    }
};

TreeSet set = new TreeSet(comparator);
set.add(new Person("嘻戏",26));
set.add(new Person("嘻戏",33));
set.add(new Person("张三",28));
set.add(new Person("李四",28));//该条元素未存储进去
set.add(new Person("王二",55));

Iterator iterator = set.iterator();
while (iterator.hasNext()){
    System.out.println(iterator.next());//java.lang.ClassCastException
}

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值