Java集合

181 阅读8分钟

集合位于java.util包

集合概念

存储对象的容器,定义了对对象常用的方法

与数组的区别

  • 数组的长度固定
  • 数组可以存储基本数据类型和引用类型,集合只能存储引用类型

Collection体系集合

  • Collection:该体系结构的根接口,被称为"集合"
    • List:有序、可重复
      • ArrayList
      • LinkedList
      • Vector
    • Set:无序、不可重复
      • HashSet
      • SortedSet
        • TreeSet

Collection接口

包含有序或者无序,重复或者不重复的集合

常用方法

//添加一个对象
boolean add(Object obj);
//将集合中的所有对象添加到此集合中  
boolean addAll(Collection c);
//清除该集合中的所有对象
void clear();
//检测是否包含对象o
boolean contains(Object o);
//比较此集合是否与指定对象相同
boolean equals();
//判断集合是否未空
boolean isEmpty();
//删除集合中的o对象
boolean remove(Object o);
//删除在本集合和c集合中均存在的元素
boolean removeAll(Collection<?> c)
//返回集合中元素的个数
int size();
//将集合转换成数组
Object[] toArray();
//返回在此集合上的进行迭代的迭代器
Iterator<E> iterator();
迭代器就是用来遍历集合的一种方式

Iterator

常用方法

//如果还有元素,返回true
boolean hasNext();
//返回下一个元素
E next();
void remove();

注意事项

  • 迭代过程中不能使用集合的提供的remove()方法

List接口

有序、元素可以重复

常用方法

//在index位置插入对象obj
void add(int index, Object obj);
//将集合中的所有对象添加到此集合中index位置
boolean addAll(int index, Collection c);
//返回集合中指定位置的元素
Object get(int index);
//返回from和to之间的集合元素
List subList(int from, int to);
//返回列表中第一次出现指定元素的索引
int indexOf(Object obj);
//按适当顺序返回迭代器
ListIterator listIterator();
//按适当顺序返回迭代器,从指定的位置开始
ListIterator listIterator(int index);
//用指定元素替换列表中指定位置的元素
E set(int index, E element);

ListIterator

常用方法

//插入元素
void add(E e);
//逆向遍历列表,如果有元素则返回true
boolean hasPrevious();
//返回next后续调用所用返回元素的索引
int nextIndex();
//返回前一个元素
E previous();
//返回对previous的后续调用所返回元素的索引
int previousIndex();
//替换元素
void set(E e);

#List实现类

ArrayList

  • 数组结构实现,查询速度快,增删慢
  • JDK1.2版本,运行效率快,线程不安全

Vector

  • 数组结构实现,查询速度快,增删慢
  • JDK1.0版本,运行效率快,线程安全

LinkedList

  • 双向链表结构实现,增删快,查询慢

ArrayList

常用功能

    • add();
    • remove(Object o);
  • 遍历
    • Iterator
      • hasNext()
      • next()
      • remove()
    • ListIterator
      • hasNext()
      • next()
      • remove()
      • hasPrevious
      • previous()
  • 判断
    • contains()
    • isEmpty()
  • 查找
    • indexOf()

注意事项

  • remove方法参数为对象时,比较的是地址是否相同

ArrayList源码分析

变量

  • int DEFAULT_CAPACITY:默认容量
  • Object[] elementData:存放元素的数组
  • int size 实际元素个数

add方法解析

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
//确保每次添加元素前数组空间足够
//minCapacity = 数组元素总数+1
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
//判断集合的构建是调用了哪个构造函数,如果是无参构造函数则返回DEFAULT_CAPACITY、否则返回minCapacity
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}
//当数组的容量elementData.length小于minCapacity(siz + 1)时进行扩容
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}
//每次扩容旧数组空间/2的大小,如果扩容之后数组仍然太小,则将最小容量赋值给newCapacity
//如果扩容之后太大,那么将MAX_ARRAY_SIZE赋值给newCapacity
//将数组复制到大小为newCapacity的新的数组中去,并将引用赋值给elementData
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);
}

Vector

