集合基础
Collection 和 Collections的区别?
java.util.Collection 是一个集合接口。
java.util.Collections 是一个包装类。
1、Collections是针对集合类的一个帮助类,他提供一系列静态方法实现对各种集合的搜索、排序、线程安全化等操作。
2、Collection是集合类的上级接口,继承与他的接口主要有Set 和List。
3、Collection定义了集合所需要的基本方法,它继承Iterable接口,所以能够使用iterator方法返回迭代器,并且所有集合类都能够使用for-each循环。
1.Collections
List<Integer> list = new ArrayList<>();
list.add(132);
list.add(45);
list.add(373);
System.out.println(list);//[132, 45, 373]
java.util.Collections.addAll(list, 12,45,79);
System.out.println(list);//[132, 45, 373, 12, 45, 79]
System.out.println(java.util.Collections.max(list));//37
System.out.println(java.util.Collections.min(list));//12
java.util.Collections.reverse(list);// -反转
System.out.println(list);//[79, 45, 12, 373, 45, 132]
java.util.Collections.replaceAll(list, 373, 25);
System.out.println(list);//[79, 45, 12, 25, 45, 132]
java.util.Collections.sort(list); //sort() 方法进行的是默认排序,
System.out.println(list);//[12, 25, 45, 45, 79, 132]
boolean isEmpty():如果集合元素为空,则返回true
boolean contains(Object o):如果集合包含指定元素则返回true。
shuffle() List 集合元素进行随机排序,类似洗牌
boolean remove(Object o):删除集合中指定元素。
boolean removeAll(Collection<?> c):从调用集合中删除c中的所有元素,如果调用集合发生改变,则返回true
1.1Comparator 比较器
由于sort()方法进行的是默认排序,Comparator 比较器进行的是指定顺序。
一种是比较死板的采用 java.lang.Comparable 接口去实现,
一种是灵活的当需要做排序的时候在去选择的java.util.Comparator 接口。
1.1.1 Comparable 和 Comparator 两个接口的区别
Comparable:
强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的 compaetTo 方法被称为她的自然比较方法。
只能在类中实现 compareTo() 一次,不能经常修改类的代码实现自己想要的排序,实现此接口的对象列表(和数组)可以通过 Collection.sort (和 Arrays.sort)进行自动排序,对象可以用作有序映射中的键或有序集合中的元素,无序指定比较器。
//pojo中 实现接口
public class Student implements Comparable<Student>{
....
@Override
public int compareTo(Student o) {
return this.age-o.age;//升序
}
}
comparator:
强行对某个对象进行整体排序。可以将 Comparator 传递给 sort 方法(如Collection.sort 或 Array.sort),从而允许在排序上实现精确控制。
还可以使用 Comparator 来控制某些数据结构(如有序 set 或有序的映射)的顺序,或者为那些没有自然顺序的对象 collection 提供排序
//如果年龄相同,按姓名第一个字母排序
Collections.sort(list, new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
// 年龄降序
int result = o2.getAge()-o1.getAge();//年龄降序
if(result==0){//第一个规则判断完了 下一个规则 姓名的首字母 升序
result = o1.getName().charAt(0)-o2.getName().charAt(0);
}
return result;
}
});
2.Collection
共有的常用方法
c.clear();//清空集合
c.contains(3) );//判断是否包含
c.equals("1,2,3,4") );//判断是否相等
c.hashCode() );//获取哈希码值
c.isEmpty() );//判断是否为空
c.remove(2) );//移除元素
c.size() );//获取集合的长度/元素的个数
c.addAll(c2) );//把集合c2加到c里去
c.containsAll(c2) );//判断c里是否有c2
c.removeAll(c2) );//移除掉c和c2里都有的--取差集
2.1.list set map 的区别?
- List特点:元素有放入顺序,元素可重复的。
- Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉。
- Map 提供了一个更通用的元素存储方法。 Map 集合类用于存储元素对(称作"键"和"值"),其中每个键映射到一个值。
List接口有三个实现类:
- LinkedList基于链表实现,链表内存是散列的,增删快,查找慢;
- ArrayList基于数组实现,非线程安全,效率高,增删慢,查找快;
- Vector基于数组实现,线程安全,效率低,增删慢,查找慢;
Set接口有两个实现类:
- HashSet底层是由 Hash Map 实现,不允许集合中有重复的值,使用该方式时需要重写 equals()和 hash Code()方法;
- TreeSet 需要排序
- LinkedHashSet继承于 HashSet,同时又基于 LinkedHashMap 来进行实现,底层使用的是 LinkedHashMap
Map接口有四个实现类:
- HashMap基于 hash 表的 Map 接口实现,非线程安全,高效,支持 null 值和 null 键;
- HashTable线程安全,低效,不支持null值和null键;
- TreeMap 排序
- LinkedHashMap是 HashMap 的一个子类,保存了记录的插入顺序;
- SortMap 接口TreeMap,能够把它保存的记录根据键排序,默认是键值的升序排序;
2.1.1. List接口
List是一个继承于Collection的接口,即List是集合中的一种。List是有序的队列,List中的每一个元素都有一个索引;第一个元素的索引值是0,往后的元素的索引值依次+1。和Set不同,List中允许有重复的元素。实现List接口的集合主要有:ArrayList(数组)、LinkedList(双向链表)、Vector(数组)。
List接口中常用方法
list.add(3, 9.9);//在指定的索引处,添加指定的数据
list.get(0) );//按照索引获取元素
list.indexOf(2.2) );//获取2.2第一次出现的索引值
list.lastIndexOf(2.2) );//获取2.2最后一次出现的索引值
list.remove(6) );//移除索引6对应的元素
list.set(4, 0.1) );//把索引4的值替换成0.1
list.subList(1, 4) ;//截取子List集合,含头不含尾[1,4)
//迭代List集合方式2: ListIterator<E> listIterator()--List子接口特有的返回子接口--可以向后遍历也提供了逆向遍历
ListIterator<Double> it2 = list.listIterator() ;
//顺序向后遍历
while(it2.hasNext()) {//判断有没有下一个元素
Double next = it2.next() ;//获取下一个元素
}
//逆向遍历--必须先顺序遍历完才能逆向! -- 了解即可
while(it2.hasPrevious()) {//判断有没有前一个元素
Double next = it2.previous() ;//获取前一个元素
System.out.println(next);
}
/***/
//迭代List集合方式3: 因为List集合有下标,所以可以用下标遍历
for(int i = 0 ; i < list.size() ; i++) {//list.size()集合的长度
Double next = list.get(i);//get()根据下标i获取元素
System.out.println(next);
}
/***/
//迭代List集合方式4: foreach / 增强for循环 -- 只能循环 -- 数组|Collection集合
//语法:for(遍历得到的数据的类型 变量名 : 想要遍历的容器){ 循环体 }
for(Double d : list){
System.out.println(d); //打印获取到的数据
}
ArrayList
ArrayList是一个动态数组,也是我们最常用的集合。它允许任何符合规则的元素插入甚至包括null。每一个ArrayList都有一个初始容量:
private static final int DEFAULT_CAPACITY = 10;
随着容器中的元素不断增加,容器的大小也会随着增加。在每次向容器中增加元素的同时都会进行容量检查,当快溢出时,就会进行扩容操作。所以如果我们明确所插入元素的多少,最好指定一个初始容量值,避免过多的进行扩容操作而浪费时间、效率。
size、isEmpty、get、set、iterator 和 listIterator 操作都以固定时间运行。add 操作以分摊的固定时间运行,也就是说,添加 n 个元素需要 O(n) 时间(由于要考虑到扩容,所以这不只是添加元素会带来分摊固定时间开销那样简单)。
ArrayList擅长于随机访问。同时ArrayList是非同步的。
LinkedList
同样实现List接口的LinkedList与ArrayList不同,ArrayList是一个动态数组,而LinkedList是一个双向链表。所以它除了有ArrayList的基本操作方法外还额外提供了get,remove,insert方法在LinkedList的首部或尾部。
由于实现的方式不同,LinkedList不能随机访问,它所有的操作都是要按照双重链表的需要执行。在列表中索引的操作将从开头或结尾遍历列表(从靠近指定索引的一端,节约一半时间)。这样做的好处就是可以通过较低的代价在List中进行插入和删除操作。
与ArrayList一样,LinkedList也是非同步的。如果多个线程同时访问一个List,则必须自己实现访问同步。一种解决方法是在创建List时构造一个同步的List:
List list = Collections.synchronizedList(new LinkedList(…));
System.out.println(list.getFirst());//获取首元素
System.out.println(list.getLast());//获取尾元素
System.out.println(list.removeFirst());//移除首元素
System.out.println(list.removeLast());//移除尾元素
list.offerFirst(666);//添加首元素
list.offerFirst(999);//添加尾元素
System.out.println(list.peekFirst());//获取首元素
System.out.println(list.peekLast());//获取尾元素
System.out.println(list.pollFirst());//移除首元素
System.out.println(list.pollLast());//移除尾元素
Vector
与ArrayList相似,但是Vector是同步的。所以说Vector是线程安全的动态数组。它的操作与ArrayList几乎一样。
Stack
Stack继承自Vector,实现一个后进先出的堆栈。Stack提供5个额外的方法使得Vector得以被当作堆栈使用。基本的push和pop方法,还有peek方法得到栈顶的元素,empty方法测试堆栈是否为空,search方法检测一个元素在堆栈中的位置。Stack刚创建后是空栈。
2.1.2. Set接
Set是一个继承于Collection的接口,Set是一种不包括重复元素的Collection。它维持它自己的内部排序,所以随机访问没有任何意义。与List一样,它同样运行null的存在但是仅有一个。由于Set接口的特殊性,所有传入Set集合中的元素都必须不同,关于API方面。Set的API和Collection完全一样。实现了Set接口的集合有:HashSet、TreeSet、LinkedHashSet、EnumSet。
HashSet
HashSet堪称查询速度最快的集合,因为其内部是以HashCode来实现的。集合元素可以是null,但只能放入一个null。它内部元素的顺序是由哈希码来决定的,所以它不保证set的迭代顺序;特别是它不保证该顺序恒久不变。
TreeSet
TreeSet是二叉树实现的,基于TreeMap,生成一个总是处于排序状态的set,内部以TreeMap来实现,不允许放入null值。它是使用元素的自然顺序对元素进行排序,或者根据创建Set时提供的 Comparator 进行排序,具体取决于使用的构造方法。
LinkedHashSet
LinkedHashSet集合同样是根据元素的hashCode值来决定元素的存储位置,但是它同时使用链表维护元素的次序。这样使得元素看起 来像是以插入顺序保存的,也就是说,当遍历该集合时候,LinkedHashSet将会以元素的添加顺序访问集合的元素。LinkedHashSet在迭代访问Set中的全部元素时,性能比HashSet好,但是插入时性能稍微逊色于HashSet。
2.1.3. Map接口
Map接口常用方法
map.containsKey(900)); //查询是否包含指定key
map.containsValue("如花"));//查询是否包含指定value
map.equals("唐伯虎"));//判断是否相等
map.get(9528));//根据key值获取value值
map.hashCode());//获取hashCode值
map.isEmpty());//判断是否为空
map.remove(null));//根据key值删除元素
map.size());//查询map集合中元素个数/map的长度
/***/
//迭代map集合方式---键集
//Set<K> keySet() --把map里的key存到set里
Set<Integer> set = map.keySet();
//使用迭代器 遍历set集合
Iterator<Integer> it = set.iterator();
while(it.hasNext()){
//得到每一个key
Integer key = it.next();
//用key会map中找回value值
String value = map.get(key);
System.out.println("key :"+key+"\t"+"value :"+value);
}
/***/
//遍历set集合 foreach
for (Integer key : set) {
//拿着key回map里找value
String value = map.get(key);
System.out.println(key+"------"+value);
}
//把map里的value们存入Collection --只能获取值
Collection<String> c = map.values();
System.out.println(c);
/***/
//把map中 key,value 同时封装成Entry对象存入set
Set<Entry<Integer, String>> set2 = map.entrySet();
//遍历set集合 --得到每一个Entry对象
for (Entry<Integer, String> entry : set2) {
Integer key = entry.getKey(); //获取key
String value = entry.getValue();//获取value
System.out.println(key+"======="+value);
}
遍历
Map与List、Set接口不同,它是由一系列键值对组成的集合,提供了key到Value的映射。在Map中它保证了key与value之间的一一对应关系。也就是说一个key对应一个value,所以它不能存在相同的key值,当然value值可以相同。实现map的集合有:HashMap、HashTable、TreeMap、WeakHashMap。
HashMap
以哈希表数据结构实现,查找对象时通过哈希函数计算其位置,它是为快速查询而设计的,其内部定义了一个hash表数组(Entry[] table),元素会通过哈希转换函数将元素的哈希地址转换成数组中存放的索引,如果有冲突,则使用散列链表的形式将所有相同哈希地址的元素串起来,可能通过查看HashMap.Entry的源码它是一个单链表结构。
HashTable
也是以哈希表数据结构实现的,解决冲突时与HashMap也一样也是采用了散列链表的形式。HashTable继承Dictionary类,实现Map接口。其中Dictionary类是任何可将键映射到相应值的类(如 Hashtable)的抽象父类。每个键和每个值都是一个对象。在任何一个 Dictionary 对象中,每个键至多与一个值相关联。Map是”key-value键值对”接口。 HashTable采用”拉链法”实现哈希表不过性能比HashMap要低。
TreeMap
有序散列表,实现SortedMap接口,底层通过红黑树实现。
WeakHashMap
谈WeakHashMap前先看一下Java中的引用(强度依次递减)
-
强引用:普遍对象声明的引用,存在便不会GC
-
软引用:有用但并非必须,发生内存溢出前,二次回收
-
弱引用:只能生存到下次GC之前,无论是否内存足够
-
虚引用:唯一目的是在这个对象被GC时能收到一个系统通知
以弱键实现的基于哈希表的Map。在 WeakHashMap 中,当某个键不再正常使用时,将自动移除其条目。更精确地说,对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。丢弃某个键时,其条目从映射中有效地移除,因此,该类的行为与其他的 Map 实现有所不同。null值和null键都被支持。该类具有与HashMap类相似的性能特征,并具有相同的效能参数初始容量和加载因子。像大多数集合类一样,该类是不同步的。
2.1.4. 总结
1、List、Set都是继承自Collection接口,Map则不是。
2、List特点:元素有放入顺序,元素可重复 ,Set特点:元素无放入顺序,元素不可重复,重复元素会覆盖掉,(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的,加入Set 的Object必须定义equals()方法 ,另外list支持for循环,也就是通过下标来遍历,也可以用迭代器,但是set只能用迭代,因为他无序,无法用下标来取得想要的值。)
3、Set和List对比:
Set:检索元素效率低下,删除和插入效率高,插入和删除不会引起元素位置改变。
List:和数组类似,List可以动态增长,查找元素效率高,插入删除元素效率低,因为会引起其他元素位置改变。
4、Map适合储存键值对的数据
5、线程安全集合类与非线程安全集合类 :
- LinkedList、ArrayList、HashSet是非线程安全的,Vector是线程安全的;
- HashMap是非线程安全的,HashTable是线程安全的;
面试常见题:
附: StringBuilder是非线程安全的,StringBuffer是线程安全的。
附: Array和ArrayList有什么区别和不同点:
1、Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
2、Array大小是固定的,ArrayList的大小是动态变化的。
3、ArrayList提供了更多的方法和特性,比如:addAll(),removeAll(),iterator()等等。
附:**Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用==还是 equals()? **
Set 里的元素是不能重复的,元素重复与否是使用 equals()方法进行判断的。
附: ArrayList和LinkedList的区别是什么?
1、ArrayList是基于数组实现,LinkedList是基于链表实现。
2、ArrayList在查找时速度快,LinkedList在插入与删除时更具优势。
附:HashMap 和 HashSet、 Hashtable 的区别?
HashMap :实现Map接口;使用hash算法,里面的数据是无序的;并且存储的是键值对;
HashSet :实现了Set接口;内部封装了HashMap,故也是无序的;HashSet本质就是HashMap,数据存储到HashSet的Key部分,Value部分被屏蔽不使用了。
同步性:Hashtable 是线程安全的,也就是说是同步的,而HashMap 是线程序不安全的,不是同步的。由于同步检查所以Hashtable性能稍慢。
附:简述HashMap的工作原理?
HashMap是面向查询优化的数据结构,查询性能优异。
在其内部利用数组存储数据。
插入数据时,先根据Key的HashCode计算出数组的下标位置,再利用Key的equals()方法检查是否以存在重复的Key,如果不重复直接存储到数组中,如果重复就作为链表存储到散列桶中。
插入的数据和数组容量的比值大于加载因子则进行数组扩容,并重新散列,默认的加载因子为“0.75”。
查询时,先根据Key的HashCode计算出数组的下标位置,再利用Key的equals()方法检查到Key的位置,如果找到返回Key对应的Value,否则返回Null。
由于利用Key的HashCode直接计算出数据的存储位置,由于直接确定数据的存储位置,相对于其他查找方式,查询效率非常高。
附:迭代器 Iterator 是什么?
Iterator 接口提供遍历任何 Collection 的接口。我们可以从一个 Collection 中使用迭代器方法来获取迭代器实例。迭代器取代了 Java 集合框架中的 Enumeration,迭代器允许调用者在迭代过程中移除元素。
List<String> list = new ArrayList<>();
....
Iterator<String> it = list. iterator();
while(it. hasNext()){
String obj = it. next();
System. out. println(obj);
}
附:Iterator 和 ListIterator 有什么区别?
Iterator 可以遍历 Set 和 List 集合,而 ListIterator 只能遍历 List。
Iterator 只能单向遍历,而 ListIterator 可以双向遍历(向前/后遍历)。
ListIterator 继承于 Iterator 接口,然后添加了一些额外的功能,比如添加一个元素、
附:** 队列**
队列是一种非常重要的数据结构,它具有先进先出的特点。元素从尾部添加,从头部移出。
队列有两种:单队列和循环队列。 Java集合的Queue接口继承自Collection接口
单队里就是最普通的队列,单队列对于空间利用率非常低,因为队头移出元素后就不能使用了,而循环队列是对他的一种改进。队列一般用于缓存、并发访问等场景。
Queue除了提供Collection里面的方法,它又根据队列的特性提供了一些方法。
Queue建议不要添加null元素,如果添加null元素,则抛出NullPointException。
这样做的目的就是为了防止获取的元素为null时不知道是对还是错。Queue下面的实现类基本都复合了该要求,但是LinkedList没有对这一要求实现。
Deque是Double ended queue的缩写,即双端队列。Dequeu支持从两端添加和删除元素。Deque继承了Queue接口。
Deque除了提供Queue中的指定方法外,还分别提供了操作队前元素和队尾操作的方法。