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

  • (一)认识类集
  • 1.1基本概念
  • 1.2基本接口
  • (二)List接口
  • (2.1)List接口
  • (2.2)ArrayList常用方法
  • (2.3)ArrayList和数组的区别
  • (2.4)LinkedList子类
  • 2.3.1双向链表Deque
  • 2.3.2队列Queue(先进先出)
  • 2.3.3栈Stack(先进后出)
  • (三)Set接口
  • 3.1Set接口
  • 3.2HashSet子类(散列的存放)
  • 3.3TreeSet子类(有序的存放)
  • (四)SortedSet接口
  • (五)集合框架的遍历
  • (1)for循环遍历
  • (2)迭代器遍历
  • (3)增强型for循环遍历
  • (六)Map接口
  • 6.1Map接口
  • 6.2HashMap子类
  • 6.3TreeMap子类
  • 6.4Map接口注意事项
  • (七)二叉树
  • 构造二叉树
  • 二叉树遍历
  • (八)Collection接口
  • (九)Collections类
  • (十)框架之间的区别
  • (1)List,Set,Map三者的区别
  • (2)ArrayList和HashSet的关系
  • (3)ArrayList和LinkedList的关系
  • (4)HashMap和HashTable的关系
  • (5)HashSet和HashMap的关系
  • (6)HashSet如何检查重复
  • (7)巧记线程安全的类
  • (8)集合的常见问题汇总



注意点:


1-我们学的那些数据结构知识,被封装起来放在这些集合框架中,开发人员可以直接调用,不用自己去写了,这是学习集合框架和学习数据结构之间的关系。


2-学习集合的使用时,重点是掌握接口的方法,因为子类往往是通过继承不同的接口来实现操作方法的。

(一)认识类集

1.1基本概念

保存一组对象,对象数组有长度的限制,现在通过一些数据结构的操作来完成动态对象数组的操作。类集框架就是对一些实现好的数据结构进行封装,不用开发人员自己写了。

1.2基本接口

(1)接口的继承关系

Java juc 框架 java框架入门_Java juc 框架

(2)集合框架常用方法

  • 1)add:增加( 有两种用法)
    第一种是直接add对象,把对象加在最后面: heros.add(new Hero("hero " + i));
    第二种是在指定位置加对象: heros.add(3, specialHero);
  • 2)contains:判断是否存在
    通过方法contains 判断一个对象是否在容器中
    判断标准: 是否是同一个对象,而不是name是否相同
  • 3)get: 获取指定位置的对象
    通过get获取指定位置的对象,如果输入的下标越界,一样会报错
  • 4)indexOf: 获取对象所处的位置
    indexOf用于判断一个对象在ArrayList中所处的位置
    与contains一样,判断标准是对象是否相同,而非对象的name值是否相等
  • 5)remove: 删除
    remove用于把对象从ArrayList中删除
    remove可以根据下标删除ArrayList的元素: heros.remove(2);
    也可以根据对象删除: heros.remove(specialHero);
  • 6)set: 替换
    set用于替换指定位置的元素
  • 7)size: 获取大小
    size 用于获取ArrayList的大小
  • 8)toArray: 转换为对象数组
    toArray可以把一个ArrayList对象转换为数组。
    需要注意的是,如果要转换为一个Hero数组,那么需要传递一个Hero数组类型的对象给toArray(),这样toArray方法才知道,你希望转换为哪种类型的数组,否则只能转换为Object数组
  • 9)addAll: 把另一个容器所有对象都加进来
    addAll 把另一个容器所有对象都加进来
  • 10)clear: 清空
    clear 清空一个ArrayList
  • 11)get(int index) 获取指定位置的元素

(二)List接口

(2.1)List接口

可以保存各个重复的内容。

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

常用子类:

  • ArrayList
  • LinkedList

(2.2)ArrayList常用方法