常用方法

//返回枚举器,与迭代器功能相似
Enumration elements();

Enumration

常用方法

boolean hasMoreElements();
Object nextElement();

LinkedList

变量

  • int size 实际元素个数
  • Node first 指向第一个结点
  • Node end 指向最后一个结点

源码解析

add方法

public boolean add(E e) {
    linkLast(e);
    return true;
}
// 创建新的结点,连接前一个节点
void linkLast(E e) {
    final Node<E> l = last;
    final Node<E> newNode = new Node<>(l, e, null);
    last = newNode;
    if (l == null)
        first = newNode;
    else
        l.next = newNode;
    size++;
    modCount++;
}

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

ArrayList与LinkedList的不同实现方式

ArrayList必须开辟连续的内存空间,而LinkedList不需要

泛型

JDK1.5引入的新特性,本质是参数化类型,把类型作为参数传递

常见形式

  • 泛型类
  • 泛型接口
  • 泛型方法

语法

<T,......> T称为类型占位符,表示一种引用类型

优势

  1. 提高代码的重用性
  2. 防止类型转换异常,提高代码的安全性

注意事项

  1. 泛型只能是引用类型
  2. 不同的泛型对象不能相互赋值

实例代码

//泛型类
public class MyGeneric <T>{

T t;

public void show(T t) {
    System.out.println(t);
}

public T getT() {
    return t;
}
//泛型接口
public interface MyInterface<T> {

    abstract T server(T t);
}
//泛型接口实现方式一
public class MyInterfaceimpl implements MyInterface<String>{

    @Override
    public String server(String t) {
        return t;
    }
}
//泛型接口实现方式二
public class MyInterfaceimpl2<T> implements MyInterface<T>{
    @Override
    public T server(T t) {
        return t;
    }
}

//泛型方法
public <T> T show(T t) {
    System.out.println(t);
    return t;
}

public class GenericTest {
    public static void main(String[] args) {
        MyGeneric<String> mg = new MyGeneric<>();
        mg.t = "11";
        mg.show(mg.t);
        System.out.println(mg.getT());
        MyGeneric<Integer> i = new MyGeneric<>();
        i.t = 39;
        i.show(200);
        System.out.println(i.getT());
        MyInterfaceimpl m = new MyInterfaceimpl();
        System.out.println(m.server("hohoho"));
        MyInterfaceimpl2<Integer> m2 = new MyInterfaceimpl2();
        System.out.println(m2.server(2378));
        MyGenericMethod mm = new MyGenericMethod();
        mm.show(53.1123);
    }
}

泛型集合

参数化类型,类型安全的集合,强制集合元素的类型必须一致

特点

  • 编译时即可检查,而非运行时抛出异常
  • 访问时不必强制类型转换
  • 不同泛型之间引用不能相互赋值,泛型不存在多态

Set集合

方法全部继承自Collection方法

Set的实现类

  • HashSet
    • 基于HashCode计算元素存放位置
    • 当存入元素的哈希值相同时,会调用equals进行确认,如果结果为true,则拒绝后者存入
  • TressSet
    • 基于排序顺序实现元素不重复
    • 实现了SortedSet接口,对集合元素自动排序
    • 元素对象的类型必须实现Comparable接口,指定排序规则
    • CompareTo来判断是否为重复元素

HashSet

数据结构

数组+链表+红黑树

存储过程

  1. 根据hashcode计算存储位置,如果位置为空则直接保存,如果不为空,则执行2
  2. 执行equals,如果equals为true则认为是重复的,反之以链表结点的形式链接

实现属性相同的对象不重复存储

