文章目录
一、Collection
单列集合类的根接口
单列集合常用方法:
ArrayList
1.底层是一个object类型的数组
2.默认长度是10
3.扩容每次扩容1.5倍
add方法的底层代码实现思路:
1.元素每次添加会让ArrayList类内部的属性size++
2.通过 ensureCapacityInternal(size + 1),通过此时的size和默认数组的容量大小作比较(当集合没有传递参数由默认数组创建时),选择最大的。若不是默认创建的数组就返回size。
3.确保容量够, ensureExplicitCapacity()
4. 容量不够就调用grow方法进行扩容
总结:判断是否需要扩容,进行扩容。
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
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);
}
1.Set
HashSet:无序,不可重复
add方法底层是利用传入对象的hashcode方法计算出地址,再由该地址存入到哈希表中,存入时若该哈希表上已存在元素,就利用equal方法判断该元素是否等于存入的元素,不等于就在后边以一个链表的形式存入
LinkedHashset:插入顺序,不可重复
关于以上两种集合的些许疑问:
LinkedHashSet 要使用双向链表而不是单向链表?
LinkedHashSet 之所以要使用双向链表而不是单向链表,是为了更好地支持元素的插入和删除操作。使用双向链表,除了可以实现元素的正序遍历外,还可以支持逆序遍历,因此在删除元素时可以更加高效地定位到要删除的节点,并且可以直接修改相邻节点的指针,而无需再次遍历链表。
此外,在迭代器遍历 LinkedHashSet 的过程中,如果需要删除当前元素,使用双向链表也可以提高删除操作的效率。具体来说,遍历器可以直接根据当前指向的节点删除元素,而不必回溯到上一个节点。这样可以避免遍历链表两次,从而提高性能。
综上所述,虽然使用单向链表同样可以实现类似的功能,但双向链表比单向链表在元素插入和删除以及迭代器遍历方面都有更好的效率。因此,LinkedHashSet 在设计时选择使用双向链表来维护元素的插入顺序。
HashSet 底层使用的确实是一个数组加链表的数据结构为啥效率就高?
在 JDK 1.8 及之前的版本中,HashSet 底层使用的确实是一个数组加链表的数据结构来实现的。具体来说,在添加元素时,首先会根据元素的 hashCode 值计算出该元素在数组中的索引位置,如果该位置已经存在元素,则需要比较该位置上的元素与当前元素是否相等。如果相等,则不需要添加该元素;否则需要将元素插入到链表的末尾。
但是,在 JDK 1.8 中,由于哈希冲突过多时链表的查找效率较低,这种实现方式被进一步优化为了数组加红黑树的数据结构。具体来说,当链表长度超过一定阈值时,它会自动转换为红黑树,从而提高查询效率,降低时间复杂度。
因此,虽然 HashSet 的底层确实包含链表,但并不是简单的链表实现,而是结合数组和树进行优化的结果。而 LinkedHashSet 则是在 HashSet 的基础上增加了一个维护插入顺序的双向链表。
Treeset:自然顺序,不可重复(通过二叉树来确保唯一和有序性,因为涉及到自然顺序,那么传入自己的类时要实现Comparable接口,或者在构造方法中存入比较器(外部比较器))
TreeSet在调用add的时候会调用compareTo方法排序。
若是在hashset或linkedhashset中添加自己的类,则要重写hashCode和equal方法
否则添加的元素就可以重复
import java.util.HashSet;
import java.util.Set;
public class test {
//测试hashset既然是根据计算hashset来存值,那么是根据hashset来取数据吗,再取数据时会对hashset值先排序吗。
public static void main(String[] args) {
student s1=new student(18,"小红");
student s2=new student(13,"小李");
student s3=new student(12,"小明");
System.out.println(s1.hashCode());
System.out.println(s2.hashCode());
System.out.println(s3.hashCode());
Set<student> set=new HashSet();
set.add(s1);
set.add(s2);
set.add(s3);
System.out.println(set);
}
764514
758387
758036
[student [age=13, name=小李], student [age=18, name=小红], student [age=12, name=小明]]
}
假设都能放入,那么遍历时假设会先拿到第一个,再一次拿,那么输出就是输入顺序了,但不是。
猜想1:在添加时会会以一种算法计算底层数组(假设是数组)的索引,并插入.
不是直接由hashcode值决定,原以为是按照hashcode值的大小来决定输出顺序的。
查阅资料:
HashSet底层维护的table数组添加的顺序是由加入的字符串中的具体字符以及字符串的长度决定的,并不是由添加的顺序决定的。HashSet底层:数组+链表的结构。
2.比较器
内部比较器,通俗的说就是把比较器写在类的内部,类实现了Comparable接口,重写了CompareTo方法,这个类就拥有了内部的比较器,就说明这个类支持排序,比如说放入HashSet的自定义元素,如果没有比较性是不能放进去的。
外部比较器,通俗的说就是把比较器写在类的外部,新定义一个类,类实现了Comparator接口,重写compare方法。
二、Map
双列集合类的根接口
HashMap:
key:唯一无序
LinkedHashMap:
key(唯一,有序)Linkedhashset
value(不唯一,无序)
TreeMap:
key(唯一,有序)Treeset
value(不唯一,无序)
Set与Map的关系:
1.Set底层用的是Map
2.Set底层只用了Map的key,存储时将元素放到了key上,value上是new object()
遍历
Map<Integer,String> m=new HashMap<>();
m.put(1, "java");
m.put(2, "js");
//遍历
Set<Integer> s=m.keySet();
for(Integer key:s) {
System.out.println(key+":"+m.get(key));
}
Set<Entry<Integer,String>> en=m.entrySet();
Iterator<Entry<Integer, String>> iterator = en.iterator();
while(iterator.hasNext()) {
Entry<Integer, String> next = iterator.next();
System.out.println(next);
}
引用
1.hashMap是无序的,TreeMap是有序的
2.hashMap允许一个null值作为key,可以有多个null值作value;TreeMap不允许有null值作为key,但是可以有多个null值作为value。
3.hashMap的增删改查速度相对快,TreeMap的相对慢
4.hashMap的占用空间相对大,TreeMap的占用空间相对小
关于集合的键可否为null的问题:
链接: link
三、迭代器
一个集合实现了Iterable接口就,就有iterator()方法,这个方法返回一个实现了Iterator接口的对象,实现了Iterator接口的对象可作为迭代器使用
foreach底层用了迭代器。
迭代器方法:
remove()
hasnext()
next()
List list=new ArrayList<>();
list.add(1);
list.add(2);
Iterator iterator = list.iterator();
while(iterator.hasNext()) {
iterator.next();//调用一次指针就往后一次,最开始在0和-1之间的位置
iterator.remove();
System.out.println(iterator.next());
}
while(iterator.hasNext()) {
System.out.println(iterator.next());
}//同一个迭代器只能迭代一次
System.out.println("--");
System.out.println(list);
普通迭代器在运转时不允许对原来的集合进行添加删除操作
迭代器出现的异常:
官方解释:
ListIterator()接口是Iterator()接口的子接口,其在迭代时可以自由添加删除元素,不受影响。
Iterator()接口给所有的Collection使用
ListIterator()接口给List使用
三、Collections
这是个类
List<student> list=new ArrayList<>();
list.add(new student(18,"tom"));
list.add(new student(19,"job"));
System.out.println(list);
//Collections.sort(list);
Collections.sort(list,new Comparator<student>(){
@Override
public int compare(student o1, student o2) {
if(o1.getAge()==o2.getAge()) {
return o1.getName().length()-o2.getName().length();
}
return o2.getAge()-o1.getAge();
}
});
System.out.println(list);
sort(List list)
根据元素的自然顺序 对指定列表按升序进行排序。
无外乎就是根据list中元素的内部比较器,比如list中传string类型,则根据string类型的compareto方法来对字符串首字母按ascll排序。
sort(List list, Comparator<? super T> c)
根据指定比较器产生的顺序对指定列表进行排序。
此外还有reverse,fill,shuffle等方法
shuffle()用来打乱集合数据,每次打乱的顺序不一