Collection与Collections
Java Collections框架中包含了大量的集合接口以及这些接口的实现类和操作他们的算法:排序,查找,反转,复制,替换,取最大值等等。主要有List(列表)、Set(集合)、Queue(队列)、Stack(栈)、Map(映射表)等数据结构。 其中List、Set、Queue、Stack都继承自Collection。
Collection是整个集合框架的基础,它里面存储一组对象来表示不同类型的Collections,他的作用是提供维护一组对象的基本接口。
Collection和Collections的区别:Collection是一个集合接口。他提供了对集合对象进行基本操作的通用方法。改接口的设计目的是为各种具体的接口提供最大化的统一的操作方式。
Collections是针对集合类的一个包装类,提供一系列的静态方法以实现对各种集合的排列、线程安全化等操作,其中大多数都是用来处理线性表。Collections类不能实例化,如同一个工具类,服务于Collection。在使用Collections类中的方法shi,如果Collection对象为null,则会抛出异常:NullPointerException。
List
又称为有序的Collection。按照对象的存入顺序保存对象,能够存入重复对象。常用实现该接口的类有:ArrayList、LinkedList、Vector。这三个类均在java.util包中,可以动态改变程度,均为可伸缩数组。
- ArrayList:线程不安全的,查找块,删除或修改慢。
ArrayList会在内存中开辟一块连续的内存空间来存储数据,存储的数据是连续的,因此,它支持通过下标来访问元素,索引数据的速度比较快,时间复杂度为O(1)。但是插入、删除数据时的效率比较低。
ArrayList有一个初始大小,但存储的元素超过这个初始大小时,他就会动态地扩充他的存储空间。为了提高程序效率,扩充空间时不会一个存储单元地扩充,而是以此增加多个存储单元,默认扩充为原来的1.5倍。 - Vector:线程安全的,查找块,删除或修改慢。
Vector和ArrayList相似,查找快,增删慢,明显的区别在于Vector的绝大多数方法(比如:add、insert、remove、equals、hashcode)都是直接或者间接同步的,所以它是是线程安全的类,而且当容量不足时会扩充为原来的2倍。 - LinkedList:线程不安全,查询慢,增删快。
LinkedList是基于双向链表实现的,因此它访问数据的速度比较慢,因为访问数据时需要从头开始遍历,时间复杂度为O(n),但是它插入和删除数据的速度比ArrayList和Vector快。
list的常用方法:
//添加元素
void add(E element);
//在指定位置插入元素,后面的元素都往后移一个元素。
void add(int index, E element);
//在指定的位置中插入c集合全部的元素,如果集合发生改变,则返回true,否则返回false。
boolean addAll(int index, Collection<? extends E> c);
//获取元素
E get(int index);
//返回list集合中第一次出现o对象的索引位置,如果没有,返回-1
int indexOf(Object o);
//删除指定索引对象
E remove(int index);
//在索引index的位置的元素更改为element
E set(int index, E element);
//返回从索引fromIndex到toIndex的元素集合,包左不包右
List<E> subList(int fromIndex,int toIndex);
2.数组、String与List之间转换
//list转String
//1.采用java8 String.join 字符串拼接
List<String> list = new ArrayList();
list.add("测试1");
list.add("测试2");
list.add("测试3");
String str = String.join(",",list);
//2.采用流的方式来写
String string2 = list2.stream().collect(Collectors.joining(","));
//String转list
//1.split方法
List<String> list2 = Arrays.asList(str.split(","));
//2.采用流的方式
//此处为了将字符串中的空格去除做了一下操作
List<String> list= Arrays.asList(str .split(",")).stream().map(s -> (s.trim())).collect(Collectors.toList());
//数组转换成集合:
Arrays.asList(数组变量名)
//集合转换成数组:
集合变量名.toArray();
3.List遍历
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
list.add("c");
//迭代器
Iterator it = list.iterator();
while (it.hasNext()){
String str = (String) it.next();
System.out.println(str);
}
//for循环
for (int i=0;i<list.size();i++){
System.out.println(list.get(i));
}
//加强for
for (String str : list){
System.out.println(str);
}
}
注意:如果在for循环中删除元素会报异常:ConcurrentModificationException,解决办法:使用Iterator 遍历,在遍历中删除元素不会报错。
4.List其他方法
//集合是否包含元素o
list.contains(Object o);
//集合list是否包含集合c中的所有元素
list.containsAll(Collection<?> c)
Set
set集合继承自Collection。元素无序,不可重复,而且向set集合中插入元素的时候不能够指定插入的位置。常用的实现类有:HashSet,TreeSet。
HashSet 效率要高于TreeSet,因为HashSet采用散列算法快速对集合进行增删改查 时间复杂度更是几乎接近O(1),但内部无序。
TreeSet 内部有序,可根据指定规则去排序。但效率要比较HashSet低。时间复杂度位O(log n)。
- HashSet:无序,不可重复,内部封装的是HashMap,使用HashMap的key的位置来存储元素。
元素不可重复
两个元素不能相同,即指向的堆上的对象不能是用一个对象。判断是否相同的方法:先使用hashCode()方法得到元素的hash值,如果hash值,如果hash相同,再使用equals()比较,如果返回true,说明两个元素是相同的,只存其中一个,如果hash值不同,说明两个元素不相同。(如果两个对象相等,hashCode值相等,但是如果hashCode值相等,但对象不一定相等。所以如果用自定的类做元素存入set时,需要重写hashCode()和equals())。
@Data
public class Student {
private int id;
private String name;
private int age;
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
//重写equals方法
@Override
public boolean equals(Object obj) {
if (obj!=null && obj instanceof Student){
Student s = (Student) obj;
if (this.id == s.id && this.age == this.age){
if (this.name != null){
return this.name.equals(s.name);
}else {
return s.name == null;
}
}
}
return false;
}
//重写hashCode方法
@Override
public int hashCode(){
return this.id + this.age;
}
}
设置两个相同的Student对象放入set
public static void main(String[] args) {
Student s1 = new Student(1,"张三",18);
Student s2 = new Student(1,"张三",18);
HashSet set = new HashSet();
set.add(s1);
set.add(s2);
System.out.println(set.size());
}
结果是1,如果没有重写hashCode和equals方法,则得到的结果是2。
- TreeSet
TreeSet是基于TreeMap实现的,因为TreeMap底层是红黑树,所以TreeSet其实也是基于红黑树实现的。他有个特点:插入是无序的,但内部的元素是有序的。
插入的对象实现Comparable接口,通过compareTo方法去比较大小,或者在实力化TreeSet的时候自定义排序Comparator方法内部的int compare(T o1, T o2)比较对象大小。
如果插入数据即不实现Commparable或在插入的时候也不指定排序方式Comparator那么就会报错。
证明TreeSet是有序的
public static void main(String[] args) {
Set set = new java.util.TreeSet();
set.add("ccc");
set.add("aaa");
set.add("ddd");
set.add("bbb");
set.forEach((str) -> System.out.println(str));
}
输出结果
如果存的元素是自定义的对象,且方需要使用其中一个对象进行排序时:自定义对象实现Comparable 方法,并重写compareTo方法。
@Data
public class Student implements Comparable<Student>{
private int id;
private String name;
private int age;
public Student(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
@Override
public int compareTo(Student o) {
// TODO Auto-generated method stub
return this.age - o.age;
}
}
public static void main(String[] args) {
Set<Person> set = new TreeSet<>();
set.add(new Person(1, "张三", 18));
set.add(new Person(2, "王五", 23));
set.add(new Person(3, "李四", 17));
set.forEach((str) -> System.out.println(str));
}
输出结果
Set集合遍历
- 迭代器
Iterator it = set.iterator();
while(it .hasNext()) {
System.out.println(iterator.next());
}
- for
for (Object object : set) {
System.out.println(object);
}
- stream流
set.forEach((x) -> System.out.println(x));
Map
Map是用来存储键值对的数据结构,常用的有三个实现类:HashMap,HashTable,TreeMap。在数组中是通过数组下标来对其中的元素进行访问的,而在Map中,是通过对象来进行索引的,这个用来索引的对象就是key,与其对应的对象就是value。
HashMap是最常用的Map,是HashTable的轻量实现(想成不安全)。HashMap和HashTable的区别有:
- HashMap允许有为null的key,但是有且只能有一个为null的key,HashTable则不允许。
- HashMap的效率高于HashTable,因为HashTable是线程安全的,而HashMap不支持线程同步。
如果在多线程环境中使用hashMap
Map map = Collections。synchronizedMap(new HashMap());
- HashTable中,hash数组默认大小是11,增加的方式是old2+1。HashMap的默认大小是16,扩容因子是0.75,增价方式是old2,而且一定是2的指数。
- HashTable使用Enumeration,HashMap使用Iterator。
向Map中添加键值对
向Map中添加键值对时,需要经过如下几个步骤:首先,调用key的hashCode()方法生成一个hash值h1,如果h1在hashMap中不存在,则直接将<key,value>添加到HashMap中;如果h1存在,则找出所有hash值为h1的key,然后分别调用key的equals()方法判断当前添加的key值是够与存在的key值相同。如果equals返回true,说明当前添加的key已经存在,那么新值就会覆盖掉旧值;如果equals()返回false,会在HashMap中创建新的映射关系。
因为对于不同的key值可能会产生相同的hsah值,当新增的key的hash值已经存在与HashMap中是,就会产生冲突,一般使用开放地址法,再hash法,链地址法等解决冲突。HashMap使用的是链地址法来解决hash冲突的。
Map的结构:
总结:HashMap工作原理:HashMap是基于哈希的原则工作的,HashMap类实现了Map<k,v>接口,使用put(key, value)存储对象,使用get(key)从HashMap中获取对象。HashMap有一个内部类Entry<k,v>,存储对象时,先对key调用hashCode方法,返回一个hashCode值找到存储位置,来存储Entry对象,如果该存储位置有值(即HashMap中存在相同的hashcode值),则调用key的equals方法比较这些值,如果返回true就覆盖,返回false就存储在下一个节点形成链表,反之如果没有相同的hashCode值,则直接存储。
HashMap内部类:
static class Node<K,V> implements Map.Entry<K,V> {
//哈希值,就是位置
final int hash;
//键
final K key;
//值
V value;
//指向下一个几点的指针
Node<K,V> next;
//...
}
java8以前HashMap是基于数组+链表实现的,但是即使哈希函数再好,也很难达到元素百分百均匀分布,这时当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下就会有一条很长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),导致效率低下。所以JDK 1.8 中引入了 红黑树来优化这个问题,它的查询的时间复杂度为:查找时间复杂度为 O(logn)。