Java基础笔记(三)---集合框架(1)简单了解

(一)集合框架1-ArrayList

ArrayList和数组的区别

(1)使用数组的局限性
如果要存放多个对象,可以使用数组,但是数组有局限性,比如声明长度是10的数组,不用的数组就浪费了,超过10的个数,又放不下。这就是数组固定长度的局限性
(2)ArrayList存放对象
为了解决数组的局限性,引入容器类的概念。
容器的容量"capacity"会随着对象的增加,自动增长。
只需要不断往容器里增加英雄即可,不用担心会出现数组的边界问题。

ArrayList常用的方法

(1)增加

  • 第一种是直接add对象:heros.add(new Hero("hero " + i));
  • 第二种是在指定位置加对象:heros.add(3, specialHero);

(2)判断是否存在

  • System.out.println(heros.contains(specialHero));

(3)获取

  • heros.get(5)

(4)获取对象所处的位置indexOf
(5)删除

  • heros.remove(2);
  • 也可以根据对象删除:heros.remove(specialHero);

(6)替换set方法
(7)获取长度size方法
(8)转换为数组toArray

  • Hero hs[] = (Hero[])heros.toArray(new Hero[]{});

(9)把另一个容器的所有对象都加进来addAll

  • heros.addAll(anotherHeros);

List接口

public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable

(二)集合框架的遍历

for循环遍历

可以用size()和get()分别得到大小,和获取指定位置的元素,结合for循环就可以遍历出ArrayList的内容

大size的数据,千万不要使用普通for循环

        for (int i = 0; i < heros.size(); i++) {
            Hero h = heros.get(i);
            System.out.println(h);
        }

迭代器遍历

有两种,通过hasNext判断下一个是否为空,不空就取出

        Iterator<Hero> it= heros.iterator();
        //从最开始的位置判断"下一个"位置是否有数据
        //如果有就通过next取出来,并且把指针向下移动
        //直到"下一个"位置没有数据
        while(it.hasNext()){
            Hero h = it.next();
            System.out.println(h);
        }
        //迭代器的for写法
        for (Iterator<Hero> iterator = heros.iterator(); iterator.hasNext();) {
            Hero hero = (Hero) iterator.next();
            System.out.println(hero);
        }

增强型for循环遍历

使用增强型for循环可以非常方便的遍历ArrayList中的元素,这是很多开发人员的首选。
不过增强型for循环也有不足:无法用来进行ArrayList的初始化。无法得知当前是第几个元素了,当需要只打印单数元素的时候,就做不到了。

        for (Hero h : heros) {
            System.out.println(h);
        }

(三)集合框架2-LinkedList

LinkedList又分为两种:队列Queue、栈Stack
(1)和ArrayList相同之处
与ArrayList一样,LinkedList也实现了List接口,诸如add,remove,contains等等方法
(2)和ArrayList不同之处
与数组结构相比较,数组结构,就好像是电影院,每个位置都有标示,每个位置之间的间隔都是一样的。 而链表就相当于佛珠,每个珠子,只连接前一个和后一个,不用关心除此之外的其他佛珠在哪里

双向链表Deque

除了实现了List接口外,LinkedList还实现了双向链表结构Deque,可以很方便的在头尾插入删除数据
新加的方法如下:addFirst、getFirst、addLast、getLast、removeFirst、removeLast。

队列Queue(先进先出)

LinkedList 除了实现了List和Deque外,还实现了Queue接口(队列)
新增的方法如下:

  • offer:加在队列的最后面
  • poll:取出第一个Hero,FIFO 先进先出
  • peek:第一个拿出来看一看,但是不取出来

栈Stack(先进后出)

继承Vector类
新增方法如下:

  • push
  • pop
  • peek
  • empty
  • search

(四)集合框架3-二叉树

二叉树由各种节点组成,每个节点都可以有左子节点,右子节点;每一个节点都有一个值

节点Node是一个接口

public class Node {
    // 左子节点
    public Node leftNode;
    // 右子节点
    public Node rightNode;
    // 值
    public Object value;
}

构造二叉树

左小右大,相同的也放发左边

public class Node {
    // 左子节点
    public Node leftNode;
    // 右子节点
    public Node rightNode;
  
    // 值
    public Object value;
  