add() 将元素插入到指定位置的 arraylist 中
addAll() 添加集合中的所有元素到 arraylist 中
clear() 删除 arraylist 中的所有元素
clone() 复制一份 arraylist
contains() 判断元素是否在 arraylist
get() 通过索引值获取 arraylist 中的元素
indexOf() 返回 arraylist 中元素的索引值
removeAll() 删除存在于指定集合中的 arraylist 里的所有元素
remove() 删除 arraylist 里的单个元素
size() 返回 arraylist 里元素数量
isEmpty() 判断 arraylist 是否为空
subList() 截取部分 arraylist 的元素
set() 替换 arraylist 中指定索引的元素
sort() 对 arraylist 元素进行排序
toArray() 将 arraylist 转换为数组
toString() 将 arraylist 转换为字符串
ensureCapacity() 设置指定容量大小的 arraylist
lastIndexOf() 返回指定元素在 arraylist 中最后一次出现的位置
retainAll() 保留 arraylist 中在指定集合中也存在的那些元素
containsAll() 查看 arraylist 是否包含指定集合中的所有元素
trimToSize() 将 arraylist 中的容量调整为数组中的元素个数
removeRange() 删除 arraylist 中指定索引之间存在的元素
replaceAll() 将给定的操作内容替换掉数组中每一个元素
removeIf() 删除所有满足特定条件的 arraylist 元素
forEach() 遍历 arraylist 中每一个元素并执行特定操作

(2.3)ArrayList和数组的区别

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

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

(2.4)LinkedList子类

(1)以下情况使用ArrayList
1-频繁访问列表中的某一个元素。
2-只需要在列表末尾进行添加和删除元素操作。

(2)以下情况使用LinkedList
1-你需要通过循环迭代来访问列表中的某些元素。
2-需要频繁的在列表开头、中间、末尾等位置进行添加和删除元素操作。

(3)LinkedList的特点
LinkedList 继承了 AbstractSequentialList 类。
LinkedList 实现了 Queue 接口,可作为队列使用。
LinkedList 实现了 List 接口,可进行列表的相关操作。
LinkedList 实现了 Deque 接口,可作为队列使用。
LinkedList 实现了 Cloneable 接口,可实现克隆。
LinkedList 实现了 java.io.Serializable 接口,即可支持序列化,能通过序列化去传输。

public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>, Deque<E>, Cloneable, Serializable

LinkedList表示的是一个链表的操作类,LinkedList又分为两种:队列Queue、栈Stack。虽然实现了List接口,但是也实现了Deque接口。因此,除了实现了上述List中的方法以外,还实现了Deque里的方法,新增方法如下。

(4)更多的情况下我们使用 ArrayList 访问列表中的随机元素更加高效,但以下几种情况 LinkedList 提供了更高效的方法。
1-在列表开头添加元素:
2-在列表结尾添加元素:
3-在列表开头移除元素:
4-在列表结尾移除元素:
5-获取列表开头的元素:
6-获取列表结尾的元素:

public class RunoobTest {
    public static void main(String[] args) {
        LinkedList<String> sites = new LinkedList<String>();
        sites.add("Google");
        sites.add("Runoob");
        sites.add("Taobao");
        // 使用 addFirst() 在头部添加元素
        sites.addFirst("Wiki");
        // 使用 addLast() 在尾部添加元素
        sites.addLast("Wiki");
        // 使用 removeFirst() 移除头部元素
        sites.removeFirst();
        // 使用 removeLast() 移除尾部元素
        sites.removeLast();
        // 使用 getFirst() 获取头部元素
        System.out.println(sites.getFirst());
        // 使用 getLast() 获取尾部元素
        System.out.println(sites.getLast());

        System.out.println(sites);
    }
}

(5)遍历LinkedList

public class RunoobTest {
    public static void main(String[] args) {
        LinkedList<String> sites = new LinkedList<String>();
        sites.add("Google");
        sites.add("Runoob");
        sites.add("Taobao");
        sites.add("Weibo");
        for (String i : sites) {
            System.out.println(i);
        }
    }
}

(6)LinkedList常用方法

