一、Collection
1.1 List
ArrayList 和 LinkedList都是List类型,它们都是按照被插入的顺序保存元素,可以插入重复的元素;
- 需要进行大量的随机访问使用ArrayList;
- 需要经常从表中插入和删除元素,应该使用LinkedList;
1.2 Set
用于存储无序(存入和取出的顺序不一定相同)元素,值不能重复。
HashSet、TreeSet、LinkedHashSet都是Set类型,每个相同的项只保存一次(元素不重复)。
- HashSet使用的是散列函数,它 获取元素的方式是最快的,元素存放顺序是无序的。
- TreeSet 底层实现的数据结构是红黑二叉树,可以有序的存放元素,需要被排序的元素或类实现Comparable<T>接口。
- LinkedHashSet使用链表来维护元素的插入顺序,按照被添加的顺序保存对象。
1.3 Map
HashMap、TreeMap、LinkedHashMap、ConcurrentHashMap 都是Map类型,存放键值对。
- HashMap与HashSet一样,提供了最快的查找技术,保存元素是无序的。
- TreeMap底层实现的数据结构是红黑二叉树,默认按照比较结果的升序保存键,需要被排序的元素或类实现Comparable<T>接口。
- LinkedHashMap按照插入顺序保存键。
- HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
1.4 Queue和Stack
LinkedList具有直接实现栈的所有功能的方法,它的底层实现是一个双向链表,因此可以直接将LinkedList作为栈使用;LinkedList也提供了方法以支持队列的行为,因此LinkedList可以作为Queue的一种实现。所以各种Queue以及栈的行为,由LinkedList提供支持。
常用API举例:
Collection<String> c = new ArrayList<String>(){{add("a");add("b");add("c");add("d");}};
System.out.println("max value:" + Collections.max(c));// 最大值 d
System.out.println("min value:" + Collections.min(c));// 最小值 a
List<String> newList = ((ArrayList<String>) c).subList(1,3);// 集合截取
Object[] array = c.toArray(); // 集合转换为数组
List<Object> transferList = Arrays.asList(array);// 数组转换为List集合
二、集合排序
2.1 List集合的排序
java提供了两种排序方式,分别是Collections.sort(List)和Collections.sort(List,Commparator)。示例如下:
List<Integer> nameList = Arrays.asList(9,5,2,4,10,7,36);
Collections.sort(nameList);// 升序
for (Integer s : nameList) {
System.out.println(s);
}
Collections.sort(nameList,Collections.reverseOrder());// 降序
for (Integer s : nameList) {
System.out.println(s);
}
2.1.1 Collections.sort(List)
(1)如果List集合中的元素内容只有一个数据,就直接比较即可,前提是保证这个数据对应的类中有CompareTo方法;
(2)如果List集合中的元素内容有多个数据,就需要这个元素类型必须实现Comparable接口,并重写CompareTo方法,在此方法中指定排序原则。
2.1.2 Collections.sort(List,Commparator)
sort方法的参数有两个,一个是要排序的List集合,另一个参数是Comparator接口,在比较器中指定要排序的原则。
使用比较器方式就不用对要比较的集合的类类型实现Comparable接口;
可以实现多个比较器,每个比较器就是一种排序原则, 比较器的实现也有两种方式 ,如下:
/**
* 根据Comparator接口的子实现来指定排序的原则,策略模式
* 按照年龄排序
* @param stus
*/
public void sortAge(List<Student> stus){
Collections.sort(stus, new Comparator<Student>(){
@Override
public int compare(Student stu1, Student stu2) {
// TODO Auto-generated method stub
return stu1.getAge()-stu2.getAge();
}
});
for(Student stu:stus){
System.out.println("name="+stu.getName()+"age="+stu.getAge()+"stuNo="+stu.getStuNo());
}
}
/××
×JAVA LIST 排序方法
×
××/
private class ComparatorUser implements Comparator {
public int compare(Object arg0, Object arg1) {
Object pd1 = (Object) arg0;
Object pd2 = (Object) arg1;
int flag = String.valueOf(获取pd2中依据排序字段).compareTo(String.valueOf(获取pd1中依据排序字段));
return flag;
}
}
//调用方法
Collections.sort(sortList, new ComparatorUser());//调用
2.2 Set 集合排序
由于Set集合自身的特殊性,保存的元素都是不重复且无序的,所以Set 、HashSet 不能对存储的元素排序。
LinkedHashSet 和 TreeSet 可以实现元素的有序存储。
2.2.1 LinkedHashSet
会保存插入的顺序。
2.2.2 TreeSet
(1) 让元素自身具备比较性
也就是元素需要实现Comparable接口,覆盖compareTo 方法。这种方式也作为元素的自然排序,也可称为默认排序。
年龄按照搜要条件,年龄相同再比姓名。
class Person implements Comparable {
private String name;
private int age;
private String gender;
public Person() {
}
...
public int compareTo(Object obj) {
Person p = (Person) obj;
System.out.println(this+" compareTo:"+p);
if (this.age > p.age) {
return 1;
}
if (this.age < p.age) {
return -1;
}
return this.name.compareTo(p.name);
}
}
(2)让容器自身具备比较性,自定义比较器
需求:当元素自身不具备比较性,或者元素自身具备的比较性不是所需的。那么这时只能让容器自身具备。
定义一个类实现Comparator 接口,覆盖compare方法。并将该接口的子类对象作为参数传递给TreeSet集合的构造函数。
当Comparable比较方式,及Comparator比较方式同时存在,以Comparator比较方式为主。
public class Demo5 {
public static void main(String[] args) {
TreeSet ts = new TreeSet(new MyComparator());
ts.add(new Book("think in java", 100));
ts.add(new Book("java 核心技术", 75));
ts.add(new Book("现代操作系统", 50));
ts.add(new Book("java就业教程", 35));
ts.add(new Book("think in java", 100));
ts.add(new Book("ccc in java", 100));
System.out.println(ts);
}
}
class MyComparator implements Comparator {
public int compare(Object o1, Object o2) {
Book b1 = (Book) o1;
Book b2 = (Book) o2;
System.out.println(b1+" comparator "+b2);
if (b1.getPrice() > b2.getPrice()) {
return 1;
}
if (b1.getPrice() < b2.getPrice()) {
return -1;
}
return b1.getName().compareTo(b2.getName());
}
}
class Book {
private String name;
private double price;
public Book() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public Book(String name, double price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "Book [name=" + name + ", price=" + price + "]";
}
}
2.3 Map 排序
Map排序的方式有很多种,这里记录下自己总结的两种比较常用的方式:按键排序(sort by key), 按值排序(sort by value)。
2.3.1 TreeMap 排序
(1)按键排序
jdk内置的java.util包下的TreeMap<K,V>既可满足此类需求,向其构造方法 TreeMap(Comparator<? super K> comparator) 传入我们自定义的比较器即可实现按键排序。
public class MapSortDemo {
public static void main(String[] args) {
Map<String, String> map = new TreeMap<String, String>();
map.put("KFC", "kfc");
map.put("WNBA", "wnba");
map.put("NBA", "nba");
map.put("CBA", "cba");
Map<String, String> resultMap = sortMapByKey(map); //按Key进行排序
for (Map.Entry<String, String> entry : resultMap.entrySet()) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
/**
* 使用 Map按key进行排序
* @param map
* @return
*/
public static Map<String, String> sortMapByKey(Map<String, String> map) {
if (map == null || map.isEmpty()) {
return null;
}
Map<String, String> sortMap = new TreeMap<String, String>(new MapKeyComparator());
sortMap.putAll(map);
return sortMap;
}
}
// 比较器类
class MapKeyComparator implements Comparator<String>{
@Override
public int compare(String str1, String str2) {
return str1.compareTo(str2);
}
}
(2)按值排序
Map本身按值排序是很有意义的,很多场合下都会遇到类似需求,可以认为其值是定义的某种规则或者权重。原理:将待排序Map中的所有元素置于一个列表中,接着使用Collections的一个静态方法 sort(List<T> list, Comparator<? super T> c)
来排序列表,同样是用比较器定义比较规则。排序后的列表中的元素再依次装入Map,为了肯定的保证Map中元素与排序后的List中的元素的顺序一致,使用了LinkedHashMap数据类型。
public class MapSortDemo {
public static void main(String[] args) {
Map<String, String> map = new TreeMap<String, String>();
map.put("KFC", "kfc");
map.put("WNBA", "wnba");
map.put("NBA", "nba");
map.put("CBA", "cba");
// Map<String, String> resultMap = sortMapByKey(map); //按Key进行排序
Map<String, String> resultMap = sortMapByValue(map); //按Value进行排序
for (Map.Entry<String, String> entry : resultMap.entrySet()) {
System.out.println(entry.getKey() + " " + entry.getValue());
}
}
/**
* 使用 Map按value进行排序
* @param map
* @return
*/
public static Map<String, String> sortMapByValue(Map<String, String> oriMap) {
if (oriMap == null || oriMap.isEmpty()) {
return null;
}
Map<String, String> sortedMap = new LinkedHashMap<String, String>();
List<Map.Entry<String, String>> entryList = new ArrayList<Map.Entry<String, String>>(
oriMap.entrySet());
Collections.sort(entryList, new MapValueComparator());
Iterator<Map.Entry<String, String>> iter = entryList.iterator();
Map.Entry<String, String> tmpEntry = null;
while (iter.hasNext()) {
tmpEntry = iter.next();
sortedMap.put(tmpEntry.getKey(), tmpEntry.getValue());
}
return sortedMap;
}
}
// 比较器类
class MapValueComparator implements Comparator<Map.Entry<String, String>> {
@Override
public int compare(Entry<String, String> me1, Entry<String, String> me2) {
return me1.getValue().compareTo(me2.getValue());
}
}
(3)总结
看到array,就要想到角标。
看到link,就要想到first,last。
看到hash,就要想到hashCode,equals.
看到tree,就要想到两个接口。Comparable,Comparator。
三、集合遍历
3.1遍历Map
在Java中,使用迭代器遍历Map
并移除不满足条件的元素时,需通过迭代器的remove()
方法操作,避免直接修改Map
导致ConcurrentModificationException
异常。以下是具体实现方法:
步骤说明
-
获取
Map
的条目(Entry)迭代器
通过map.entrySet().iterator()
获取键值对的迭代器,直接操作条目。 -
遍历并检查条件
使用while
循环逐个检查条目,判断是否需要移除。 -
调用迭代器的
remove()
方法
当条件满足时,通过迭代器删除当前条目,而非直接操作Map
。
代码示例
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class MapIteratorRemove {
public static void main(String[] args) {
// 初始化Map
Map<String, Integer> map = new HashMap<>();
map.put("A", 1);
map.put("B", 2);
map.put("C", 3);
map.put("D", 4);
// 获取Entry迭代器
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
// 遍历并移除值为奇数的条目
while (iterator.hasNext()) {
Map.Entry<String, Integer> entry = iterator.next();
if (entry.getValue() % 2 != 0) {
iterator.remove(); // 关键:通过迭代器删除
}
}
// 输出结果:{B=2, D=4}
System.out.println(map);
}
}
关键点解析
-
迭代器来源
必须通过map.entrySet().iterator()
获取条目迭代器,而非keySet()
或values()
的迭代器,否则无法直接删除整个键值对。 -
remove()
的作用域
iterator.remove()
会删除当前迭代到的条目(即最后一次调用next()
返回的条目)。 -
避免直接操作Map
若在遍历过程中使用map.remove(key)
,会触发并发修改异常,必须通过迭代器删除。
Java 8+简化方案(推荐)
使用removeIf
方法结合Lambda表达式,代码更简洁:
map.entrySet().removeIf(entry -> entry.getValue() % 2 != 0);
常见错误示例
// 错误!直接调用Map.remove()会导致ConcurrentModificationException
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() % 2 != 0) {
map.remove(entry.getKey()); // 抛出异常
}
}
总结
方法 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
传统迭代器 | Java 7及以下 | 显式控制删除逻辑 | 代码稍冗长 |
removeIf +Lambda | Java 8及以上 | 代码简洁,一行完成 | 需熟悉函数式编程 |
根据项目环境选择合适方法,核心原则是通过迭代器或removeIf
安全删除元素。
四、数组转List的三种方式及对比
(1)最常见方式(未必最佳)
通过 Arrays.asList(strArray)
方式,将数组转换List后,不能对List增删,只能查改,否则抛异常。关键代码:List list = Arrays.asList(strArray);
private void testArrayCastToListError() {
String[] strArray = new String[2];
List list = Arrays.asList(strArray);
//对转换后的list插入一条数据
list.add("1");
System.out.println(list);
}
执行结果
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at com.darwin.junit.Calculator.testArrayCastToList(Calculator.java:19)
at com.darwin.junit.Calculator.main(Calculator.java:44)
程序在list.add(“1”)处,抛出异常:UnsupportedOperationException。
原因解析:
Arrays.asList(strArray)
返回值是java.util.Arrays
类中一个私有静态内部类java.util.Arrays.ArrayList
,它并非java.util.ArrayList
类。java.util.Arrays.ArrayList
类具有 set(),get(),contains()等方法,但是不具有添加add()
或删除remove()
方法,所以调用add()
方法会报错。
使用场景:Arrays.asList(strArray)
方式仅能用在将数组转换为List后,不需要增删其中的值,仅作为数据源读取使用。
(2)数组转为List后,支持增删改查的方式
通过ArrayList的构造器,将Arrays.asList(strArray)
的返回值由java.util.Arrays.ArrayList
转为java.util.ArrayList
。
关键代码:ArrayList<String> list = new ArrayList<String>(Arrays.asList(strArray)) ;
private void testArrayCastToListRight() {
String[] strArray = new String[2];
ArrayList<String> list = new ArrayList<String>(Arrays.asList(strArray)) ;
list.add("1");
System.out.println(list);
}
使用场景:需要在将数组转换为List后,对List进行增删改查操作,在List的数据量不大的情况下,可以使用。
(3)通过集合工具类Collections.addAll()方法(最高效)
通过Collections.addAll(arrayList, strArray)
方式转换,根据数组的长度创建一个长度相同的List,然后通过Collections.addAll()
方法,将数组中的元素转为二进制,然后添加到List中,这是最高效的方法。
private void testArrayCastToListEfficient(){
String[] strArray = new String[2];
ArrayList< String> arrayList = new ArrayList<String>(strArray.length);
Collections.addAll(arrayList, strArray);
arrayList.add("1");
System.out.println(arrayList);
}
使用场景:需要在将数组转换为List后,对List进行增删改查操作,在List的数据量巨大的情况下,优先使用,可以提高操作速度。
(4)问题:数组类型如果是整型数组,转为List时,会报错?那么在声明数组时,用int[]
还是Integer[]
,哪种声明方式才能正确的转为List
呢?
答案: 只能用Integer[]
转List<Integer>
,即只能用基本数据类型的包装类型,才能直接转为List
。
-
述源码中可以看出,
List
声明时,需要传递一个泛型<E>
作为形参,asList()
参数类型也是泛型中的通配类型<T>
。Java中所有的泛型必须是引用类型。 -
什么是引用类型?
Integer
是引用类型,那int
是什么类型?int
是基本数据类型,不是引用类型。这就是为什么java中没有List<int>
,而只有List<Integer>
。 -
举一反三:其他8种基本数据类型
byte、short、int、long、float、double、char
也都不是引用类型,所以8种基本数据类型都不能作为List的形参。但String、数组、class、interface
是引用类型,都可以作为List的形参,所以存在List<Runnable>
接口类型的集合、List<int[]>
数组类型的集合、List<String>
类的集合。但不存在list<byte>
、list<short>
等基本类型的集合。 -
为什么
int[]
不能直接转换为List<Integer>
,而Integer[]
就可以转换为List<Integer>
了吧。因为List
中的泛型必须是引用类型,int
是基本数据类型,不是引用类型,但int
的包装类型Integer
是class
类型,属于引用类型,所以Integer
可以作为List
形参,List<Integer>
在java中是可以存在的,但不存在List<int>
类型。在编码时,我们不光要知其然,还要知其所以然,通过分析JDK源码,才能得出一手信息,不仅了解到了如何用,还能得出为何这样用。
五、常用排序算法实现
5.1 插入排序(InsertionSort)
一般也被称为直接插入排序。
(1)对于少量元素的排序,它是一个有效的算法。插入排序是一种最简单的排序方法,它的基本思想是将一个记录插入到已经排好序的有序表中,从而一个新的、记录数增 1 的有序表
。在其实现过程使用双层循环,外层循环对除了第一个元素之外的所有元素,内层循环对当前元素前面有序表进行待插入位置查找,并进行移动。
(2)插入排序的平均时间复杂度也是 O(n^2),空间复杂度为常数阶 O(1),具体时间复杂度和数组的有序性也是有关联的。
插入排序中,当待排序数组是有序时,是最优的情况,只需当前数跟前一个数比较一下就可以了,这时一共需要比较 N-1 次,时间复杂度为 O(N)。最坏的情况是待排序数组是逆序的,此时需要比较次数最多,最坏的情况是 O(n^2)。
(3)代码实现
/**
* 插入排序
*
* @author yahui.xu
* @date 2021/9/7 17:34
*/
public class InsertionSort {
public static void main(String[] args) {
Integer[] data = new Integer[]{5,10,8,6,9,1,7};
sort(data);
Arrays.stream(data).forEach(one -> System.out.println(one));
}
public static void sort(Comparable[] arr){
int num = arr.length;
for(int i=0;i<num;i++){
for(int j = i;j>0;j--){
if(arr[j].compareTo(arr[j-1])<0)
swapItem(arr,j,j-1);
else
break;
}
}
}
public static void swapItem(Object[] arr,int i,int j){
Object temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
六、集合数据合并(合计)
下面代码实现的是:将一个list的根据能源类型code分组之后,将其key的每个对象的能耗值累加到第一个元素的能耗值上,并且返回。其实就是一个数据合计的算法。
/**
* @author: xuyahui
* @Date: 2021/10/16 16:38
* @Description: 合计对象属性值
*/
public static List<ConsumeVo> merge(List<ConsumeVo> consumeVoList){
return consumeVoList.stream().
collect(Collectors.toMap(ConsumeVo::getEnergyCode,k->k,(v1,v2)->{
v1.setConsume(v1.getConsume().add(v2.getConsume()));
return v1;
})).values().stream().collect(Collectors.toList());
}
七、算法
7.1 找最匹配的方案数据
直接算法实现,for循环,条件判定,先比较相等,再比较最接近。
/**
* 匹配流量最接近的方案
* @param listBasePlans
* @param totalFlow
* @return
*/
private BasePlanEO matchFlowBasePlan(List<BasePlanEO> listBasePlans, BigDecimal totalFlow){
BigDecimal min = new BigDecimal(Long.MAX_VALUE);
BasePlanEO resEo = null;
for(BasePlanEO eo : listBasePlans){
// 匹配与totalFlow值的相等的方案数据
if(eo.calculateTotalFlow().compareTo(totalFlow) == 0){
return eo;
}
// 匹配最接近totalFlow值的方案数据
if(eo.calculateTotalFlow().subtract(totalFlow).abs().compareTo(min) < 0){
min = eo.calculateTotalFlow().subtract(totalFlow).abs();
resEo = eo;
}
}
return resEo;
}
七、JAVA中如何正确地退出多层嵌套循环
使用一个标识符(如布尔变量)来检查是否应该退出循环。
当满足某个条件时,你可以设置这个标识符,并在每个循环的开始处检查它。
public class NestedLoopExitWithFlag {
public static void main(String[] args) {
boolean exitLoop = false; // 退出循环的标识符
for (int i = 0; i < 3 && !exitLoop; i++) {
for (int j = 0; j < 3 && !exitLoop; j++) {
if (i == 1 && j == 1) {
exitLoop = true; // 设置标识符为 true,以退出循环
}
System.out.println("i: " + i + ", j: " + j);
}
// 检查是否需要退出循环(即使内层循环已经退出)
if (exitLoop) {
**break;** // 如果需要,退出外层循环
}
}
}
}
这种方法的好处是它 更通用
,可以在任何支持条件语句的编程语言中使用,并且它通常更容易阅读和维护。
八、页面的列表数据某一行上移、下移的算法
在Java中实现页面列表数据中某行上移或下移的功能,可以通过交换列表中元素的索引位置来实现。这里我将提供一个简单的示例,演示如何使用Java代码来完成这个功能。
假设我们有一个列表List<String>
,我们想要实现某个特定元素上移或下移的功能。
import java.util.ArrayList;
import java.util.List;
public class ListMover {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Item 1");
list.add("Item 2");
list.add("Item 3");
list.add("Item 4");
list.add("Item 5");
// 打印原始列表
System.out.println("Original List: " + list);
// 假设我们要移动"Item 3"上移和下移
moveUp(list, "Item 3");
System.out.println("After moving 'Item 3' up: " + list);
moveDown(list, "Item 3");
System.out.println("After moving 'Item 3' down: " + list);
}
public static void moveUp(List<String> list, String item) {
int index = list.indexOf(item);
if (index > 0) { // 确保不是第一个元素
list.set(index, list.set(index - 1, item)); // 交换位置
}
}
public static void moveDown(List<String> list, String item) {
int index = list.indexOf(item);
if (index < list.size() - 1) { // 确保不是最后一个元素
list.set(index, list.set(index + 1, item)); // 交换位置
}
}
}
解释:
(1)moveUp 方法:这个方法首先找到要上移的元素索引,然后检查该元素是否不是列表中的第一个元素。如果不是第一个元素,它会将该元素与前一个元素的位置互换。这里使用了list.set(index, list.set(index - 1, item))
,这种方法实际上是先获取当前位置的元素(为了后续赋值使用),然后将当前位置的元素设置为前一个元素的值,最后再将获取到的当前元素值赋给前一个位置。这样实现了上移。
(2)moveDown 方法:这个方法的工作方式类似于moveUp
,但它检查元素是否不是列表中的最后一个元素。如果是,它将该元素与下一个元素的位置互换。同样使用了list.set(index, list.set(index + 1, item))
来交换位置。
注意事项:
-
使用
list.indexOf(item)
找到元素索引,确保了操作的准确性。如果元素不存在于列表中,indexOf
将返回-1,但在实际操作中通常会有检查以确保操作的合法性(例如通过用户界面选择正确的项目)。 -
在实际应用中,你可能需要处理用户界面的事件来调用这些方法,比如在Web前端或桌面应用中。确保在调用这些方法之前对列表的有效性进行检查(例如,确保用户选择了正确的项目)。
这种方法适用于任何类型的对象列表,不仅仅是字符串。你只需要将类型参数从String
改为你的对象类型即可。例如,对于List<MyObject>
,只需将字符串替换为相应的对象即可。