  1. 重写hashCode()方法,计算每个属性的哈希值,并相加返回,此时两个值相同对象的哈希值相同
  2. 重写equals方法,如果每个属性值相同,则判断两个对象相同

TreeSet

数据结构

红黑树

红黑树

元素实现Comparable接口

TreeSet的存储结构为红黑树,需要确认比较的数值是哪个,因此需要确定比较的值

// 重写compare
@override
public int compare(Person o1, Person o2){
  int n1 = o1.getAge()-o2.getAge();
  int n2 = o1.getName().comareTo(o2.getName());
  return n1 == 0 ? n2 : n1;
}

Comparator比较器

定制比较规则,元素可以不实现Comparable接口

TreeSet t1 = new TreeSet(new Comparator() {
    @Override
    public int compare(Object o1, Object o2) {
    Dog d1 = (Dog)o1;
    Dog d2 = (Dog)o2;
    int n1 = d1.getName().compareTo(d2.getName());
    int n2 = d1.getAge() - d2.getAge();
    return n2==0?n1:n2;
    }
});

Map集合

  • Map I
    • HashMap C
    • SortedMap I
      • TreeMap C

I表示接口,C表示类

特点

  • 存储任意键值对
  • 键:无序、无下标、不允许重复
  • 值:无序、无下标、允许重复

常用方法

V put(K key,V value);
Object get(Object key);
//返回所有key
Set keySet();
//返回包含所有值的Collection集合
Collection<V> values();
//返回键值匹配的Set集合
Set<Map.Entry<K,V>> entrySet();

遍历方式

  1. 使用keySet方法
for(String key: s) {
    System.out.println(key + " " + m.get(key));
}
  1. 使用entrySet方法
Set<Map.Entry<String, Integer>> ss = m.entrySet();

for (Map.Entry<String, Integer> mm : ss) {
    System.out.println(mm.getKey() + " " + mm.getValue());
}

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

一个Entry类型就是一个键值对

HashMap

JDK1.2加入,线程不安全,运行效率快,允许使用null作为key或者value

存储结构

数组+链表+红黑树

案例使用

HashMap<Student, String> s = new HashMap();
Student a = new Student("芜湖1", 18);
Student b = new Student("芜湖2", 19);
Student c = new Student("芜湖3", 20);
s.put(a,"a");
s.put(b,"b");
s.put(c,"c");
//对象地址不同,会插入。如果想要以属性作为基准,与HashSet相同,重写HashCode和equals方法
s.put(new Student("芜湖3", 20),"d");

System.out.println(s.size());
System.out.println(s.toString());

Set<Student> ss = s.keySet();
for(Student st: ss) {
    System.out.println(st.toString() + " " + s.get(st));
}
Set<Map.Entry<Student, String>>et = s.entrySet();
System.out.println(et.toString());

源码分析

变量

//默认容量
int DEFAULT_INITIAL_CAPACITY = 16;
//最大容量
int MAXIMUM_CAPACITY = 1<<30; 
//默认加载因子,当大于容量的75%时进行扩容
float DEFAULT_LOAD_FACTOR = 0.75f;
//当链表长度大于8时,并且数组长度大于等于64,就将该链表变为红黑树
int TREEIFY_THRESHOLD = 8;
//当链表长度小于6时,调整会链表结构
int UNTREEIFY_THRESHOLD = 6;
哈希表中的数组
Node<K,V>[] table;
//元素的个数
int size

构造方法

//此时size为0,table为null
public HashMap() {
this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

put

源码学习:blog.csdn.net/Z_ChenChen/…

总结

  • HashMap刚创建时table为null,添加第一个元素时容量调整为16
  • 当元素个数大于阀值会进行空若,扩容,扩容为原来的两倍
  • 每当链表长度大于8并且元素个数大于64,则会调整为红黑树
  • 当链表长度小于6时调整为链表
  • jdk1.8以前从链表头插入,1.8以后从尾部插入

TreeMap

实现SortedMap接口(是Map的子接口),可以对key进行自动排序

数据结构

红黑树

使用方法

与TreeSet相同

Collections工具类

常用方法

//反转顺序
void reverse(List<?> list);
//随机重置集合元素顺序
void shuffle(List<?> list);
//升序排序,必须实现Comparable接口
void sort(List<?> list);
//将j复制到i中去,注意需要将i的空间大小变得与j一致再复制
copy(List<?> i,List<?> j)
toArray(Array[] a);
//数组转成集合,集合受限,不能进行增删
List<?> Array.asList(Array);