单列集合List
重点:
List集合
概述:List接口就是继承了Collection接口的子接口,拓展了一些新的方法。
特点:List体系中的实现类都具有可重复性,有序,有索引。
List集合操作索引的特有方法
List<String> nameList = new List<>();
nameList.add("小明");
nameList.add("小军");
nameList.add("小李");
(1)public void add(int index, E e):添加元素到指定的索引(原索引后移,注意越界问题索引)。
nameList.add(3, "小花");
System.out.println("nameList : " + nameList);
(2)public E remove(int index) : 删除索引的元素 将被删除的元素返回。
String removeElement = nameList.remove(0);
System.out.println("nameList : " + nameList);
System.out.println("removeElement : " + removeElement);
(3)public E set(int index,E e):将指定索引的元素替换为参数e并且将被替换的元素返回。
String replaceElement = nameList.set(2, "小王");
System.out.println("nameList : " + nameList);
System.out.println("replaceElement : " + replaceElement);
(4)public E get(int index):获取指定索引的元素。
String element = nameList.get(0);
System.out.println("0索引的元素是 : " + element);
List集合的四种遍历方式
迭代器遍历:
Iterator<String> it = nameList.iterator();
while (it.hasNext()) {
String element = it.next();
System.out.println("迭代器 : " + element);
}
增强for循环:
for (String element : nameList) {
System.out.println("增强For : " + element);
}
Lambda表达式(函数式):
nameList.forEach(s -> System.out.println("Lambda : " + s));
普通for循环:
for (int i = 0; i < nameList.size(); i++) {
String element = nameList.get(i);
System.out.println("普通For : " + element);
}
ArrayList集合
ArrayList底层数据结构
ArrayList底层是基于数组结构来保存数据的。ArrayList适合数据量小有频繁查询的场景,不适合数据量大又频繁增删的场景。
数组结构在创建的时候长度就已经确定了(开辟一块连续的空间),数组的内存空间是连续的。
特点:
- 查询快:基于索引查询,查询任意索引的时间相同。
- 增删慢:因为要保证连续的特点,删除元素需要后面的元素前移,添加元素需要原始的元素后移,可能还需要扩容。
ArrayList底层基于一个elementData的数组来保存实际的元素。
(1)当基于无参构造创建ArrayList的时候,底层的elementData默认是一个长度为0的数组。
(2)第一次添加元素的时候,会将elementData初始化为长度为10的数组。
(3)当数组内容满了需要扩容,则每次扩容1.5倍。
(4)如果可以明确之后存储元素的大概个数,推荐使用哪个ArrayList的有参构造!
LinkedList集合
LinkedList底层数据结构
LinkedList底层的接口是双向链表。
单向链表:
特点:
增删快:因为链表添加节点不会影响整体。
查询慢:由于内存不连续,导致查询任何一个内容,都要从头结点开始查询。
双向链表:
特点:
增删快:因为链表添加节点不会影响整体。
查询慢:由于内存不连续,导致查询任何一个内容,可以基于离头近还是离尾近找到对应节点开始查询。
LinkedList特有操作首尾的方法
代码示例:
//public void addFirst(E e) : 添加元素作为链表头结点 => add(0,元素);
nameLinkedList.addFirst("小张");
//public void addLast(E e) : 添加元素作为链表尾结点 => add
nameLinkedList.addLast("小王");
System.out.println("nameLinkedList : " + nameLinkedList);
//public E removeFirst() : 删除头结点(返回被删除的元素) 在模拟一些逻辑结构的时候有移除并返回的需求 remove(0)
nameLinkedList.removeFirst();
//public E removeLast() : 删除尾结点(返回被删除的元素) remove(长度-1)
nameLinkedList.removeLast();
System.out.println("nameLinkedList : " + nameLinkedList);
//public E getFirst() : 获取头节点元素 get(0)
System.out.println("First Node : " + nameLinkedList.getFirst());
//public E getLast() : 获取尾节点元素 get(长度-1)
System.out.println("Last Node : " + nameLinkedList.getLast());
栈结构与队列结构
队列结构
队列也是一种数据结构可以用来存储数据。特点:先进先出FIFO。
图解:
代码示例:
//泛型 T为占位符,对象调用时明确数据类型
public class MyQueue<T> {
//基于LinkedList保存实际存储在队列中的数据
private LinkedList<T> dataList = new LinkedList<>();
//存储数据
public void save(T t) {
dataList.addLast(t);
}
//获取数据
public T get() {
return dataList.removeFirst();
}
}
栈结构
栈也是一种数据结构可以用来存储数据。特点:先进后出FILO。
图解:
代码示例:
public class MyStack<T> {
//基于LinkedList保存实际存储在栈中的数据
private LinkedList<T> dataList = new LinkedList<>();
//存储数据到栈中的方法(压栈push)
public void push(T e) {
dataList.addLast(e);
}
//获取栈中数据的方法(弹栈pop)
public T pop() {
return dataList.removeLast();
}
}
Set集合
Set接口以及实现类通用特点:
不保存重复元素,没有索引。
Set集合体系:
HashSet集合
HashSet是使用频率最高的Set集合的实现类。
特点:
(1)无序:元素存入的顺序与取出的顺序不保证一致。
(2)保证元素唯一:当add方法添加元素存在时不保存。
(3)没有索引
HashSet保证元素唯一的方式
HashSet底层的数据结构是哈希表,是一种复合型的数据结构,由数组+链表组成的哈希表。
特点:添加快,删除快,查询快,保证元素唯一!
哈希值:
哈希值是一串int数字,任何对象都可以基于哈希函数计算出自己的哈希值,重复率极低。
哈希值的功能唯一,就是为了服务哈希表的进行去重的。
HashSet使用时注意事项
容易出的错误:
自定义的类对象在HashSet集合中使用时不重写hashCode方法,导致相同的数据可以存进去数值。
(1)未重写hashCode方法
当自定义的类没有重写hashCode方法,则对象调用hashCode方法是基于地址值生成的。
//(1)创建3个Person对象(有参构造)
Person p1 = new Person("小刘", 23);
Person p2 = new Person("小刘", 23);
Person p3 = new Person("小李", 24);
//(2)直接基于Person对象调用hashCode方法即可获取该对象的哈希值(未重写:基于地址)
System.out.println("p1的哈希值是:" + p1.hashCode()); //990368553
System.out.println("p2的哈希值是:" + p2.hashCode()); //1828972342
System.out.println("p3的哈希值是:" + p3.hashCode()); //1452126962
哈希表无法保证内容相同的元素唯一!和
(2)重写hashCode方法
当自定义的类重写了hashCode方法,对象调用是根据内容生成的哈希值。
//(1)创建3个Person对象(有参构造)
Person p1 = new Person("小刘", 23);
Person p2 = new Person("小刘", 23);
Person p3 = new Person("小李", 24);
//(2)直接基于Person对象调用hashCode方法即可获取该对象的哈希值(重写:基于内容)
System.out.println("p1的哈希值是:" + p1.hashCode()); //745705612
System.out.println("p2的哈希值是:" + p2.hashCode()); //745705612
System.out.println("p3的哈希值是:" + p3.hashCode()); //663508400
总结:
(1)如果想要HashSet保存自定义的元素并且按照内容进行出重,保证唯一,自定义的类必须重写hashCode方法和equals方法。
(2)HastSet并不是真的无序,只是按照元素的哈希值在哈希表中散列之后的顺序保存的。
LinkedHashSet集合
特点:
(1)有序:可以保证元素的存入顺序与取出的顺序一致。
(2)保证元素的唯一。
(3)没有索引。
LinkedHashSet保证元素唯一的方式
想要维护元素的存取有序势必要使用更多的内存空间,取舍的问题根据实际情况选择。
TreeSet集合
TreeSet的底层基于红黑树。
特点:
(1)元素会按照指定的规则进行排序。
(2)保证元素的唯一【基于排序的规则】。
(3)没有索引。
TreeSet的构造方法
public TreeSet(Compartor c):基于指定排序规则创建TreeSet集合。
代码示例:
//(1)创建TreeSet集合并且指定排序规则(TreeSet保存Person,Person的排序规则)
TreeSet<Person> personTreeSet = new TreeSet<>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o2.getAge() - o1.getAge();
}
});
//注意:当添加元素搭配TreeSet自动调用排序规则方法为元素进行排序
personTreeSet.add(p1);
personTreeSet.add(p3);
personTreeSet.add(p4);
personTreeSet.add(p5);
TreeSet集合保证元素唯一的方式
TreeSet底层是红黑树,所以保证元素唯一的方式和元素的哈希值没有一点关系。基于排序规则答返回值保证唯一,如果添加的元素和已存在的元素经过对比返回值为0,就不进行添加。
TreeSet集合的极端问题解决方法
当给出的排序规则过于简单,有可能会造成某些认为不相同的元素也会被排除掉。
例如:
Person p1 = new Person("小刘",21);
Person p2 = new Person("小李",21);
//排序规则
TreeSet<Person> personTreeSet = new TreeSet<>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
//主要条件
return result = o2.getAge() - o1.getAge();
}
});
只简单的根据年龄进行判断,年龄虽然相同但是不是一个对象,明显不是一个相同的,TreeSet会认为是相同的。
改进:
TreeSet<Person> personTreeSet = new TreeSet<>(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
//主要条件
int result = o2.getAge() - o1.getAge();
//如果主要条件是0,只能说明o1和o2的age是相同的,但是并不能说明其他项也是相同。
if (result == 0)
result = o1.equals(o2) ? 0 : 1;
return result;
}
});
可变参数
可变参数就是参数的个数可以发生改变。
可变参数的格式
可变参数一般作为方法的参数列表。
权限修饰符 返回值类型 方法名(数据类型... 变量名){}
public static void show(int...a){}
可变参数的调用
public static void show(int... a) {
//可变参数的本质就是一个数组(和之前的区别在于,由Java默认将多个参数收集到数据中)
for (int i : a) {
System.out.println(i);
}
}
//(1)调用可变参数的方法-不传递数据 (√)
show(); //数组长度为0
//(2)调用可变参数的方法-可以传递任意个数的数据,要是用,分隔 (√)
show(10, 20, 30, 40, 50, 60, 70, 80, 90); //数组中收集多少个数据
//(3)调用可变参数的方法-可以传递null值(没什么意义)
show(null); //int[] a = null; 可能会空指针
//(4)调用可变参数的方法-可以传递一个对应的数组(没什么意义)
int[] arr = new int[]{2, 3, 4, 5};
show(arr); //int[] a = arr; 地址值
可变参数的本质
java中会自动将多个参数收集到一起封装到一个数组中传递给可变参数,可变参数的本质上就是一个数组。
在方法中可以使用数组的方法。
可变参数的注意事项
(1)一个方法的可变参数只能有一个。
(2)一个方法如果有普通参数与可变参数,那么可变参数必须放到形参列表的最后面。
单列集合的工具类Collections
Collections常用方法
Collections的两种排序规则
自定义排序
调用Collections的排序方法,传入需要排序的单列集合和自定义排序规则。
代码示例
Collections.sort(dogList, new Comparator<Dog>() {
@Override
public int compare(Dog o1, Dog o2) {
return o2.getAge() - o1.getAge();
}
});
类自身包含排序规则
需要让排序的类实现Comparable接口,实现compareTo方法,声明好排序规则。
private static class Dog implements Comparable<Dog> {
//this和o进行对比 this:o1 o:o2
@Override
public int compareTo(Dog o) {
return this.age - o.age;
}
}
在排序的时候就可以只传递集合。
Collections.sort(dogList);
自定义给出的排序规则优先级高于自身包含的排序规则。