1.集合框架
数组初始化后长度确定-------------------------不便于扩展
声明的类型,决定初始化类型
提供属性方法少,不便于添加、删除、插入操作,效率不高,无法直接获取存储元素个数
数组存储的数据是有序、可以重复的
2.Collection接口方法
@Test
public void Test(){
Collection collection = new ArrayList();
Collection collection1 = new ArrayList();
//1.添加元素
collection.add(123);
collection.add("asd");
collection.add(new Date());
collection1.addAll(collection);//添加一个集合
//2.获取有效元素个数
int num = collection.size();
System.out.println(num);
//3.是否空集合
System.out.println(collection.isEmpty());
//4.是否包含某个元素
System.out.println(collection.contains(123));
System.out.println(collection.containsAll(collection1));//比较两个集合是否相等
System.out.println("----------------------");
//5.删除某个元素
collection.remove(123);
System.out.println(collection.size());
collection1.removeAll(collection);//删除相同的元素
System.out.println(collection1.size());
//6.取两个集合的交集
System.out.println(collection);//[asd, Tue Feb 22 22:42:34 CST 2022]
System.out.println(collection1);//[123]
collection.retainAll(collection1);//取两个集合的交集,并保存在前面的集合中,所以collection变成空的了
System.out.println(collection);//[]
//7.判断集合是否相等
System.out.println(collection.equals(collection1));//false 不相等
System.out.println("=================");
//8.集合-》数组
collection1.add("sadad");
Object [] arr=collection1.toArray();
for (int i = 0; i < arr.length; i++) {
System.out.println(arr[i]);
}
System.out.println("++++++++++++++++++++++++");
//9。返回当前对象的hash值
System.out.println(collection1.hashCode());//109198623
//10.数组-》集合 Arrays.asList()
List list = Arrays.asList(new int[]{12,33,24});
System.out.println(list);
List list1 = Arrays.asList(new Integer[]{12,33,24});
System.out.println(list1);
List list2 = Arrays.asList(new String[]{"12","33","24"});
System.out.println(list2);
List list3 = Arrays.asList("12SSD","ADSDsA","ASD3");
System.out.println(list3);
}
3.迭代器接口Iterator
//迭代器测试iterator
@Test
public void Test1(){
//1.迭代器只用于遍历集合,首先要有一个集合
Collection collection = new ArrayList();
collection.add("22dd");
collection.add("vssdv");
collection.add(123);
//2.需要迭代器对象
Iterator iterator = collection.iterator();
//3.迭代器是指针形式,需要先判断指针指向的下一个元素是否为空?
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
//迭代器测试remove
@Test
public void Test2(){
Collection collection = new ArrayList();//list存放有序、可重复的数据
collection.add(123);
collection.add(456);
collection.add("qwe");
collection.add("asd");
collection.add(123);
collection.add("qwe");
Iterator iterator = collection.iterator();
while (iterator.hasNext()){
Object o = iterator.next();
if ("qwe".equals(o)){
iterator.remove();
}
}
iterator = collection.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
@Test
public void Test3(){
Collection collection = new ArrayList();
collection.add("123");
collection.add("qwe");
collection.add(123);
for (Object obj : collection) {
System.out.println(obj);
}
}
4.List接口,有序(按照添加顺序)、可重复
1.ArrayList:线程不安全,效率高,使用数组存储2.LinkedList:使用双向链表存储,对于频繁的插入、删除效率高3.Vector:线程安全,效率低,使用数组存储
1.ArrayList
/**
* .ArrayList的源码分析:
* 1 jdk 7情况下,先创建一个长度为10的数组
* ArrayList list = new ArrayList();//底层创建了长度是10的Object[]数组elementData
* list.add(123);//elementData[0] = new Integer(123);
* ...
* list.add(11);//如果此次的添加导致底层elementData数组容量不够,则扩容。
* 默认情况下,扩容为原来的容量的1.5倍,同时需要将原有数组中的数据复制到新的数组中。
*
* 结论:建议开发中使用带参的构造器:ArrayList list = new ArrayList(int capacity)
*
* 2 jdk 8中ArrayList的变化:添加元素时才创建长度为10的数组
* ArrayList list = new ArrayList();//底层Object[] elementData初始化为{}.并没有创建长度为10的数组
*
* list.add(123);//第一次调用add()时,底层才创建了长度10的数组,并将数据123添加到elementData[0]
* ...
* 后续的添加和扩容操作与jdk 7 无异。
* 3 小结:jdk7中的ArrayList的对象的创建类似于单例的饿汉式,而jdk8中的ArrayList的对象
* 的创建类似于单例的懒汉式,延迟了数组的创建,节省内存。
*
*/
2.LinkedList
- 对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高
- LinkedList:双向链表,内部没有声明数组,而是定义了Node类型的first和last,用于记录首末元素。同时,定义内部类Node,作为LinkedList中保存数据的基本结构。
/**
* 3.LinkedList的源码分析:
* LinkedList list = new LinkedList(); 内部声明了Node类型的first和last属性,默认值为null
* list.add(123);//将123封装到Node中,创建了Node对象。
*
* 其中,Node定义为:体现了LinkedList的双向链表的说法
* private static class Node<E> {
* E item;
* Node<E> next;
* Node<E> prev;
*
* Node(Node<E> prev, E element, Node<E> next) {
* this.item = element;
* this.next = next; //next变量记录下一个元素的位置
* this.prev = prev; //prev变量记录前一个元素的位置
* }
* }
*/
参考链接:https://blog.csdn.net/PorkBird/article/details/113727330
3.Vector
Vector 是一个古老的集合,JDK1.0就有了。大多数操作与ArrayList相同,区别之处在于Vector是线程安全的。
在各种list中,最好把ArrayList作为缺省选择。当插入、删除频繁时,使用LinkedList;Vector总是比ArrayList慢,所以尽量避免使用。
/**
* 4.Vector的源码分析:jdk7和jdk8中通过Vector()构造器创建对象时,底层都创建了长度为10的数组。
* 在扩容方面,默认扩容为原来的数组长度的2倍。
*/
List接口常用方法
//List接口常用方法
@Test
public void Test4(){
//增:add(int index, Object ele):在index位置插入ele元素
//删:Object remove(int index):移除指定index位置的元素,并返回此元素
//改:Object set(int index, Object ele):设置指定index位置的元素为ele
//查:Object get(int index):获取指定index位置的元素
//boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
//int indexOf(Object obj):返回obj在集合中首次出现的位置
//int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
//List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
ArrayList arrayList = new ArrayList();
//1.增
arrayList.add(123);
arrayList.add("asdf");
arrayList.add("rrgb");
arrayList.add(1,"nihao");
System.out.println(arrayList);
//2.删
arrayList.remove(1);
System.out.println(arrayList);
//3.改
arrayList.set(1,"nihao");
System.out.println(arrayList);
//4.查
Object obj=arrayList.get(1);
System.out.println(obj);
//5.boolean addAll(int index, Collection eles):从index位置开始将eles中的所有元素添加进来
ArrayList arrayList1 = new ArrayList();
arrayList1.add(999);
arrayList1.add(999);
arrayList1.add(999);
arrayList.addAll(1,arrayList1);
System.out.println(arrayList);
//6.int indexOf(Object obj):返回obj在集合中首次出现的位置
//7.int lastIndexOf(Object obj):返回obj在当前集合中末次出现的位置
int startIndex = arrayList.indexOf(999);
System.out.println(startIndex);
int endIndex = arrayList.lastIndexOf(999);
System.out.println(endIndex);
//8.List subList(int fromIndex, int toIndex):返回从fromIndex到toIndex位置的子集合
List sublist = arrayList.subList(1,5);
System.out.println(sublist);
}
@Test
public void Test5(){
// 对于频繁的插入或删除元素的操作,建议使用LinkedList类,效率较高
// 新增方法:
// void addFirst(Object obj)
// void addLast(Object obj)
// Object getFirst()
// Object getLast()
// Object removeFirst()
// Object removeLast()
LinkedList linkedList = new LinkedList();
linkedList.add(123);
linkedList.add("adas");
linkedList.add("uuu");
linkedList.addFirst(999);
linkedList.addLast(999);
System.out.println(linkedList);
System.out.println(linkedList.getFirst());
System.out.println(linkedList.getLast());
Object obj = linkedList.removeFirst();
System.out.println(obj);
obj = linkedList.removeLast();
System.out.println(obj);
System.out.println(linkedList);
}
面试题:
/**
* 请问ArrayList/LinkedList/Vector的异同?谈谈你的理解?
* ArrayList底层是什么?扩容机制?Vector和ArrayList的最大区别?
*
* ArrayList和LinkedList的异同二者都线程不安全,相对线程安全的Vector,执行效率高。
* 此外,ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
* 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
* 对于新增和删除操作add(特指插入)和remove,LinkedList比较占优势,因为ArrayList要移动数据。
*
* ArrayList和Vector的区别Vector和ArrayList几乎是完全相同的,
* 唯一的区别在于Vector是同步类(synchronized),属于强同步类。
* 因此开销就比ArrayList要大,访问要慢。正常情况下,
* 大多数的Java程序员使用ArrayList而不是Vector,
* 因为同步完全可以由程序员自己来控制。Vector每次扩容请求其大小的2倍空间,
* 而ArrayList是1.5倍。Vector还有一个子类Stack。
*/
5.set接口,无序(不按照添加顺序,根据数据哈希值排序)、不可重复
/**
* 1.Set接口的框架:
* |----Collection接口:单列集合,用来存储一个一个的对象
* |----Set接口:存储无序的、不可重复的数据 -->高中讲的“集合”
* |----HashSet:作为Set接口的主要实现类;线程不安全的;可以存储null值
* |----LinkedHashSet:作为HashSet的子类;遍历其内部数据时,可以按照添加的顺序遍历
* 对于频繁的遍历操作,LinkedHashSet效率高于HashSet.
* |----TreeSet:可以按照添加对象的指定属性,进行排序。
*/
1.HashSet
- 底层也是数组,初始容量为16,当如果使用率超过0.75,(16*0.75=12)就会扩大容量为原来的2倍。(16扩容为32,依次为64,128…等)
- 数组+链表的结构。
添加元素的过程:以HashSet为例:
* 我们向HashSet中添加元素a,首先调用元素a所在类的hashCode()方法,计算元素a的哈希值,
* 此哈希值接着通过某种算法计算出在HashSet底层数组中的存放位置(即为:索引位置),判断
* 数组此位置上是否已经有元素:
* 如果此位置上没有其他元素,则元素a添加成功。 --->情况1
* 如果此位置上有其他元素b(或以链表形式存在的多个元素),则比较元素a与元素b的hash值:
* 如果hash值不相同,则元素a添加成功。--->情况2
* 如果hash值相同,进而需要调用元素a所在类的equals()方法:
* equals()返回true,元素a添加失败
* equals()返回false,则元素a添加成功。--->情况2
*
* 对于添加成功的情况2和情况3而言:元素a 与已经存在指定索引位置上数据以链表的方式存储。
* jdk 7 :元素a放到数组中,指向原来的元素。
* jdk 8 :原来的元素在数组中,指向元素a
* 总结:七上八下
原文链接:https://blog.csdn.net/PorkBird/article/details/113727330
关于hashCode()和equals()的重写:https://blog.csdn.net/PorkBird/article/details/113727330
2.LinkedHashSet
3.TreeSet
1.自然排序:不能向TreeSet里面添加不同类型的元素
为什么重写compareTo方法同时重写equals方法?与HashSet不同,TreeSet插入元素时的判断标准其实只需要实现Comparable接口的compareTo()方法就可以了,但是一般情况了,我们推荐同时重写实现equals()这个方法,原因在于我们写这个类的时候,无法确认在后面的情况下,会不会用到equals()方法,比如可能会要把这个类的实例加入到HashSet中?这是可能出现的,我们当然不希望出现compareTo()方法得到的结果为0,但是equals()方法得到的却是flase这样的奇怪情况出现,所以一般实现compareTo()时会同时重写equals()来保证两个方法的结果一致,不产生冲突。
class Student implements Comparable{
//1.属性
private int id;
private String name;
//2.构造器
public Student(int id,String name){
this.id=id;
this.name=name;
}
//3.get/set方法
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//重写equals方法
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (this.id!=student.id){
return false;
}else{
if (this.name!=null){
return this.name.equals(student.name);
}else {
return student.name==null;
}
}
}
@Override
public int hashCode() {
int result;
if (this.name!=null){
result=name.hashCode();
result=31*result+this.id;
return result;
}else {
result=0;
result=31*result+this.id;
return result;
}
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
//学号从小到大,姓名从小到大
//重写compareTo方法
@Override
public int compareTo(Object o) {
if(o instanceof Student){
Student s = (Student) o;
int compare=-this.name.compareTo(s.name);//字符串比较的是unicode值大小,小于0放左面
if (compare!=0){
return compare;
}else {
return Integer.compare(this.id,s.id);
}
}else {
throw new RuntimeException("输入的类型不匹配!");
}
}
}
@Test
public void Test2(){
Set set = new TreeSet();
set.add(new Student(3,"KKK"));
set.add(new Student(1,"zzz"));
set.add(new Student(2,"zzz"));
set.add(new Student(1,"zzz"));
set.add(new Student(1,"www"));
Iterator iterator = set.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next().toString());
}
}
2.定制排序
//TreeSet的定制排序:在什么条件下使用?若比较的对象没有实现compareable接口 或者 不希望按照默认情况进行排序,可以考虑定制排序,使用comparator接口,重写compare(a,b)方法,若a》b则返回正数
//TreeSet的定制排序:在什么条件下使用?若比较的对象没有实现compareable接口 或者 不希望按照默认情况进行排序
//可以考虑定制排序,使用comparator接口,重写compare(a,b)方法,若a》b则返回正数
@Test
public void Test(){
//按照年龄从小到大排序
Comparator com = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if(o1 instanceof Student && o2 instanceof Student){
Student s1 = (Student) o1;
Student s2 = (Student) o2;
return Integer.compare(s1.age,s2.age);
}else {
throw new RuntimeException("输入的数据类型不正确");
}
}
};
TreeSet treeSet = new TreeSet(com);
treeSet.add(new Student("QQQ",22));
treeSet.add(new Student("www",11));
treeSet.add(new Student("eee",33));
Iterator iterator = treeSet.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next().toString());
}
}
class Student {
//1.属性
private String name;
private int age;
//2.构造器
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//3.get、set方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}