Collection接口
集合的特点
集合:用于存储、获取、操作对象的容器
数组的不好处:数组的长度不可变
集合的特点:
1、集合的长度是可变的
2、集合中可以存储是任意类型的对象且只能存储对象
Collection
java.util.Collection接口是 List、Set 和 Queue 接口的父接口
boolean add(E e) ;//向集合中添加元素
int size() ;//获取集合中有效元素个数
void clear() ;//清空集合
isEmpty();//判断集合是否为空
addAll(Collection coll);//将coll所有元素们一个一个加进去
contains();//判断集合中是否某个元素是否存在,依据是equals
containsAll();?跟顺序有关吗,无关//
remove();//要删除先判断有没有,contains
removeAll();//删除的是俩个集合的交集
retainAll();//取得是俩个集合的交集
toArray();将集合转数组
验证contains方法跟顺序无关
@Test public void test3(){ //将数组转集合 Collection c1 = Arrays.asList(1, 2, 3, 4, 5, 6); Collection c2 = Arrays.asList(6, 3, 1); //如果此 c1 包含指定 c2 中的所有元素,则返回 true。 System.out.println(c1.containsAll(c2));//true }
List接口
List接口是Collection接口的子接口。特点:存储的元素有序且可重复的。有序 的意思是存进去的顺序和取出来的顺序是一样的。有序的原因是list集合具有索引值。
List接口在Collection接口上增加了一些根据元素索引来操作集合的特有方法
void add(int index, Object ele)
boolean addAll(int index, Collection eles)
Object get(int index);获取指定
int indexOf(Object obj)//从前往后找指定元素的索引值,找不到返回-1
int lastIndexOf(Object obj);//从后往前找
Object remove(int index);//按照指定索引删除。和Collection接口的按照元素删构成重载,删除指定位置元素 返回被删除元素
Object set(int index, Object ele);//替换指定索引元素,返回替换前的元素
List subList(int fromIndex, int toIndex)//截取子集合,左闭右开
Set接口
Set接口是Collection接口的子接口。set集合里的元素是无序不可重复的,且set集合没有下标这个说法。
Set接口实现类:
HashSet:是一个set接口的典型实现类【jdk1.7 底层:数组+链表】LinkedHashSet
TreeSet
迭代器 Iterator
![](https://i-blog.csdnimg.cn/direct/1e2652e5dc6748639b0965323d9c3919.png)
List<String> list = new ArrayList<>();
Iterator<String> it = list. iterator();
while(it. hasNext()){
String obj = it. next();
System. out. println(obj);
}
边遍历 边修改
Iterator<Integer> it = list.iterator();
while(it.hasNext()){
*// do something*
it.remove();
}
//边遍历集合 边修改集合
for (String i : list) {
list.remove(i);
}
List接口实现类
ArrayList是List接口的实现类。ArrayList基于数组,是⼀块连续的内存空间
//new ArrayList,实际底层是个Object类型的数组
transient Object[] elementData;
因此,ArrayList不适合随机插入和删除的操作,更适合随机查找和遍历的操作
ArrayList数组长度
ArrayList不需要在定义时指定数组的长度,在数组长度不能满足存储要求时,ArrayList会创建一个新的更大的数组并将数组中已有的数据复制到新的数组中。
jdk1.7 数组默认长度10;
jdk1.8 数组长度默认为0,做任何小操作则数组长度变为10(懒初始化)
//以下是ArrayList源码 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//以下是ArrayList源码 private void ensureCapacityInternal(int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //DEFAULT_CAPACITY = 10; minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); }
总结:new ArrayList ==== new Object[10]
ArrayList(int initialCapacity) 也可以通过有参构造器指定数组长度
//以下是ArrayList源码部分
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
ArrayList自动扩容
在插⼊时候,会先检查是否需要扩容,如果当前容量+ 1超过数组长度,就会进⾏扩容。
ArrayList的扩容是创建⼀ 个1.5倍的新数组,然后把原数组的值拷贝过去。
//以下是ArrayList源码部分
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);
//参数一:需要复制的原始数组 参数二:新数组的长度
elementData = Arrays.copyOf(elementData, newCapacity);
}
前面是10作为初始容量,请问,第1次扩容后从10 到多少?X ;第2次扩容后从X 到多少? ??
答案,arraylist扩容机制,就是原值的一半,
10 一半 5 ==》 10+5 = 15;
15 一半 7.5 ===》 15 + 取整7 = 22;
ArrayList线程不安全
请写一个Arraylist线程不安全的案例并测试出来bug
为什么它线程不安全?
ArrayList的线程安全可以通过这些⽅案:
使⽤ Vector 代 替 ArrayList。(不推荐,Vector是⼀个历史遗留类 )
使⽤ Collections.synchronizedList 包 装 ArrayList,然后操作包 装后 的 list。
使⽤ CopyOnWriteArrayList 代 替 ArrayList。
在使⽤ ArrayList 时 ,应⽤程序通过同步机制去控制 ArrayList 的读 写。
LinkedList类
LinkedList也是List接口的实现类。LinkedList是一个双向链表。
双向链表的链接方向是双向的,它由若干个节点组成,每个节点都包含下一个节点和上一个节点的指针,所以从双向链表的任意节点开始,都能很方便访问他的前驱结点和后继节点。
LinkedList提供了大量首尾操作的方法,因此在开发时,LinkedList集合也可以作为堆栈,队列的结构使用
LinkedList特有的方法:
void addFirst(Object obj)
void addLast(Object obj)
Object getFirst()
Object getLast()
Object removeFirst();//移除并返回集合中第一个元素,如果集合为空报异常
Object removeLast();//移除并返回集合中最后一个元素,如果集合为空报异常
LinkedList封装队列
如下将LinkedList封装为一个队列
public class SylQueue {
//底层使用LinkedList封装Queue
LinkedList ll=new LinkedList();
public SylQueue() {
}
//添加
public void add(Object obj){
ll.addFirst(obj);
}
//获取
public Object get(){
// return ll.removeFirst();//达成后进先出的堆栈效果
return ll.removeLast();//达成后进后出的队列效果
}
//判断
public boolean isNull(){
return ll.isEmpty();
}
public String getAll(){
return ll.toString();
}
}
public class ExceptionTest {
@Test
public void testA() {
SylQueue sylQueue = new SylQueue();
sylQueue.add("a");
sylQueue.add("b");
sylQueue.add("c");
sylQueue.add("d");
sylQueue.add("e");
String all = sylQueue.getAll();
System.out.println(all);
System.out.println("=================");
Object o = sylQueue.get();
String all2 = sylQueue.getAll();
System.out.println(all2);
System.out.println("=================");
}
}
问题3:
LinkedList中get(index i)和数组有啥区别【源码】
index i <.size(),从前往后找,反之从后往前找。
Vector类
Vector的数据结构和ArrayList一样,都是基于数组实现的,不同的是Vector是线程安全的。
即同一时刻只允许一个线程对Vector进行 写操作(新增、删除、修改),以保证多线程环境下数据的一致性, 但需要频繁地对Vector实例进行加锁和释放锁操作,因此,Vector的读写效率在整体上比ArrayList低。
ListIterator
数组和 List转换
数组转 List :使用 Arrays. asList(array) 进行转换。String[] array = new String[]{"123","456"}; List<String> asList = Arrays.asList(array);
Arrays.asList(…) 方法返回的 List 集合既不是 ArrayList 实例,也不是 Vector 实例而是Arrays的内部类ArrayList
List 转数组:使用 List 自带的 toArray() 方法。List<String> list = new ArrayList<>(); list.add("123"); list.add("456"); Object[] array = list.toArray();
java.util.Set接口
1、HashSet
HashSet 具有以下特点:
- 不能保证元素的排列顺序
- HashSet 不是线程安全的
- 集合元素可以是 null
哈希算法:会将哈希码值变为数组的索引值,hash算法是一种可以从任何数据中提取出其“指纹”的数据摘要算法,它将任意大小的数据映射到一个固定大小的序列上,这个序列被称为hash code。
Hash的公式---> index = HashCode(Key) & (Length - 1)
如果我们往集合中存放自定义的对象,那么保证其唯一,就必须重写hashCode和equals方法建立属于当前对象的比较方式
重写的目的:让内容一样的对象不能存到set集合里。
重写的规则:当两个对象的 equals() 方法比较返回 true 时,这两个对象的 hashCode() 方法的返回值也应相等。
2、HashSet集合存储过程
1、要明白 new HashSet 就相当于new HashMap
//以下HashSet类为源码部分
public HashSet() {
map = new HashMap<>();
}
2、HashSet的add方法,只关注key,不理睬value,根据返回值是否为空来判断是否插⼊元素成功
//以下为源码部分
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
3、如何判断是同一个对象?
用hashcode比较,要比较2次
1. hashcode不同,则是不同对象,到此为止。
2. hashcode相同(发生了hash冲突),则进一步比较,看equals
3、哈希冲突
每个对象都有一个hashCode值,类似于自身的身份证号码。该hashCode值默认出厂来自于Object类的这个方法,
//发现只有方法的声明,没有方法的实现,也即java需要通过Native关键字调用系统底层函数,给我返回值。
//native方法,表示调用底层第3方函数,C语言系统生成的东西
public native int hashCode();
哈希冲突:即hashCode哈希值冲突了,两个不同的对象居然碰撞出来同一个hashCode值
-------------------------
演示哈希冲突1
//演示哈希冲突1:
System.out.println("Aa".hashCode());//2112
System.out.println("BB".hashCode());//2112
演示哈希冲突2:上万次的计算后,hash值会冲突
//演示哈希冲突2:上万次的计算后,hash值会冲突
Set set = new HashSet();
int hashCode;
for (int i = 1; i <=110000 ; i++) {
hashCode = new Object().hashCode();
if(set.contains(hashCode))
{
System.out.println("----出现了hash冲突,在第几次:"+i+"\t hashCode: "+hashCode);
continue;
}
set.add(hashCode);
}
System.out.println(set.size());
哈希冲突,每个人的环境下出现的哈希冲突在第几次一般是固定的。
4、LinkedHashSet
LinkedHashSet 继承 HashSet是链表和哈希表组合的一个数据存储结构,相比于HashSet 多了一个链表维护集合中的元素,效率变低了。
LinkedHashSet遍历的时候直接一根列表,可以按添加顺序遍历,且遍历时不用过滤null值。
public class Demo {
public static void main(String[] args) {
LinkedHashSet set = new LinkedHashSet();
set.add(10);
set.add(8);
set.add(1);
set.add(11);
//LinkedHashSet会按照添加顺序遍历
System.out.println(set);
}
}
5、TreeSet
底层采用红黑树,有自己指定的排序方式。
2.compareTo,返回正数,0,负数
3.要是不是Person类怎么返回呢,应该是抛出异常吧。在TreeSet中应用的时候返回0可以。
如果返回0,在TreeSet中会视为重复,是对的。
4。为啥compareTo方法形参不不直接写Person 【方法重写】
Collection接口遍历
1、增强for循环遍历
for(元素数据类型 变量名:被遍历的集合){
}
注意:增强for循环不擅长改变数组中的值。
代码案例如下:
增强for循环 也可以遍历数组【但是要改变数组中某一个值,改不了】
@Test public void test6() { ArrayList<Integer> arrayList = new ArrayList<>(); arrayList.add(1); arrayList.add(2); arrayList.add(1); System.out.println(arrayList);//[1,2,1] for (Integer i : arrayList) { System.out.println("i="+i); if(i==2){ i=1; } } System.out.println(arrayList);//[1,2,1] } }
结果:
那正确的操作方式呢?
发现,set方法是根据索引去修改值得,在增强for循环里没有索引值,自然也就不能改值了
@Test
public void test6() {
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1);
arrayList.add(2);
arrayList.add(1);
System.out.println(arrayList);//[1,2,1]
for (int i = 0; i < arrayList.size(); i++) {
if(arrayList.get(i)==2){
//public E set(int index, E element)
arrayList.set(i,1);
}
}
System.out.println(arrayList);//[1,2,1]
}
2、迭代器遍历Collection
迭代器Iterator
Collection中有一个方法:Iterator iterator() ;//返回Iterator的实例,交给迭代器遍历
首先看看迭代器对象的3个方法,真老六啊
- hasNext() 如果仍有元素可以迭代,返回true
- next() 返回迭代的下一个元素
- remove() 移除元素
使用迭代器遍历集合是固定写法,如下遍历一个TreeSet
@Test
public void testIter(){
TreeSet set = new TreeSet();
set.add(10);
set.add(8);
Iterator iterator = set.iterator();
while (iterator.hasNext()){
Object next = iterator.next();
System.out.println(next);
}
// System.out.println(iterator.next()); 当next()没有值时,NoSuchElementException
}
迭代器Iterator进行list集合元素的删除
在集合结构发生变化时,迭代器必须重新获取,不然当你下一次调next()方法时,会报异常。
俩个remove方法
下图最重要的就是,你在while循环里调用集合自带的remove方法删除元素是不可取的,想在迭代时删除,可以,但你必须用我迭代器自己的remove方法。
Collections工具类
1、排序
均为Collections中的static方法
reverse(List):反转 List 中元素的顺序
shuffle(List):对 List 集合元素进行随机排序
sort(List):根据元素的自然顺序【自然排序】对指定 List 集合元素按升序排序
sort(List,Comparator):根据指定的 Comparator 产生的顺序【定制排序】对 List 集合元素进行排序
swap(List,int i, int j):将指定 list 集合中的 i 处元素和 j 处元素进行交换
2、查找、替换
均为Collections中的static方法
int binarySearch(List list, Object key)//对List进⾏⼆分查找,返回索引,注意List必须是有序的
Object max(Collection):根据元素的自然顺序,返回给定集合中的最大元素
Object max(Collection,Comparator):根据 Comparator 指定的顺序,返回给定集合中的最大元素
Object min(Collection)
Object min(Collection,Comparator)
int frequency(Collection,Object):返回指定集合中指定元素的出现次数
void copy(List dest,List src):将src中的内容复制到dest中。要满足:dest.size>=src.size
boolean replaceAll(List list,Object oldVal,Object newVal):使用新值替换 List 对象的所有旧值
3、同步控制
synchronizedCollection(Collection<T> c) //返回指定 collection ⽀持的同
步(线程安全的)collection。
synchronizedList(List<T> list)//返回指定列表⽀持的同步(线程安全的)
List。
synchronizedMap(Map<K,V> m) //返回由指定映射⽀持的同步(线程安全的)
Map。
synchronizedSet(Set<T> s) //返回指定 set ⽀持的同步(线程安全的)set。
Vector 和 Enumeration 接口:
Vector采用数组结构存储元素,是线程安全的,效率低
Enumeration 接口是 Iterator 迭代器的 “古老版本”
@Test
public void test1() {
Vector vec = new Vector();
vec.addElement("AA");
vec.addElement("BB");
vec.addElement("CC");
Enumeration elements = vec.elements();
while (elements.hasMoreElements()) {
Object element = elements.nextElement();
System.out.println(element);
}
}