Java集合

Java集合学习笔记

    • 1 什么是集合
      • 1.1 集合的介绍
      • 1.2 集合的分类
    • 2 Collection
      • 2.1 Collection中能存放什么元素
      • 2.2 Collection中常用的方法
      • 2.3 关于Collection遍历/迭代专题
      • 2.4 深入Collection集合的contains方法
    • 3 List
      • 3.1 List集合存储元素特点及分类
      • 3.2 ArrayList
      • 3.3 LinkedList
      • 3.4 Vector
    • 4 Set
      • 4.1 Set 集合存储元素特点及分类
      • 4.2 HashSet
      • 4.3 TreeSet
    • 5 Map
      • 5.1 Map集合存储元素特点及分类
      • 5.2 Map的特性
      • 5.3 Map接口中常用方法:
      • 5.4 关于Map遍历/迭代专题
      • 5.5 小知识
      • 5.6 HashMap
      • 5.7 TreeMap
    • 6 小结

1 什么是集合

1.1 集合的介绍

​ 集合实际上就是一个容器,可以用来容纳其他类型的数据,数组相当于一个最简单的集合

​ 集合在java中本身就是一个容器,是一个对象,是一个引用数据类型

​ 集合容器中存储的其实是每个对象的内存地址,每个内存地址指向各自的对象

​ 集合里面可以套集合

​ java中每一个不同的集合,底层都会对应不同的数据结构,往不同的集合中存储元素,等于将数据放到了不同的数据结构中,数据结构 就是数据存储的结构,不同的数据结构,数据存储方式不同
​ 例如:数组,二叉树,链表,哈希表

​ 所有的集合都是可迭代的,可遍历的

1.2 集合的分类

​ java中集合分为两大类:
​ 一类是单个方式存储元素(对象)
​ 以单个方式存储元素,这一类的集合中超级父接口:java.util.Collection

​ 一类是以键值对的方式存储元素(对象)
​ 以单个方式存储元素,这一类的集合中超级父接口:java.util.Map

​ 在java中,数据结构已经写好了常用的工具类Collections
​ 需要掌握怎么用,在合适的情况下选择合适的集合
​ new不同的对象,会创建一个底层数据结构不同的集合

​ 所有的集合类和集合接口,都在java.util.*下边

​ Map下的集合不能使用Collections工具类

2 Collection

2.1 Collection中能存放什么元素

​ 没有使用泛型之前,Collection中可以存储Object的所有子类型

​ 使用泛型之后,Collection中只能存储某个具体的类型

2.2 Collection中常用的方法

​ boolean add(Object e) 向集合中添加元素

//创建一个集合对象
//Collection collection = new Collection(); 接口是抽象的,无法实例化
//多态
Collection c = new ArrayList();
c.add(1200);//自动装箱
c.add(3.14);//自动装箱
c.add(new Object());
c.add(new Student());
c.add(true);//自动装箱

​ int size() 获取集合中元素的个数

//获取集合中元素的个数
System.out.println("集合中元素个数为:"+c.size());

​ void clear() 清空集合

//清空集合
c.clear();
System.out.println("集合中元素个数为:"+c.size());//0

​ boolean contains(Object o) 判断当前集合中是否包含元素o,包含返回true,不包含返回false

c.add("hello");
c.add("world");
//判断集合中是否包含hello
boolean b = c.contains("hello");
System.out.println(b);//true

​ void remove() 删除集合中某个元素

//删除集合中某个元素
c.remove("hello");
System.out.println(c.contains("hello"));//false

​ boolean isEmpty() 判断集合是否为空

//判断集合是否为空
System.out.println(c.isEmpty());//false
c.clear();
System.out.println(c.isEmpty());//true

​ Object[] toArray() 将集合转换为数组 [作为了解,使用不多]

//将集合转换为数组
Object[] objects = c.toArray();
System.out.println(Arrays.toString(objects));//[world]

​ boolean retainAll() 保留指定集合中的元素

2.3 关于Collection遍历/迭代专题