public boolean add(E e) 链表末尾添加元素,返回是否成功,成功为 true,失败为 false。
public void add(int index, E element) 向指定位置插入元素。
public boolean addAll(Collection c) 将一个集合的所有元素添加到链表后面,返回是否成功,成功为 true,失败为 false。
public boolean addAll(int index, Collection c) 将一个集合的所有元素添加到链表的指定位置后面,返回是否成功,成功为 true,失败为 false。
public void addFirst(E e) 元素添加到头部。
public void addLast(E e) 元素添加到尾部。
public boolean offer(E e) 向链表末尾添加元素,返回是否成功,成功为 true,失败为 false。
public boolean offerFirst(E e) 头部插入元素,返回是否成功,成功为 true,失败为 false。
public boolean offerLast(E e) 尾部插入元素,返回是否成功,成功为 true,失败为 false。
public void clear() 清空链表。
public E removeFirst() 删除并返回第一个元素。
public E removeLast() 删除并返回最后一个元素。
public boolean remove(Object o) 删除某一元素,返回是否成功,成功为 true,失败为 false。
public E remove(int index) 删除指定位置的元素。
public E poll() 删除并返回第一个元素。
public E remove() 删除并返回第一个元素。
public boolean contains(Object o) 判断是否含有某一元素。
public E get(int index) 返回指定位置的元素。
public E getFirst() 返回第一个元素。
public E getLast() 返回最后一个元素。
public int indexOf(Object o) 查找指定元素从前往后第一次出现的索引。
public int lastIndexOf(Object o) 查找指定元素最后一次出现的索引。
public E peek() 返回第一个元素。
public E element() 返回第一个元素。
public E peekFirst() 返回头部元素。
public E peekLast() 返回尾部元素。
public E set(int index, E element) 设置指定位置的元素。
public Object clone() 克隆该列表。
public Iterator descendingIterator() 返回倒序迭代器。
public int size() 返回链表元素个数。
public ListIterator listIterator(int index) 返回从指定位置开始到末尾的迭代器。
public Object[] toArray() 返回一个由链表元素组成的数组。
public T[] toArray(T[] a) 返回一个由链表元素转换类型而成的数组。

2.3.1双向链表Deque

public interface Deque<E> extends Queue<E>

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

  • addFirst、
  • getFirst、
  • addLast、
  • getLast、
  • removeFirst、
  • removeLast。

2.3.2队列Queue(先进先出)

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

  • element:找到链表的表头
  • offer:加在队列的最后面
  • poll:取出第一个Hero,FIFO 先进先出
  • peek:第一个拿出来看一看,但是不取出来
  • remove:检索并移除表头

2.3.3栈Stack(先进后出)

继承Vector类
新增方法如下:

  • push
  • pop
  • peek
  • empty
  • search

和ArrayList不同之处:
与数组结构相比较,数组结构,就好像是电影院,每个位置都有标示,每个位置之间的间隔都是一样的。 而链表就相当于佛珠,每个珠子,只连接前一个和后一个,不用关心除此之外的其他佛珠在哪里

(三)Set接口

3.1Set接口

与List接口不同,不能保存重复的内容。Set接口和List接口都是继承Collection接口,都没有对方法进行扩充,只是Set接口更加严格了,不允许增加重复元素。另外,Set接口的实例没法像List接口那样可以进行双向输出,所以Set接口没有提供get(int index)方法。
常用子类:

  • 散列的存放:HashSet
  • 有序的存放:TreeSet

3.2HashSet子类(散列的存放)

(1)HashSet介绍
1-HashSet 基于 HashMap 来实现的,是一个不允许有重复元素的集合。
2-HashSet 允许有 null 值。
3-HashSet 是无序的,即不会记录插入的顺序。
4-HashSet 不是线程安全的, 如果多个线程尝试同时修改 5-HashSet,则最终结果是不确定的。 您必须在多线程访问时显式同步对 HashSet 的并发访问。
5-HashSet 实现了 Set 接口。

public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, Serializable

(2)注意两点:
1)Set的子类,所以不能重复(对于重复元素只会增加一次)
2)采用数列的存储方式,所以没有顺序

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();
    }
 
}