    // 插入 数据
    public void add(Object v) {
        // 如果当前节点没有值,就把数据放在当前节点上
        if (null == value)
            value = v;
  
        // 如果当前节点有值,就进行判断,新增的值与当前值的大小关系
        else {
            // 新增的值,比当前值小或者相同
             
            if ((Integer) v -((Integer)value) <= 0) {
                if (null == leftNode)
                    leftNode = new Node();
                leftNode.add(v);
            }
            // 新增的值,比当前值大
            else {
                if (null == rightNode)
                    rightNode = new Node();
                rightNode.add(v);
            }
  
        }
  
    }
  
    public static void main(String[] args) {
  
        int randoms[] = new int[] { 67, 7, 30, 73, 10, 0, 78, 81, 10, 74 };
  
        Node roots = new Node();
        for (int number : randoms) {
            roots.add(number);
        }
  
    }
}

二叉树遍历

实际上,二叉树数据已经排好序了。 接下来要做的是看,把这些已经排好序的数据,遍历成我们常用的List或者数组的形式
二叉树的遍历分左序,中序,右序:
左序即: 中间的数遍历后放在左边
中序即: 中间的数遍历后放在中间
右序即: 中间的数遍历后放在右边

(五)集合框架4-HashMap

HashMap储存数据的方式是—— 键值对。key是唯一的,不可以重复的。所以,以相同的key 把不同的value插入到 Map中会导致旧元素被覆盖,只留下最后插入的元素。

(六)集合框架5-HashSet

Set中的元素,不能重复
Set中的元素,没有顺序
Set不提供get()来获取指定位置的元素,所以遍历需要用到迭代器,或者增强型for循环
HashSet自身并没有独立的实现,而是在里面封装了一个Map。HashSet是作为Map的key而存在的,而value是一个命名为PRESENT的static的Object对象,因为是一个类属性,所以只会有一个。

public class HashSet<E>
    extends AbstractSet<E>
    implements Set<E>, Cloneable, java.io.Serializable
{
    //HashSet里封装了一个HashMap
    private  HashMap<E,Object> map;
 
    private static final Object PRESENT = new Object();
 
    //HashSet的构造方法初始化这个HashMap
    public HashSet() {
        map = new HashMap<E,Object>();
    }
 
    //向HashSet中增加元素,其实就是把该元素作为key,增加到Map中
    //value是PRESENT,静态,final的对象,所有的HashSet都使用这么同一个对象
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
 
    //HashSet的size就是map的size
    public int size() {
        return map.size();
    }
 
    //清空Set就是清空Map
    public void clear() {
        map.clear();
    }
     
    //迭代Set,就是把Map的键拿出来迭代
    public Iterator<E> iterator() {
        return map.keySet().iterator();
    }
 
}

(七)Collection接口

Collection是 Set List Queue和 Deque的接口
Collection和Map之间没有关系,Collection是放一个一个对象的,Map 是放键值对的
Deque 继承 Queue,间接的继承了 Collection

(八)Collections类

Collections是一个类,容器的工具类,就如同Arrays是数组的工具类

  • reverse 使List中的数据发生翻转
  • shuffle 混淆List中数据的顺序
  • sort 对List中的数据进行排序
  • swap 交换两个数据的位置
  • rotate 把List中的数据,向右滚动指定单位的长度
  • synchronizedList 把非线程安全的List转换为线程安全的List。 因为截至目前为止,还没有学习线程安全的内容,暂时不展开。 线程安全的内容将在多线程章节展开。

(九)框架之间的区别

(1)List,Set,Map三者的区别

  • List(对付顺序的好帮手): List接口存储一组不唯一(可以有多个元素引用相同的对象),有序的对象。
  • Set(注重独一无二的性质): 不允许重复的集合。不会有多个元素引用相同的对象。
  • Map(用Key来搜索的专家): 使用键值对存储。Map会维护与Key有关联的值。两个Key可以引用相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象。

(2)ArrayList和HashSet的关系

(1)是否有顺序
ArrayList: 有顺序
HashSet: 无顺序
(2)能否重复
List中的数据可以重复
Set中的数据不能够重复
重复判断标准是:首先看hashcode是否相同,如果hashcode不同,则认为是不同数据,如果hashcode相同,再比较equals,如果equals相同,则是相同数据,否则是不同数据

(3)ArrayList和LinkedList的关系