​ Iterator迭代器,只能在所有的Collection及其子类中使用,Map集合下不能用,因为Collection继承了Iterable接口

public interface Collection<E> extends Iterable<E>
//如下的遍历方式/迭代方式,是所有Collection通用的一种方式
//创建集合对象
Collection c = new ArrayList();//
//添加元素
c.add("hello");
c.add("world");
c.add(123);
c.add(new Object());
//对集合Collection进行遍历/迭代

//第一步:获取集合对象的迭代器对象
Iterator it = c.iterator();
//第二步:通过以上获取的迭代器对象开始迭代/遍历集合
/*
    boolean hasNext()如果仍有元素可以迭代,则返回true
    Object next() 返回迭代的下一个元素
*/
while (it.hasNext()){
    //存进去什么类型,取出来就是什么类型
    //此时并没有使用泛型,所以取出来就是Object类型
    Object obj = it.next();
    //输出的时候会换成字符串,因为println会调用toString
    System.out.println(obj);
}

​ 使用增强for循环,代码更加方便简洁,同样适合所有的Collection及其子类使用

for(Object o : c){
    System.out.println(o);
}

2.4 深入Collection集合的contains方法

​ contains方法是用来判断集合中是否包含某个元素的方法,调用了equals方法进行比较,比较内容不比较地址

Collection c = new ArrayList();
//像集合中存储元素
String s1 = new String("abc");
c.add(s1);
String s2 = new String("abc");
c.add(s2);
//源码中比较的不是地址,是指内容
System.out.println(c.contains(x));//true

3 List

3.1 List集合存储元素特点及分类

​ List集合存储元素特点:有序可重复,存储的元素有下标

​ 有序指的是存进去是什么顺序取出来还是什么顺序,不是指按照大小排序

​ 常用类:

​ ArrayList(非线程安全):集合底层数据结构为数组

​ LinkedList:集合底层数据结构为双向链表

​ Vector(线程安全):集合底层数据结构为数组

​ 其所有方法都有synchronized修饰,但是效率低下,现在保障线程安全有其他方法,Vector使用较少

3.2 ArrayList

​ 1.默认初始化容量是10(底层先创建了一个长度为0的数组,当添加第一个元素是,初始化容量是10 )

private static final int DEFAULT_CAPACITY = 10;

​ 2.集合底层是一个Object[]数组

private static final Object[] EMPTY_ELEMENTDATA = {};

​ 3.常用构造方法:

new ArrayList();//创建默认定长ArrayList
new ArrayList(20);//创建定长ArrayList
new ArrayList(set);//创建一个ArrayList,并将set集合中的元素放进去
//该方法可以用于Collection下的所有集合   

​ 4.ArrayList集合的扩容:

​ 以下是底层扩容数组的代码,通过位运算使新数组的容量变为原容量的1.5倍。

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
}

​ 注意:尽可能少的扩容。因为数组扩容效率比较低,建议在使ArrayList集合的时候预估计元素的个数,给定一个初始化容量。

​ 5.数组优点:

​ 检索效率比较高。( 每个元素占用空间大小相同,内存地址是连续的,知道首元素内存地址,然后知道下标,通过数学表达式计算出元素的内存地址,所以检索效率最高。)

​ 6.数组缺点:

​ 随机增删元素效率比较低

​ 7.向数组末尾添加元素,效率很高,不收影响

​ 8.面试时如何介绍

​ ArrayList是最常用的集合,因为往数组末尾添加元素,效率不受影响。另外,我们检索/查找某个元素的操作比较多

3.3 LinkedList

​ 1.链表的结构

​ 对于链表的数据结构来说,基本的单元是Node
​ 对于单项链表来说,任何一个结点Node中都有两个属性:
​ 一个属性,是存储的数据,另一个属性是下一个结点的内存地址

​ 2.链表的优点

​ 由于链表上的元素在空间存储上内存地址不连续,所以随机增删元素的时候不会有大量元素位移,因此随机增删效率较高。