(3)HashSet常用方法

public class RunoobTest {
    public static void main(String[] args) {
    HashSet<String> sites = new HashSet<String>();
        sites.add("Google");
        sites.add("Runoob");
        sites.add("Taobao");
        sites.add("Zhihu");
        //1-添加元素可以使用 add() 方法:
        sites.add("Runoob");  // 重复的元素不会被添加
        //2-使用 contains() 方法来判断元素是否存在于集合当中
        System.out.println(sites.contains("Taobao"));
        //3-使用 remove() 方法来删除集合中的元素
        sites.remove("Taobao");  // 删除元素,删除成功返回 true,否则为 false
        //4-使用 for-each 来迭代 HashSet 中的元素
        for (String i : sites) {
            System.out.println(i);
        }
        //5-计算 HashSet 中的元素数量可以使用 size() 方法
        System.out.println(sites.size());
        System.out.println(sites);
        //6-删除集合中所有元素可以使用 clear 方法
        sites.clear(); 
    }
}

3.3TreeSet子类(有序的存放)

public class TreeSet<E> extends AbstractSet<E> implements NavigableSet<E>, Cloneable, Serializable

注意两点:
1)如果想对输入的数据进行有序排列,使用树结构,因为实现了SortedSet接口,所以可以进行排序。
2)实现Set接口,所以不允许数据重复。

(四)SortedSet接口

实现Set接口

public interface SortedSet<E> extends Set<E> {
    Comparator<? super E> comparator();

    SortedSet<E> subSet(E var1, E var2);

    SortedSet<E> headSet(E var1);

    SortedSet<E> tailSet(E var1);

    E first();

    E last();

    default Spliterator<E> spliterator() {
        return new IteratorSpliterator<E>(this, 21) {
            public Comparator<? super E> getComparator() {
                return SortedSet.this.comparator();
            }
        };
    }
}

(五)集合框架的遍历

(1)for循环遍历

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

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

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

(2)迭代器遍历

有两种,通过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);
        }

(3)增强型for循环遍历

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

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

(六)Map接口

6.1Map接口

之前所讲的Collection、List、Set接口都是单值操作,也就是说每次只能处理一个对象,而Map不同的是每次操作的是键值对象。

(1)Map接口中常用的方法

  • clear:清空Map集合
  • containsKey判断key是否存在
  • containsValue
  • equals:对象比较
  • size
  • isEmpty
  • putAll
  • put
  • hashCode:返回哈希码
  • remove
  • keySet:存放所有key值的地方,可以遍历来取出所有key
  • values:存放所有value值的地方
  • Set<Map.Entry<K, V>> entrySet():把Map对象变为Set集合,每对数据都是通过Map.Entry接口来保存的

(2)Map接口的常用子类

  • HashMap:无序,key不重复,重复就会覆盖
  • Hashtable:无序,key不重复(已经被HashMap取代)
  • TreeMap:按key排序,key不重复

6.2HashMap子类

(1)HashMap介绍
1)HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
2)HashMap 实现了 Map 接口,根据键的 HashCode 值存储数据,具有很快的访问速度,最多允许一条记录的键为 null,不支持线程同步。
3)HashMap 是无序的,即不会记录插入的顺序。
4)HashMap 继承于AbstractMap,实现了 Map、Cloneable、java.io.Serializable 接口。

public class HashMap<K, V> extends AbstractMap<K, V> implements Map<K, V>, Cloneable, Serializable

(2)注意两点
key无序
key不重复

(3)HashMap常用方法