主要区别:

  • 1)是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
  • 2)底层数据结构: Arraylist 底层使用的是 Object 数组;LinkedList 底层使用的是 双向链表 数据结构(JDK1.6之前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)
  • 3)插入和删除是否受元素位置的影响: ① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e) 方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element) )时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② LinkedList 采用链表存储,所以对于add(�E e)方法的插入,删除元素时间复杂度不受元素位置的影响,近似 O(1),如果是要在指定位置i插入和删除元素的话((add(int index, E element)) 时间复杂度近似为o(n))因为需要先移动到指定位置再插入

ArrayList 插入,删除数据慢,但是查找数据很快,因为是顺序结构。就像电影院位置一样,有了电影票,一下就找到位置了。
LinkedList插入,删除数据快,但是查找数据很慢,因为是链表结构。就像手里的一串佛珠,要找出第99个佛珠,必须得一个一个的数过去。

  • 4)是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index) 方法)。
  • 5)内存空间占用: ArrayList的空 间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。

(4)HashMap和HashTable的关系

HashMap和Hashtable都实现了Map接口,都是键值对保存数据的方式

  • 1)线程是否安全: HashMap 是非线程安全的,HashTable 是线程安全的;HashTable 内部的方法基本都经过synchronized 修饰。(如果你要保证线程安全的话就使用 ConcurrentHashMap 吧!);
    在这里插入图片描述

HashMap不是线程安全的类
Hashtable是线程安全的类

  • 2)效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。另外,HashTable 基本被淘汰,不要在代码中使用它;
  • 3)对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。。但是在 HashTable 中 put 进的键值只要有一个 null,直接抛出 NullPointerException。

HashMap可以存放 null
Hashtable不能存放null

  • 4)初始容量大小和每次扩充容量大小的不同 : ①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值,那么 Hashtable 会直接使用你给定的大小,而 HashMap 会将其扩充为2的幂次方大小(HashMap 中的tableSizeFor()方法保证,下面给出了源代码)。也就是说 HashMap 总是使用2的幂作为哈希表的大小,后面会介绍到为什么是2的幂次方。
  • 5)底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。

HashMap 中带有初始容量的构造函数:

    public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
     public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

(5)HashSet和HashMap的关系

HashSet 底层就是基于 HashMap 实现的。(HashSet 的源码非常非常少,因为除了 clone() 、writeObject()、readObject()是 HashSet 自己不得不实现之外,其他方法都是直接调用 HashMap 中的方法。
HashSet自身并没有独立的实现,而是在里面封装了一个Map。HashSet是作为Map的key而存在的,而value是一个命名为PRESENT的static的Object对象,因为是一个类属性,所以只会有一个。

在这里插入图片描述

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable {
    static final long serialVersionUID = -5024744406713321676L;
    private transient HashMap<E, Object> map;
    private static final Object PRESENT = new Object();

    public HashSet() {
        this.map = new HashMap();
    }

    public HashSet(Collection<? extends E> var1) {
        this.map = new HashMap(Math.max((int)((float)var1.size() / 0.75F) + 1, 16));
        this.addAll(var1);
    }

    public HashSet(int var1, float var2) {
        this.map = new HashMap(var1, var2);
    }

    public HashSet(int var1) {
        this.map = new HashMap(var1);
    }

    HashSet(int var1, float var2, boolean var3) {
        this.map = new LinkedHashMap(var1, var2);
    }

    public Iterator<E> iterator() {
        return this.map.keySet().iterator();
    }

(6)HashSet如何检查重复

当你把对象加入HashSet时,HashSet会先计算对象的hashcode值来判断对象加入的位置,同时也会与其他加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现。但是如果发现有相同hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同。如果两者相同,HashSet就不会让加入操作成功。

  • 1)hashCode()与equals()的相关规定:
    1. 如果两个对象相等,则hashcode一定也是相同的
    2. 两个对象相等,对两个equals方法返回true
    3. 两个对象有相同的hashcode值,它们也不一定是相等的
    4. 综上,equals方法被覆盖过,则hashCode方法也必须被覆盖
    5. hashCode()的默认行为是对堆上的对象产生独特值。如果没有重写hashCode(),则该class的两个对象无论如何都不会相等(即使这两个对象指向相同的数据)。
  • 2)==与equals的区别
    • ==是判断两个变量或实例是不是指向同一个内存空间 equals是判断两个变量或实例所指向的内存空间的值是不是相同
    • ==是指对内存地址进行比较 equals()是对字符串的内容进行比较
    • ==指引用是否相同 equals()指的是值是否相同
发布了41 篇原创文章 · 获赞 5 · 访问量 662

猜你喜欢

转载自blog.csdn.net/weixin_44823875/article/details/104885667