​ 在以后的开发中,如果遇到随机增删集合中元素的业务比较多时,建议使用LinkedList。

​ 3.链表的缺点

​ 不能通过数学表达式计算被查找元素的内存地址,每一次查找都是从头节点开始遍历,直到找到为止。

​ 所以LinkedList集合检索/查找的效率较低。

​ 4.LinkedList和ArrayList 区别

​ ArrayList :把检索发挥到极致。( 末尾添加元素效率还是很高的)

​ LinkedList :把随机增删发挥到极致。

​ 加元素都是往末尾添加,所以ArrayList用的比LinkedList多。

3.4 Vector

​ 因为Vector使用较少便没有过多研究,不过中途发现了有趣的一点,ArrayList 中通过位运算扩容,Vector通过三目运算符判断扩容,ArrayList扩容后为1.5倍,Vector扩容后为两倍。

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

4 Set

4.1 Set 集合存储元素特点及分类

​ 无序不可重复,存储的元素无下标,无序指的是存进去什么顺序取出来就不一定是这个顺序了

​ 常用类:

​ HashSet:底层实际上new了一个HashMap集合,向HashSet中存储元素,实际上是存储到HashMap上了

​ HashSet是一个哈希表(散列表)数据结构

​ TreeSet:底层实际上new了一个TreeMap集合,间接实现了SortedSet接口,能够对集合中的对象进行排序。

​ TreeSet是一个红黑树数据结构

4.2 HashSet

​ 1.按照哈希算法来存取集合中的对象,存取速度比较快。

​ 2.(详情原理看5.6.8和5.6.9)当向集合中加入一个对象时,HashSet会调用对象的hashCode()方法来获得哈希码,然后根据这个哈希码进一步计算出对象在集合中的存放位置。

​ 3.如果要判断去重,必须重写hashCode()和equals()。JVM会先调用hashCode()比较哈希码,确定相同为true后再调用equals(),确定值相同则会认为两个对象是同一个

Set set = new HashSet();
Customer customer1 = new Customer("Tom", 15);
Customer customer2 = new Customer("Tom", 15);
set.add(customer1);
set.add(customer2);
System.out.println(set.size());//2
//因为customer中未重写hashCode()和equals()

​ 4.底层实际上new了一个HashMap集合,该HashMap初始化容量是16,默认加载因子是0.75,意味着如果容量到达四分之三则会扩容,以下是HashSet源码节选

//HashMap中的key用来保存set的值
private transient HashMap<E,Object> map;
//使用一个PRESENT作为Map集合的所有value。
private static final Object PRESENT = new Object();
public HashSet() {
    map = new HashMap<>();
}
//HashMap初始化容量是16,默认加载因子是0.75
public HashSet(Collection<? extends E> c) {
    map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16));
    addAll(c);
}

4.3 TreeSet

​ 1.TreeSet集合储存元素特点:

​ 1.无序不可重复的,但是存储的元素可以自动按照大小顺序排序,称为:可排序集合
​ 2.无序指的是存进去的顺序和取出来的顺序不同,并且没有下标

​ 2.TreeSet支持两种排序方式:自然排序和客户端排序。

​ 自然排序:实现Comparable接口并重写compareTo方法

​ 客户端排序:实现比较器Comparator接口并重写compare方法

​ 返回值等于0,x=y,结果不变

​ 返回值大于0,x>y,升序排列

​ 返回值小于0,x

​ Comparable和Comparator怎么选择呢?
​ 当比较规则不会发生改变的时候,或者说当比较规则只有1个的时候,建议实现comparable接口。
​ 如果比较规则有多个,并且需要多个比较规则之间频繁切换,建议使用comparator接口。

​ Comparator接口的设计符合0CP原则。

​ 3.底层实际上new了一个TreeMap集合

public class TreeSet<E> extends AbstractSet<E>
    implements NavigableSet<E>, Cloneable, java.io.Serializable{
    //NavigableMap中的key用来保存set的值
    private transient NavigableMap<E,Object> m;
    //使用一个PRESENT作为Map集合的所有value。
    private static final Object PRESENT = new Object();
    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }
    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }

5 Map

5.1 Map集合存储元素特点及分类

​ HashMap:底层是哈希表数据结构,非线程安全,对放入其中的键用hash算法进行去重操作

​ HashTable:synchronized修饰,效率低,使用较少

​ 子类:Properties:继承HashTable,储存也用键值对,只支持String类型

​ SortedMap:子类:TreeMap:底层数据结构是二叉树,对其中放入的数据除了去重之外,还需要对放入其中的键进行排序

5.2 Map的特性

​ 1.Map和Collection没有继承关系。

​ 2.Map集合以key和value的方式存储数据:键值对

​ key:非空且唯一

​ value:没有要求

​ 键相同的情况下,会做值的覆盖

​ key和value都是引用数据类型。

​ key和value都是存储对象的内存地址。

​ key起到主导的地位, value是key的一个附属品。

5.3 Map接口中常用方法:

​ V put(K key, V value) 向Map集合中添加键值对

Map<Integer,String> map = new HashMap<>();
//健不允许重复
//健重复了,执行覆盖操作
map.put(1, "hello");
map.put(1, "world");
System.out.println(map);//{1=world}

​ V get(object key) 通过key获取value

String s = map.get(1);
System.out.println(s);//world

​ boolean isEmpty( ) 判断Map 集合中元素个数是否为e

​ void clear() 清空Map 集合

System.out.println(map.isEmpty());//false
map.clear();
System.out.println(map.isEmpty());//true

​ int size() 获取Map 集合中键值对的个数。

​ boolean containsKey(Object key) 判断Map 中是否包含某个key

​ boolean containsValue(object value) 判断Map 中是否包含某个value

map.put(1, "hello");
map.put(2, "world");
System.out.println(map.size());//2
System.out.println(map.containsKey(1));//true
System.out.println(map.containsValue("world"));//true

​ V remove(object key) 通过key 删除鍵值对

map.remove(1);
System.out.println(map.containsKey(1));

5.4 关于Map遍历/迭代专题

​ Map无法直接遍历,都需要通过方法间接转换成Set之后才能用Collection中的遍历方式进行遍历

​ Set keySet() 获取Map集合所有的key (所有的键是一个set集合)

Set mapKeys=map.keySet();
Iterator iter  =mapKeys.iterator();
while(iter.hasNext()) {
    Object key =iter.next();
    System.out.println(key+":"map.get(key));
}

​ Set> entrySet() 将Map集合转换成Set集合
Map集合通过entrySet()方法转换成Set集合,其中元素的类型是Map.Entry
Map.Entry是Map中的静态内部类

Set<Map.Entry<Integer, String>> entries = map.entrySet();
for(Map.Entry<Integer, String> entry : entries){
    System.out.println(entry.getKey()+":"+entry.getValue());
}

​ 如上,使用了keySet()或是entrySet()方法后,就可以像遍历Collection一样遍历Map,无论是用迭代器还是增强for循环都可以

5.5 小知识

​ HashMap集合key部分允许null,但是只能用一个null,面试可能会问,但是开发用不到

Map map = new HashMap();
map.put(null,null);
map.put(null,100);
System.out.println(map.size());//1
System.out.println(map.get(null));//100

5.6 HashMap

​ 1.HashMap 集合底层是哈希表/散列表的数据结构。

​ 2.哈希表是一个怎样的数据结构呢?

​ 哈希表是一个数组和单向链表的结合体。
​ 数组:在查询方面效率很高,随机增删方面效率很低。
​ 单向链表:在随机增删方面效率较高,在查询方面效率很低。
​ 哈希表将以上的两种数据结构融合在一起,充分发挥它们各自的优点。

​ 3.HashMap集合底层源代码:

​ public class HashMap{
​ //HashMap底层实际上是一个数组(一维数组)
​ Node[] table;
​ //静态的内部类HashMap,Node
​ static class Node{
​ final int hash;
​ final K key;
​ V value;
​ Node next;
​ }
​ }