public class RunoobTest {
    public static void main(String[] args) {
        // 创建 HashMap 对象 Sites
        HashMap<String, String> Sites = new HashMap<String, String>();
        // 1-添加键值对
        Sites.put("one", "Google");
        Sites.put("two", "Runoob");
        Sites.put("three", "Taobao");
        Sites.put("four", "Zhihu");
        //2-使用 get(key) 方法来获取 key 对应的 value
        System.out.println(Sites.get(3));
        //3-使用 remove(key) 方法来删除 key 对应的键值对(key-value)
        Sites.remove(4);
        //4-计算 HashMap 中的元素数量可以使用 size() 方法
        System.out.println(Sites.size());
        //5-用 for-each 来迭代 HashMap 中的元素
        //如果你只想获取 key,可以使用 keySet() 方法,然后可以通过 get(key) 获取对应的 value,如果你只想获取 value,可以使用 values() 方法。
        // 输出 key 和 value
        for (Integer i : Sites.keySet()) {
            System.out.println("key: " + i + " value: " + Sites.get(i));
        }
        // 返回所有 value 值
        for(String value: Sites.values()) {
          // 输出每一个value
          System.out.print(value + ", ");
        }
        System.out.println(Sites);
        //6-删除所有键值对(key-value)可以使用 clear 方法
        Sites.clear();
    }
}

(4)HashMap其他常用方法

clear() 删除 hashMap 中的所有键/值对
clone() 复制一份 hashMap
isEmpty() 判断 hashMap 是否为空
size() 计算 hashMap 中键/值对的数量
put() 将键/值对添加到 hashMap 中
putAll() 将所有键/值对添加到 hashMap 中
putIfAbsent() 如果 hashMap 中不存在指定的键,则将指定的键/值对插入到 hashMap 中。
remove() 删除 hashMap 中指定键 key 的映射关系
containsKey() 检查 hashMap 中是否存在指定的 key 对应的映射关系。
containsValue() 检查 hashMap 中是否存在指定的 value 对应的映射关系。
replace() 替换 hashMap 中是指定的 key 对应的 value。
replaceAll() 将 hashMap 中的所有映射关系替换成给定的函数所执行的结果。
get() 获取指定 key 对应对 value
getOrDefault() 获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值
forEach() 对 hashMap 中的每个映射执行指定的操作。
entrySet() 返回 hashMap 中所有映射项的集合集合视图。
keySet() 返回 hashMap 中所有 key 组成的集合视图。
values() 返回 hashMap 中存在的所有 value 值。
merge() 添加键值对到 hashMap 中
compute() 对 hashMap 中指定 key 的值进行重新计算
computeIfAbsent() 对 hashMap 中指定 key 的值进行重新计算,如果不存在这个 key,则添加到 hasMap 中
computeIfPresent() 对 hashMap 中指定 key 的值进行重新计算,前提是该 key 存在于 hashMap 中。

6.3TreeMap子类

public class TreeMap<K, V> extends AbstractMap<K, V> implements NavigableMap<K, V>, Cloneable, Serializable
public interface NavigableMap<K, V> extends SortedMap<K, V>

注意两点:
因为继承SortedMap接口,所以可以按key值排序
key不重复

除了Map接口中常用的方法,又继承了SortedMap接口,新增的方法如下:

  • private final Comparator<? super K> comparator;:返回比较器的对象
  • firstKey:返回第一个元素的key值
  • lastKey

6.4Map接口注意事项

(1)不能直接使用迭代输出Map中的全部内容
因为Map接口的每个位置放的是一对值,而遍历的时候只能找到一个值。
如果要遍历也要交给Map.Entry接口来操作

(七)二叉树

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

节点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或者数组的形式
二叉树的遍历分左序,中序,右序:
左序即: 中间的数遍历后放在左边
中序即: 中间的数遍历后放在中间
右序即: 中间的数遍历后放在右边

(八)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对象,因为是一个类属性,所以只会有一个。

Java juc 框架 java框架入门_java_02

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()指的是值是否相同

(7)巧记线程安全的类

喂,SHE

线程安全的类有
喂(Vector):就比arraylist多了个同步化机制(线程安全),因为效率较低,现在已经不太建议使用。在web应用中,特别是前台页面,往往效率(页面响应速度)是优先考虑的。
S(Stack):堆栈类,先进后出
H(hashtable):就比hashmap多了个线程安全
E(enumeration) :枚举,相当于迭代器

除了这些之外,其他的都是非线程安全的类和接口。

(8)集合的常见问题汇总

文章点这里:Java集合容器面试题