​ 哈希表/散列表:一维数组,这个数组中的每一个元素都是一个单项链表(数组和链表的结合体)

​ 4.为什么哈希表的随机增删,以及查询效率都很高?

​ 增删是在链表上完成。
​ 查询也不需要都扫描,只需要部分扫描。

​ 5.HashMap 集合的key部分特点:

​ 无序,不可重复。
​ 为什么统序?因为不- -定挂到哪个单向链表上。
​ 不可重复是怎么保证的? equals 方法来保证HashMap集合的key不可重复。
​ 如果key重复了, value会覆盖。
​ 放在HashMap集合key部分的元素其实就是放到HashSet集合中了。.
​ 所以HashSet集合中的元素也需要同时重写hashCode()+equals()方法。

​ 6.哈希表HashMap 使用不当时无法发挥性能!

​ 假设将所有的hashCode()方法返回值固定为某个值,那么会导致底层哈希表变成了
​ 纯单向链表。这种情况我们成为:散列分布不均匀。
​ 什么是散列分布均勻?
​ 假设有100个元素, 10个单向链表,那么每个单向链表上有10个节点,这是最好的,
​ 是散列分布均匀的。
​ 假设将所有的hashCode()方法返回值都设定为不一样的值,可以吗,有什么问题?
​ 不行,因为这样的话导致底层哈希表就成为-维数组了,没有链表的概念了。
​ 也是散列分布不均匀。
​ 散列分布均匀需要你重导hashCode()方法时有一定的技巧。

​ 7.HashMap初始化容量是16,默认加载因子是.75

​ 8.向Map集合中存与取,都是先调用key的hashCode方法,然后再调用equals

​ equals方法有可能调用,也有可能不调用

​ 拿put(k,v)举例,什么时候equals不会调用?
k. hashCode()方法返回哈希值,
​ 哈希值经过哈希算法转换成数组下标。
​ 数组下标位置上如果是null,equals不需要执行。

​ 拿get(k)举例,什么时候equals不会调用?
​ k. hashCode()方法返回哈希值,
哈希值经过哈希算法转换成数组下标。
数组下标位置上如果是null,equals不需要执行。

​ 注意:如果一个类的equals方法重写了,那么hashCode()方法必须重写,并且equals方法返回如果是true , hashCode()方法返回的值必须一样,equals方法返@true表示两个对象相同,在同一个单向链表上比较,那么对于同一个单向链表上的节点来说,他们的哈希值都是相同的,所以hashCode()方法的返回值也应该相同。

​ 9.范围问题

​ 在JDK8之后,如果哈希表单向链表中元素超过8个,单向链表这种数据结构会变成红黑树数据结构,当红黑树上的节点数童小于6时,会重新把红黑树变成单项链表数据结构,这种方式也是为了提高检索缩小扫描范围,提高效率

​ 对于哈希表数据结构来说:
​ 如果o1和2的hash值相同,一定是放到同一个单向链表上。当然如果o1和2的hash值不同,但由于哈希算法执行结束之后转换的数组下标可能相同,此时会发生“哈希碰撞”。

5.7 TreeMap

​ 基本同TreeSet一样,排序实现接口也与之相同,详情见4.3内容

6 小结

​ 本文为我上课学习时的课堂笔记,是将当堂写的java文件中的注释整理后直接复制过来,并未做过多的修改,是上课跟随老师学习的第一观感,发出来既是作为自己的学习积累,也是作为一种纪念。

​ 个人感觉学习Collection和Map时并无太大困难之处,能快速跟上老师速度并熟练敲出代码,但就是考试笔试时容易忘记个别方法名或者拼错,通过这次整理笔记也能对知识体系有更稳固的掌握,课余时间会闲下来多了解了解数据结构中的底层操作,算法这块也是我的薄弱,希望可以找个时间专攻,弥补一下。

你可能感兴趣的:(学习笔记,java)