• [其他] java中如何判断线程池中所有任务已结束
    在Java中,可以使用ExecutorService的awaitTermination方法来判断线程池中所有任务是否已结束。这个方法会阻塞当前线程,直到所有任务完成执行或者超时。示例代码:import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class ThreadPoolExample { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { executorService.submit(() -> { try { System.out.println("任务开始执行:" + Thread.currentThread().getName()); TimeUnit.SECONDS.sleep(2); System.out.println("任务执行完毕:" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } }); } executorService.shutdown(); try { if (!executorService.awaitTermination(1, TimeUnit.HOURS)) { System.out.println("线程池未在指定时间内关闭"); } else { System.out.println("线程池已关闭"); } } catch (InterruptedException e) { e.printStackTrace(); } } }在这个示例中,我们创建了一个固定大小的线程池,提交了10个任务。然后调用executorService.shutdown()来关闭线程池。接下来,我们使用executorService.awaitTermination(1, TimeUnit.HOURS)来等待线程池中的所有任务完成执行或者超时。如果线程池在指定的时间内关闭,那么awaitTermination方法返回true,否则返回false。
  • [其他] java中ThreadPoolExecutor线程池的生命周期
    Java中的ThreadPoolExecutor线程池的生命周期主要包括以下几个阶段:创建线程池:通过ThreadPoolExecutor类的构造方法创建一个线程池实例。在创建过程中,可以设置线程池的核心线程数、最大线程数、空闲线程存活时间等参数。import java.util.concurrent.*; public class ThreadPoolDemo { public static void main(String[] args) { int corePoolSize = 5; // 核心线程数 int maximumPoolSize = 10; // 最大线程数 long keepAliveTime = 60L; // 空闲线程存活时间(秒) TimeUnit unit = TimeUnit.SECONDS; // 时间单位 BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(25); // 任务队列 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue); } }提交任务:通过线程池的submit()方法提交一个任务到线程池中执行。submit()方法会返回一个Future对象,可以通过该对象获取任务的执行结果或者取消任务。import java.util.concurrent.*; public class ThreadPoolDemo { public static void main(String[] args) { // ...创建线程池的代码... Runnable task = () -> { System.out.println("任务执行中:" + Thread.currentThread().getName()); }; Future<?> future = threadPoolExecutor.submit(task); } }关闭线程池:当所有任务都执行完毕后,需要调用线程池的shutdown()方法来关闭线程池。此时,线程池不再接受新的任务,但会继续执行已提交的任务。如果需要在关闭线程池之前等待所有任务完成,可以使用awaitTermination()方法。import java.util.concurrent.*; public class ThreadPoolDemo { public static void main(String[] args) { // ...创建线程池和提交任务的代码... threadPoolExecutor.shutdown(); // 关闭线程池 try { if (!threadPoolExecutor.awaitTermination(60, TimeUnit.SECONDS)) { threadPoolExecutor.shutdownNow(); // 超时后强制关闭线程池 } } catch (InterruptedException e) { threadPoolExecutor.shutdownNow(); // 发生异常时强制关闭线程池 } } }处理任务结果:如果任务有返回值,可以通过Future对象的get()方法获取任务的执行结果。如果任务执行过程中发生异常,可以通过get()方法的重载版本获取异常信息。import java.util.concurrent.*; public class ThreadPoolDemo { public static void main(String[] args) { // ...创建线程池和提交任务的代码... Future<String> future = threadPoolExecutor.submit(() -> { System.out.println("任务执行中:" + Thread.currentThread().getName()); return "任务执行结果"; }); try { String result = future.get(); // 获取任务执行结果 System.out.println("任务执行结果:" + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } }
  • [其他] 线程结束后锁会自动释放吗
     在多线程编程中,锁是一种用于保护共享资源的重要机制。当多个线程需要访问同一个共享资源时,可以使用锁来确保在任何时刻只有一个线程能够访问该资源,从而避免数据竞争和不一致的问题。然而,当一个线程结束执行并释放其占用的锁时,其他等待的线程是否能够立即获得锁并继续执行呢?本文将介绍在线程结束后锁是否会自动释放的情况。  首先,我们需要了解锁的释放方式。在大多数编程语言中,锁的释放是由程序员显式完成的。当一个线程完成了对共享资源的访问后,它需要调用相应的方法来释放锁,以便其他线程可以获取锁并继续执行。这种方式被称为“显式释放”。  然而,在某些情况下,程序员可能会忘记显式释放锁,或者由于程序逻辑错误导致锁无法被释放。在这种情况下,锁将一直被占用,其他线程将无法获得锁并继续执行。这种情况可能会导致死锁或程序无响应等问题。  为了避免这种情况的发生,一些编程语言提供了自动释放锁的机制。当一个线程结束时,它会自动释放其占用的锁,以便其他线程可以立即获得锁并继续执行。这种机制被称为“自动释放”。  以Java语言为例,当一个线程结束时,它会自动释放其占用的所有锁。这是因为Java虚拟机(JVM)在垃圾回收器回收线程对象时,会检查该线程是否持有任何锁。如果该线程持有锁,垃圾回收器将等待直到锁被释放,然后再回收线程对象。这样可以避免死锁和其他与锁相关的问题。  需要注意的是,自动释放锁的机制并不是所有编程语言都支持的。在一些编程语言中,程序员仍然需要显式地释放锁,否则可能导致程序出现问题。因此,在使用多线程编程时,程序员应该仔细考虑如何正确地管理锁,以避免出现潜在的问题。 
  • [其他] java的深拷贝和浅拷贝
    Java中的浅拷贝和深拷贝是两种不同的对象复制方式。它们的主要区别在于对对象内部成员的处理方式。下面我们来详细介绍一下这两种拷贝方式。浅拷贝浅拷贝是指对基本数据类型进行值传递,对引用数据类型进行引用传递般的拷贝,新旧对象还是共享同一个内存空间。简单来说,浅拷贝只是复制了对象的引用,而不是对象本身。因此,原始对象和新对象的成员变量指向的是同一个内存地址。在Java中,可以使用Object类的clone()方法实现浅拷贝。需要注意的是,clone()方法的默认实现是浅拷贝。以下是一个简单的示例:class Person implements Cloneable { String name; int age; Person(String name, int age) { this.name = name; this.age = age; } @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } public class ShallowCopyDemo { public static void main(String[] args) { try { Person p1 = new Person("张三", 20); Person p2 = (Person) p1.clone(); System.out.println("p1: " + p1.name + ", " + p1.age); System.out.println("p2: " + p2.name + ", " + p2.age); p1.name = "李四"; p1.age = 30; System.out.println("修改后的p1: " + p1.name + ", " + p1.age); System.out.println("修改后的p2: " + p2.name + ", " + p2.age); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } }输出结果:p1: 张三, 20 p2: 张三, 20 修改后的p1: 李四, 30 修改后的p2: 张三, 20从输出结果可以看出,修改p1的name和age属性后,p2的对应属性并没有发生改变,说明它们是浅拷贝。深拷贝深拷贝是对基本数据类型进行值传递,对引用数据类型,创建一个新的对象,并复制其内容。简单来说,深拷贝会创建一个新的对象,并将原对象的内容复制到新对象中。因此,原始对象和新对象的成员变量指向的是两个不同的内存地址。在Java中,可以通过实现Cloneable接口并重写clone()方法来实现深拷贝。需要注意的是,需要对引用数据类型进行递归拷贝。以下是一个简单的示例:class Person implements Cloneable { String name; int age; Person(String name, int age) { this.name = name; this.age = age; } @Override protected Object clone() throws CloneNotSupportedException { Person person = (Person) super.clone(); person.name = new String(this.name); // 对引用数据类型进行递归拷贝 return person; } } public class DeepCopyDemo { public static void main(String[] args) { try { Person p1 = new Person("张三", 20); Person p2 = (Person) p1.clone(); System.out.println("p1: " + p1.name + ", " + p1.age); System.out.println("p2: " + p2.name + ", " + p2.age); p1.name = "李四"; p1.age = 30; System.out.println("修改后的p1: " + p1.name + ", " + p1.age); System.out.println("修改后的p2: " + p2.name + ", " + p2.age); } catch (CloneNotSupportedException e) { e.printStackTrace(); } } }输出结果:p1: 张三, 20 p2: 张三, 20 修改后的p1: 李四, 30 修改后的p2: 张三, 20从输出结果可以看出,修改p1的name和age属性后,p2的对应属性并没有发生改变,说明它们是深拷贝。
  • [其他] java中list数组的常用stream操作
    Java中的List数组是一种常用的数据结构,它允许我们存储和操作一系列的对象。而Stream API是Java 8中引入的一个新特性,它提供了一种高效且简洁的方式来处理List数组中的数据。本文将介绍一些常用的Stream操作。创建Stream要使用Stream API处理List数组,首先需要创建一个Stream对象。可以通过以下几种方式创建Stream:使用list.stream()方法从List数组中创建一个Stream。使用Arrays.stream(array)方法从数组中创建一个Stream。使用Stream.of(elements)方法从多个元素中创建一个Stream。List<String> list = Arrays.asList("A", "B", "C"); Stream<String> stream = list.stream();过滤(Filter)过滤操作可以根据给定的条件筛选出符合条件的元素。可以使用filter()方法实现过滤操作。List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); Stream<Integer> evenNumbers = numbers.stream().filter(n -> n % 2 == 0);映射(Map)映射操作可以将一个类型的元素转换为另一个类型的元素。可以使用map()方法实现映射操作。List<String> names = Arrays.asList("张三", "李四", "王五"); Stream<Integer> nameLengths = names.stream().map(String::length);排序(Sort)排序操作可以对Stream中的元素进行排序。可以使用sorted()方法实现排序操作。默认情况下,排序是基于元素的自然顺序进行的。如果需要自定义排序规则,可以提供一个比较器(Comparator)。List<String> words = Arrays.asList("apple", "banana", "orange"); Stream<String> sortedWords = words.stream().sorted();去重(Distinct)去重操作可以去除Stream中重复的元素。可以使用distinct()方法实现去重操作。List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5); Stream<Integer> distinctNumbers = numbers.stream().distinct();限制(Limit)限制操作可以获取Stream中的前n个元素。可以使用limit()方法实现限制操作。List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); Stream<Integer> firstThreeNumbers = numbers.stream().limit(3);跳过(Skip)跳过操作可以跳过Stream中的前n个元素。可以使用skip()方法实现跳过操作。List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); Stream<Integer> numbersAfterTwo = numbers.stream().skip(2);收集(Collect)收集操作可以将Stream中的元素收集到一个新的数据结构中,例如List、Set或Map。可以使用collect()方法实现收集操作。需要提供一个Collector,用于指定如何将元素收集到目标数据结构中。例如,可以使用Collectors.toList()将元素收集到一个List中。List<String> words = Arrays.asList("apple", "banana", "orange"); List<String> wordList = words.stream().collect(Collectors.toList());
  • [问题求助] 救助-java解析KAKFKA从DRS获取的AVRO数据反序列化问题
    问题描述:公司最近使用使用华为kafka,通过DRS获取ORACLE数据库日志文件并且使用ARVO格式序列化到kafka里的topic里,最后通过工具插入目标数据库中。问题点:现要通过java获取topic里的数据,并且反序列化ARVO格式成JSON可识别语句。目前已经获取到topic数据,但是反序列化出错,(使用的是官网给的反序列化demo和schem)kafka 消费者获取数据代码:出错代码行:报错信息:烦请大佬帮忙!!!
  • [技术干货] java线程池怎么用最安全?
     Java线程池是一种管理线程的工具,它可以在需要时创建新的线程,也可以在不需要时回收和重用已经存在的线程。使用线程池可以有效地减少线程的创建和销毁开销,提高系统性能。然而,如果不正确使用线程池,可能会导致资源浪费、系统崩溃等问题。因此,如何安全地使用Java线程池是非常重要的。  以下是一些关于如何最安全地使用Java线程池的建议:  1. 合理设置线程池大小:线程池的大小应该根据系统的硬件资源、并发任务的数量和任务的处理时间来设置。如果线程池太小,可能会导致任务排队等待处理,影响系统性能;如果线程池太大,可能会消耗过多的系统资源,导致系统崩溃。  2. 使用有界队列:有界队列可以防止任务过多而无法处理的情况。当队列满时,线程池会根据设定的策略拒绝新任务或者丢弃旧任务。这样可以保证系统的稳定性。  3. 避免长时间执行的任务:线程池中的线程是复用的,如果某个任务长时间执行,可能会导致其他任务无法得到及时处理。因此,对于长时间执行的任务,应该单独启动一个线程来处理。  4. 使用合适的拒绝策略:当线程池和队列都无法处理新任务时,线程池会调用设定的拒绝策略。常见的拒绝策略有抛出异常、丢弃任务、在队列中等待等。应该根据实际需求选择合适的拒绝策略。  5. 定期检查和调整线程池:应该定期检查线程池的状态,如线程数量、队列大小等,并根据实际需求进行调整。例如,如果发现队列经常满,可能需要增加线程池的大小或者增大队列的容量。  6. 使用线程池监控工具:有许多第三方库提供了线程池的监控工具,如VisualVM、JConsole等。这些工具可以帮助我们更好地了解线程池的状态,及时发现和解决问题。  7. 注意线程安全:在多线程环境下,需要注意线程安全问题。例如,多个线程同时访问共享资源时,可能会导致数据不一致的问题。可以使用synchronized关键字、Lock接口等机制来保证线程安全。  8. 及时关闭线程池:当不再需要线程池时,应该及时关闭它,以释放资源。可以通过调用ThreadPoolExecutor的shutdown()方法来关闭线程池。 
  • [技术干货] Java 反射机制
    什么是反射Java 中的反射(Reflection)是一种强大的编程特性,它允许程序在运行时动态地获取类的信息,包括类名、属性、方法等,并且可以对这些信息进行操作,如创建对象、调用方法和访问属性。反射机制使得程序在运行时能够更加灵活地处理不同类型的对象和情况,提高了程序的扩展性和灵活性。反射的作用反射主要提供以下功能:在运行时判断任意一个对象所属的类。在运行时构造任意一个类的对象。在运行时判断任意一个类所具有的成员变量和方法。在运行时调用任意一个对象的方法。Java为什么要支持反射Java 支持反射机制的原因主要有以下几点:灵活性:反射机制使得 Java 程序在运行时能够动态地获取类的信息,包括类名、属性、方法等。这使得程序在运行时能够更加灵活地处理不同类型的对象和情况。解耦:反射机制有助于实现程序组件之间的解耦。通过反射,可以在运行时动态地加载类、创建对象、调用方法等,这使得程序之间的依赖关系变得更加松散。扩展性:反射机制为 Java 程序提供了强大的扩展能力。通过反射,可以在运行时动态地加载和调用自定义的类和方法,这为程序的扩展和定制提供了便利。高效性:反射机制可以实现程序的优化。在某些场景下,通过反射创建对象、调用方法等操作可能会比使用静态语句更加高效。反射可以在运行时直接调用指定的方法,避免了额外的类型检查和绑定过程。安全性:反射机制可以提高 Java 程序的安全性。通过反射,可以对程序的运行状态进行动态监控和调试,有助于及时发现和修复潜在的安全隐患。框架基础:反射机制是许多 Java 框架(如 Hibernate、Struts 等)的基础。这些框架利用反射实现对对象、方法和属性的操作,从而简化了开发者的任务,提高了开发效率。反射机制为 Java 程序带来了更高的灵活性、扩展性、高效性和安全性,同时还是许多重要框架和技术的基础。反射的应用场景反射机制在 Java 中的应用场景包括:动态地加载类和对象。实现多态性,即在运行时动态地确定对象类型。序列化库(如 Jackson)和 Web 框架(如 Spring MVC、Jersey)中,利用反射和注解实现通用的序列化/反序列化机制以及请求参数和内容的转换。 通过灵活掌握反射机制,开发者可以更好地定制和优化程序,提高代码的灵活性和扩展性。同时,反射机制也是许多 Java 框架和高级技术的基础。代码示例以下是一个简单的 Java 反射应用示例,通过代码演示了如何使用反射创建对象、访问属性和方法:首先,定义一个简单的类 Person:public class Person { private String name; private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = age; } 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; } }然后,使用反射创建 Person 对象、访问属性和方法:import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ReflectionExample { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { // 1. 获取 Class 对象 Class<?> personClass = Class.forName("Person"); // 2. 创建对象 Constructor<?> constructor = personClass.getConstructor(String.class, int.class); Person person = (Person) constructor.newInstance("张三", 25); // 3. 访问属性 Field nameField = personClass.getDeclaredField("name"); nameField.setAccessible(true); nameField.set(person, "李四"); Field ageField = personClass.getDeclaredField("age"); ageField.setAccessible(true); ageField.set(person, 26); // 4. 访问方法 Method getNameMethod = personClass.getMethod("getName"); String name = (String) getNameMethod.invoke(person); System.out.println("Name: " + name); Method setNameMethod = personClass.getMethod("setName", String.class); setNameMethod.invoke(person, "王五"); Method getAgeMethod = personClass.getMethod("getAge"); int age = (int) getAgeMethod.invoke(person); System.out.println("Age: " + age); Method setAgeMethod = personClass.getMethod("setAge", int.class); setAgeMethod.invoke(person, 27); } }在这个示例中,我们首先使用 Class.forName() 方法获取 Person 类的 Class 对象。然后,通过 getConstructor() 方法获取构造方法,创建新的 Person 对象。接下来,使用 getDeclaredField() 方法获取类的属性,并通过 setAccessible() 方法访问私有属性。最后,使用 getMethod() 方法获取类的方法,并调用这些方法。
  • [技术干货] JAVA简单制作excel文件
    JAVA简单制作excel文件 需求:查询的业务数据根据指定的样式下载 比如这个模板包含了行合并和表头字体颜色和内容字体和下面的小计的颜色的行合并 怎么去做呢? 其实使用excel有很多种做法,要么你对自己的手法很自信那就直接手撸代码写样式,如果你想偷懒那就按照我说这种方式也许能给你提供一些想法。 1.创建一个excel文件制作两个sheet页 第一个sheet页制作: 我们可以根据模板将一些固定不变的模板画在这个sheet页,就像现在这个,我就可以直接去填充数据 第二个sheet页制作: 第二个sheet页你就放只有一条数据的一个简单的完整样式,用于下面的取样式  2.核心代码 //将你创建好的excel文件放到静态文件夹下面然后引用 ClassPathResource classPathResource = new ClassPathResource("static/zlfxb.xlsx"); InputStream inputStream = classPathResource.getStream(); XSSFWorkbook workBook = new XSSFWorkbook(inputStream); //引用完成 //接下来一个一个来获取业务数据的样式 //解读:正文样式:第2个sheet页第三行第一列的样式 XSSFCellStyle ptzt = workBook.getSheetAt(1).getRow(2).getCell(0).getCellStyle(); //这里可能会有多个样式,这就按照你的需求来,像上面一样创建多个样式.... XSSFSheet sheet1 = workBook.getSheetAt(0);//获取第一个sheet页,也就是正常填充业务数据的sheet页 XSSFRow row = null; Cell cell = null; //现在是循环填充数据 //这个mapList就是你的数据有多少行就是for多少个 for (int i = 0; i < mapList.size(); i++) {     //我们是从第三行开始填充     row = sheet1.createRow(i + 2);     //现在是要看你有多少列也是需要循环的     for(int x = 0; x < 11;x++){         //你有11列那就赋值下去咯,这里不是统一的,看你的数据格式         //这里是第i行创建第x列         cell = row.createCell(x);         //使用的是上面我们创建的正文格式         cell.setCellStyle(ptzt);         //给这个列表赋值,但是一定要是string不然会报错         cell.setCellValue(String.valueOf(map.get("orgNo")));     } } //赋值完数据后然后就把第二个样式sheet页删除掉就ok了 workBook.removeSheetAt(1);   FileOutputStream fos = null;   File file = File.createTempFile("分析表", ".xlsx");  fos = new FileOutputStream(file, false);  workBook.write(fos);  //弄成文件输出就行  核心代码就是这样的,上面的代码是提供的一些思路  3.一些常用java制作excel操作: 1.合并列:  sheet.addMergedRegion(new CellRangeAddress(firstRow,lastRow    ,firstCol, lastCol)); //例子解读:从第三行开始到第三行结束,第一列到第五列合并 sheet.addMergedRegion(new CellRangeAddress(2,2, 0, 4)); 2.删除行:  //其实java是没有删除excel这一说法的,只是说从第几行开始,下面的行要往上移几行,这一行代码最好写在需要删除行的下面,及时删除是最好的 sheet.shiftRows(5,sheet1.getLastRowNum(),-2); ———————————————— 原文链接:https://blog.csdn.net/weixin_42235875/article/details/132497421 
  • [技术干货] Java创建Excel文件
     public synchronized static int writeExcel(JSONArray jsonArray,int k) {         //创建Excel文件薄         HSSFWorkbook workbook=new HSSFWorkbook();         //创建工作表sheeet         HSSFSheet sheet=workbook.createSheet();         //创建第一行         HSSFRow row=sheet.createRow(0);         String[] title={"货号","中文名","英文名","CAS","分子式","分子量","图片网址","svg网址"};         HSSFCell cell_title = null;         for (int i=0;i<title.length;i++){             cell_title=row.createCell(i);             cell_title.setCellValue(title[i]);         }         //追加数据         for (int i=0;i<jsonArray.size();i++){             JSONObject jsonObject =(JSONObject) jsonArray.get(i);             HSSFRow nextrow=sheet.createRow(i+1);             HSSFCell cell_value=nextrow.createCell(0);             cell_value.setCellValue("");             cell_value=nextrow.createCell(1);             cell_value.setCellValue(TransApi.EN_to_ZH(jsonObject.get("name").toString()));             cell_value=nextrow.createCell(2);             cell_value.setCellValue(jsonObject.get("name").toString());             cell_value=nextrow.createCell(3);             cell_value.setCellValue(jsonObject.get("CAS").toString());             cell_value=nextrow.createCell(4);             cell_value.setCellValue(jsonObject.get("FCS").toString());             cell_value=nextrow.createCell(5);             cell_value.setCellValue(jsonObject.get("MW").toString());             cell_value=nextrow.createCell(6);             cell_value.setCellValue(jsonObject.get("img").toString());             cell_value=nextrow.createCell(7);             cell_value.setCellValue(jsonObject.get("svg").toString());         }         //创建一个文件         try {             File file=new File("D:/pacong/poi_test_"+k+".xls");             file.createNewFile();             FileOutputStream stream= FileUtils.openOutputStream(file);             workbook.write(stream);             stream.close();         }catch (Exception e){             e.printStackTrace();         }finally {             return 1;         }     }原文链接:https://blog.csdn.net/qq_31122833/article/details/123449030
  • [技术干货] java代码生成excel表格
    在很多项目中,都需要我们把数据库查询出来的数据导出成excel表格,导出就算了,还给我弄个模板,大标题要写在合并表格里还要居中,标题要有底色,还要加粗,等等,下面我就来讲一下我用到的表格操作:  首先要进行表格操作,要添加相关依赖  这里我重点说明一下,我用4.3及以上版本就不行,可能是方法有升级  <dependency>             <groupId>org.apache.poi</groupId>             <artifactId>poi-ooxml</artifactId>             <version>4.0.1</version>         </dependency>         <dependency>             <groupId>org.apache.poi</groupId>             <artifactId>poi</artifactId>             <version>4.0.1</version>         </dependency> 创建excel文件  如果想生成.xls文件:  Workbook workbook = new HSSFWorkbook(); 如果想生成.xlsx文件  Workbook workbook = new XSSFWorkbook(); 创建工作簿  也就是我们说的Sheet页,sheet页是根据Workbook对象生成的,也就是sheet页依赖excel文件而存在。  Sheet sheet = workbook.createSheet("Sheet1");  // Sheet1为要生成的sheet页的名称 写入操作  完成以上操作,在不考虑样式的前提下,我们就可以写数据了。写数据的话是按行进行写入的。 首先声明行变量 行是根据sheet声明的,参数为该行的位置(从0开始算)  Row titleRow = sheet.createRow(0); 写入数据 有了行对象,那么我们就可以对该行的某列进行操作了 首先要创建该行某列对象,然后用getCell方法获取单元格,参数为单元格索引位置(从0开始算),然后用setCellValue方法进行内容写入,参数为内容  titleRow.createCell(0); titleRow.getCell(0).setCellValue("序号"); 写完之后,返回workbook对象就可以了。 按这样操作,一份没有样式的表格文件就生成好了。 样式操作  首先要声明样式对象 因为创建的workbook对象方式不同,样式对象的类型也不一样,不过方法是类似的  XSSFCellStyle cellStyle = workbook.createCellStyle();  背景颜色  这里修改只需要改new java.awt.Color里的参数值即可,也支持输入0xff000000(默认值)16进制数 需要与设置填充格式一起使用才生效,使用无先后顺序  cellStyle.setFillForegroundColor(new XSSFColor(new java.awt.Color(89, 214, 255),new DefaultIndexedColorMap())); 字体居中  cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);  // 垂直居中 cellStyle.setAlignment(HorizontalAlignment.CENTER); // 水平居中 设置边框  BorderStyle.THIN为宽度属性  cellStyle.setBorderBottom(BorderStyle.THIN); // 下边框 cellStyle.setBorderRight(BorderStyle.THIN);  // 右边框 cellStyle.setBorderLeft(BorderStyle.THIN);   // 左边框 cellStyle.setBorderTop(BorderStyle.THIN);    // 上边框 设置填充格式  cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); 自动换行  cellStyle.setWrapText(true); 字体设置  Font titleFont = workbook.createFont();  // 声明字体对象 titleFont.setBold(true);        // 字体加粗 titleFont.setFontName("华文彩云");  // 字体类型 titleFont.setFontHeightInPoints((short) 20);  // 字体大小 cellStyle.setFont(titleFont);   // 赋值样式对象 设置列宽  sheet.setColumnWidth(0, 4200);  // 第0列列宽设置为4200 合并单元格  sheet.addMergedRegion(new CellRangeAddress(1, 3, 1, 3));  // 参数分别为起始行,结束行,起始列,结束列 1 返回前端的话,写法是这样的,方法是没有返回的,也就是void  public void index(HttpServletResponse response) {         XSSFWorkbook workbook = service.test();         if (workbook!=null){             String fileName = "1.xlsx";             try {                 fileName = URLEncoder.encode(fileName, "UTF-8");                 //设置ContentType请求信息格式                 response.setContentType("application/vnd.ms-excel;charset=utf-8");                 response.setHeader("Content-disposition", "attachment;filename=" + fileName);                 OutputStream outputStream = response.getOutputStream();                 workbook.write(outputStream);                 outputStream.flush();                 outputStream.close();             } catch (Exception e) {                 System.out.println(e);             }         }     }  我这边用到的基本是这些,如果有其他的操作可以评论我或者私信我,我这边在做补充 ———————————————— 原文链接:https://blog.csdn.net/weixin_45494557/article/details/132088533 
  • [技术干货] Java8让Excel的读写变得更加简单高效
    Apache POI 在业务开发中我们经常会遇到Excel的导入导出,而 Apache POI 是Java开发者常用的API。 【https://poi.apache.org/components/spreadsheet/index.html】  GridExcel Universal solution for reading and writing simply Excel based on functional programming and POI EventModel  GridExcel是基于Java8函数式编程和POI EventModel实现的用于Excel简单读写的通用解决方案。  基于POI EventModel,在读写数据量非常大的Excel时,降低内存占用避免OOM与频繁FullGC 基于函数编程,支持关联对象等多种复杂情况的处理,学习成本低 支持流式API,使代码编写和理解更简单,更直观 EventModel 什么是EventModel?在POI FAQ(常见问题解答)【https://poi.apache.org/help/faq.html#faq-N100C2】官方给出解释:  The SS eventmodel package is an API for reading Excel files without loading the whole spreadsheet into memory. It does require more knowledge on the part of the user, but reduces memory consumption by more than tenfold. It is based on the AWT event model in combination with SAX. If you need read-only access, this is the best way to do it.  SS eventmodel包是一个用于读取Excel文件而不将整个电子表格加载到内存中的API。 它确实需要用户掌握更多知识,但是将内存消耗减少了十倍以上。 它基于AWT(Abstract Window Toolkit)event model与SAX的结合。 如果您需要只读访问权限,这是最好的方式。  函数编程 说到函数编程,就不得不提Lambda表达式,如果对Java8中的Lambda不了解或理解不深刻,可以看下甲骨文官网给出的这篇文章,【https://www.oracle.com/technetwork/articles/java/architect-lambdas-part1-2080972.html】,个人认为这是Java8 Lambda从入门到进阶最好的文章之一。  其中函数编程的目的就是实现代码块传递,即,将方法作为参数在方法间传递。为此,随着Java语言的发展,不断出现一些解决方案:  Java 1.0, 使用Abstract Window Toolkit (AWT) EventModel来实现,但笨拙且不可行 Java 1.1,提出一系列“Listeners” 后来使用内部类和匿名内部类来实现,但是大多数情况下,它们只是被用作事件处理。 再后来发现更多地方将代码块作为对象(实际上是数据)不仅有用而且是必要的,但是Java中函数编程还是很笨拙,它需要成长。 直到Java 1.7,Java引入了java.lang.invoke包,提供一 种新的动态确定目标方法的机制(可以不用再单纯依靠固化在虚拟机中的字节码调用指令),称为MethodHandle,模拟字节码的方法指针调用,类似于C/C++的Function Pointer(函数指针)并引入第5条方法调用的字节码指令invokedynamic。 直到Java 1.8,基于Java 1.7提出的字节码指令invokedynamic,实现了Lamda技术,将函数作为参数在方法间传递,Java开始更好的支持函数式编程。 用反射不是早就可以实现了吗?Reflection API 重量级,性能低。 注意: 5、6、7参考《深入理解Java虚拟机》第2版,8.3.3 动态类型语言支持。  在POI的使用过程中,对大多数API User来说经常面临两个问题,这也是GridExcel致力解决的问题。  问题1. 仅使用简单的导入导出功能,但每次业务的数据对象结构不同,需要重新编写处理方法,很麻烦! 解决方法 将Excel处理逻辑抽取出来,封装成工具类。  封装条件 与大多数Java API一样,POI把更多的精力放在高级功能的处理上,比如Formula(公式)、Conditional Formatting(条件格式)、Zoom(缩放)等。对于仅仅做数据导入导出功能的API User,很少使用这些高级特性,这允许API用户对POI的使用进行简单的封装。  封装方式 无论是读是写,我们都需要解决Excel中的Columns(列)与Java数据对象Fields(字段)的映射关系,将这种映射关系作为参数(Map对象HashMap或LinkedHashMap),传递给工具类。  对于Columns不难理解,它可以是有序的数字或字母,也可以是其它字符串用来作为首行,表示该列数据的含义。  对于Fields,它的处理需要兼容复杂情况,如下:  查询字段时出现异常 字段或单元格的值为null 该列的值可能对应关联对象、甚至是关联集合中的某个字段值 字段或单元格的值需要做特殊处理,例如value == true?完成:失败; 反射 首先想到,也是大多数封装者都在使用的方式是就是Reflection API,从上文 函数编程 章节我们了解到,反射重量级,会降低代码的性能,同时对复杂情况的处理支持性不够好。  反射+注解 这种方式可以更好的支持复杂情况,但是反射依然会降低性能,同时注解对数据对象会造成代码侵入,而且对该工具类封装者的其他使用者无疑会增加学习成本。  匿名内部类 这种方式也可以很好的支持复杂情况,但是使用匿名内部类的语法显然患有“垂直问题”(这意味着代码需要太多的线条来表达基本概念),太过冗杂。至于性能,应该也不如直接传递函数来的快吧。  函数接口(Lambda) 这种方式是基于第5条方法调用的字节码指令invokeDynamic实现的,直接传递函数代码块,很好的支持复杂情况,性能较高,代码编写更简单结构更加简洁,而且对数据对象代码零侵入。  问题2. Excel导入或导出数据量比较大,造成内存溢出或频繁的Full GC,该如何解决? 解决方法 读Excel —— eventmodel 写Excel —— streaming.SXSSFWorkbook 原理 POI的使用对我们来说很常见,对下面两个概念应该并不陌生:  HSSFWorkbook(处理97(-2007) 的.xls) XSSFWorkbook(处理2007 OOXML (.xlsx) ) 但是对于eventmodel和streaming.SXSSFWorkbook就很少接触了,它们是POI提供的专门用来解决内存占用问题的low level API(低级API),使用它们可以读写数据量非常大的Excel,同时可以避免内存溢出或频繁的Full GC。【https://poi.apache.org/components/spreadsheet/how-to.html】  eventmodel,用来读Excel,并没有将Excel整个加载到内存中,而是允许用户从InputStream每读取一些信息,就交给回调函数或监听器,至于丢弃,存储还是怎么处理这些内容,都交由用户。 streaming.SXSSFWorkbook,用来写Excel(是对XSSFWorkbook的封装,仅支持.xlsx),通过滑动窗口来实现,只在内存中保留滑动窗口允许存在的行数,超出的行Rows被写出到临时文件,当调用write(OutputStream stream)方法写出内容时,再直接从临时内存写出到目标OutputStream。SXSSFWorkbook的使用会产生一些局限性。 Only a limited number of rows are accessible at a point in time. Sheet.clone() is not supported. Formula evaluation is not supported 解决途径 https://github.com/liuhuagui/gridexcel 基于Java函数编程(Lambda),支持流式API,使用环境Java1.8或更高,学习成本:Lambda 实际上POI官网已经给了用户使用示例,而上述工具只是做了自己的封装实现,让使用更方便。  快速使用 <dependency>     <groupId>com.github.liuhuagui</groupId>     <artifactId>gridexcel</artifactId>     <version>2.2</version> </dependency> GridExcel.java GridExcel.java提供了多种静态方法,可以直接使用,具体式例可参考测试代码(提供了测试数据和测试文件):  https://github.com/liuhuagui/gridexcel/blob/master/src/test/java/ReadTest.java https://github.com/liuhuagui/gridexcel/blob/master/src/test/java/WriteTest.java 流式API /**   * 业务逻辑处理方式三选一:   * 1.启用windowListener,并将业务逻辑放在该函数中。   * 2.不启用windowListener,使用get()方法取回全部数据集合,做后续处理。   * 3.readFunction函数,直接放在函数中处理 或 使用final or effective final的局部变量存放这写数据,做后续处理。   * 注意:使用EventModel时readFunction函数的输入为每行的cell值集合List<String>。   * @throws Exception   */  @Test  public void readXlsxByEventModel() throws Exception {      InputStream resourceAsStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("2007.xlsx");      GridExcel.readByEventModel(resourceAsStream,TradeOrder.class,ExcelType.XLSX)              .window(2,ts -> System.out.println(JSON.toJSONString(ts)))//推荐在这里执行自己的业务逻辑              .process(cs ->{                  TradeOrder tradeOrder = new TradeOrder();                  tradeOrder.setTradeOrderId(Long.valueOf(cs.get(0)));                  Consultant consultant = new  Consultant();                  consultant.setConsultantName(cs.get(3));                  tradeOrder.setConsultant(consultant);                  tradeOrder.setPaymentRatio(cs.get(16));                  return tradeOrder;              },1);  }  /**   * 使用Streaming UserModel写出数据到Excel   * @throws Exception   */  @Test  public void writeExcelByStreaming() throws Exception {      GridExcel.writeByStreaming(TradeOrder.class)              .head(writeFunctionMap())//对象字段到Excel列的映射              .createSheet()              .process(MockData.data())//模拟数据。在这里设置业务数据集合。              .write(FileUtils.openOutputStream(new File("/excel/test.xlsx")));  }  ReadExcel ReadExcelByUserModel Use user model to read excel file. userModel ——  缺点:内存消耗大,会将excel信息全部加载到内存再进行处理。 优点:现成的API,使用和理解更简单。 使用场景:可以处理数据量较小的Excel。 ReadExcelByEventModel Use event model to read excel file. eventModel ——  缺点:没有现成的API,使用和理解较为复杂,适合中高级程序员(GridExcel的目标之一就是让EventModel的使用变得简单) 优点:非常小的内存占用,并没有在一开始就将所有内容加载到内存中,而是把主体内容的处理(存储,使用,丢弃)都交给了用户,用户可以自定义监听函数来处理这些内容。 使用场景:可以处理较大数据量的Excel,避免OOM和频繁FullGC WriteExcel WriteExcelByUserModel Use user model to write excel file. userModel ——  缺点:会将产生的spreadsheets对象整个保存在内存中,所以write Excel的大小受到堆内存(Heap space)大小限制。 优点:使用和理解更简单。 使用场景:可以写出数据量较小的Excel。 WriteExcelByStreaming Use API-compatible streaming extension of XSSF to write very large excel file. streaming userModel——  缺点: 仅支持XSSF; Sheet.clone() is not supported; Formula evaluation is not supported; Only a limited number of rows are accessible at a point in time. 优点:通过滑动窗口来实现,内存中只保留指定size of rows的内容,超出部分被写出到临时文件,write Excel的大小不再受到堆内存(Heap space)大小限制。 使用场景:可以写出非常大的Excel。 Issues 在使用工具过程中出现问题,有功能添加或改动需求的可以向作者提Issue:https://github.com/liuhuagui/gridexcel/issues 比如说,想要增加对首行以外的行列做样式扩展 ————————————————  原文链接:https://blog.csdn.net/qq_32331073/article/details/97650960 
  • [技术干货] 小型java easy Excel导出工具通类(可拓展) jdk8函数式接口
    小型java easy Excel导出工具类(可拓展) jdk8函数式接口 首先需求是前端传入对象list,指定后端配置的Excel导出实体类package地址(根据需求可脱离前端根据业务需求定义接口直接调工具类方法即可,传参数方式由指向地址改为Excel导出实体类Class对象)并且以application/vnd.ms-excel类型的回执返回给前端download(实现逻辑简单不喜勿喷,希望得到各路神仙🐮的指点OwO)。讲一下实现思路首先后端拿到list和实体类映射地址或者类Class定义文件输出流定义两个方法一个作为创建列头和数据,另一个作为导出操作(这一步操作是由Lambda表达式写入提交)直接写入到流中返回给前端。  1.引入maven poi依赖以及其他应用到的依赖         <dependency>            <groupId>org.apache.poi</groupId>            <artifactId>poi</artifactId>        </dependency>        <dependency>            <groupId>org.apache.poi</groupId>            <artifactId>poi-ooxml</artifactId>        </dependency>         <dependency>             <groupId>org.springframework.boot</groupId>             <artifactId>spring-boot-starter-web</artifactId>         </dependency> 2.结构目录  annotations:字段型自定义注解(列头/日期格式/字典式数字替换文字/列头宽度……后期拓展字段交给你们OwO🐮) as:函数接口/自定义接口/定义接口实现 entity:Excel导出行数据转换实体 model:Excel导出实体类 uitls:Excel导出工具类  3.定义字段型注解(解释都在注释里了) package 这个你们放你们自己的包吧;  import java.lang.annotation.*;  @Documented @Target({ElementType.FIELD}) //注解应用类型(应用到方法的注解,还有类的可以自己试试) @Retention(RetentionPolicy.RUNTIME) // 注解的类型 public @interface Excel {      /**      * Excel列头      **/     String name() default "";      /**      * 日期格式转换      **/     String format() default "yyyy/MM/dd";      /** 文字替换根据 ":" 提供两种方式如下      * 1.普通数组替换: {"文本1:文本2",……}      * 2.反射方法替换: "${无参数方法返回值类型为List<Object>}"      * 且方法必须在同一实体类      **/     String[] filterText() default {};      /**      * 列头宽度      **/     int width() default 30;      /**      * 函数配置      **/     String func() default ""; }  4.自定义一个消费型函数式接口 这里着重讲一下含义所谓消费,其实就是将数据提交到接口里由方法重写由Lambda表达式的方式处理数据优化代码避免性能浪费  package 这个你们放你们自己的包吧;  /**  有且只有一个的函数式接口,称之为函数接口,接口可以包含其他方法(静态和默认以及私有)  * @Author du_zy  * @Description //TODO  * @Date 3:18 PM 2022/3/8  **/ @FunctionalInterface public interface ExportConsumer<ByteArrayOutputStream, Workbook, List>{      /** 自定义导出数据拼装      * @Author du_zy      * @Description //TODO      * @Date 2:46 PM 2022/3/8      * @Param [fileOut, wk, list]      * fileOut文件输出流最后是将文件写入到这个里传给前端的,wk是poi处理Excel样式以及数据的APi里面有很多强大的功能可以参考官方有兴趣可以看看,list是前端传入的数据      * @return void      **/     public abstract void accept(ByteArrayOutputStream fileOut, Workbook wk, List list) throws Exception; } 5.定义数据转换类实体 这里是对list的属性的一些定义因为要对list里的数据标记那些要日期格式化那些要文字替换下文在注释里有详解  package 这个你们放你们自己的包吧;  /**  * @ClassName FieldExcel  * @Description TODO  * @Author du_zy  * @Date 2022/3/11 10:09 AM  * @Version 1.0  **/ public class FieldExcel {      private String[] fieldArr;      private String fieldName;      private String fieldType;      private String fieldFormat;      public String[] getFieldArr() {         return fieldArr;     }      public void setFieldArr(String[] fieldArr) {         this.fieldArr = fieldArr;     }      public String getFieldName() {         return fieldName;     }      public void setFieldName(String fieldName) {         this.fieldName = fieldName;     }      public String getFieldType() {         return fieldType;     }      public void setFieldType(String fieldType) {         this.fieldType = fieldType;     }      public String getFieldFormat() {         return fieldFormat;     }      public void setFieldFormat(String fieldFormat) {         this.fieldFormat = fieldFormat;     }      public FieldExcel() {         this.fieldArr = new String[]{};         this.fieldName = "null_flag";     } } 6.导出列头实体类(这个类里的属性根据业务需求制作) 主要需要看Excel注解  package 这个你们放你们自己的包吧;  import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import 这个你们放你们自己的包吧.CfmRepairCodeEnum; import 这个你们放你们自己的包吧.annotations.Excel; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component;  import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.Map;  /**  *  实体类  * @author du  * @since 2021-12-07 15:23:53  */  @Data @EqualsAndHashCode(callSuper = false) @NoArgsConstructor @AllArgsConstructor @Component public class FitMentExcelModel implements Serializable {      private static final long serialVersionUID = 1L;     /**      * 装店装修业务单主键id      */     private Long cfmRepairId;     /**      * 装店装修单R单单号      */     @Excel(name = "装店装修单R单单号")     private String cfmRepairCode;     /**      * 申请日期      */     @Excel(name = "申请日期")     private LocalDateTime applyTime;     /**      * 申请人      */     @Excel(name = "申请人")     private String applyName;     /**      * 申请人电话      */     @Excel(name = "申请人电话")     private String applyMobile;     /**      * 城市经理名称      */     @Excel(name = "城市经理名称")     private String cityManager;     /**      * 城市经理电话      */     @Excel(name = "城市经理电话")     private String cityManagerPhone;     /**      * 装修负责人      */     @Excel(name = "装修负责人")     private String repairLeader;     /**      * 负责人电话      */     @Excel(name = "负责人电话")     private String leaderPhone;     /**      * 省份      */     @Excel(name = "省份")     private String province;     /**      * 城市      */     @Excel(name = "城市")     private String city;     /**      * 区      */     @Excel(name = "区")     private String area;     /**      * 店面名称      */     @Excel(name = "店面名称")     private String storeName;     /**      * 店面类别      */     @Excel(name = "店面类别")     private String storeType;     /**      * 店面类型      */     @Excel(name = "店面类型")     private String storeCategory;     /**      * 店面性质      */     @Excel(name = "店面性质")     private String storeProperty;     /**      * 店面面积      */     @Excel(name = "店面面积")     private BigDecimal storeArea;     /**      * 店面所属商圈      */     @Excel(name = "店面所属商圈")     private String storeBusinessArea;     /**      * 地级/县级      */     @Excel(name = "地级/县级", filterText = {"1:地级", "2:县级"})     private String cityLevel;     /**      * 老店历史装修时间      */     @Excel(name = "老店历史装修时间")     private LocalDate decorateDate;     /**      * 装修性质      */     @Excel(name = "装修性质")     private String repairLevelName;     /**      * 装修模式      */     @Excel(name = "装修模式")     private String repairModelName;     /**      * 预计开业时间      */     @Excel(name = "预计开业时间")     private LocalDate openDate;     /**      * 店面专区备注      */     @Excel(name = "店面专区备注")     private String storeTeamRemark;     /**      * 店面专区面积      */     @Excel(name = "店面专区面积")     private BigDecimal storeTeamArea;     /**      * 开始设计时间      */     @Excel(name = "开始设计时间")     private LocalDate designingDate;     /**      * 设计完成时间      */     @Excel(name = "设计完成时间")     private LocalDate designFinishDate;     /**      * 设计周期      */     @Excel(name = "设计周期")     private Integer designCycle;     /**      * 铺地砖日期      */     @Excel(name = "铺地砖日期")     private LocalDate brickDate;     /**      * 木工日期      */     @Excel(name = "木工日期")     private LocalDate woodDate;     /**      * 油工日期      */     @Excel(name = "油工日期")     private LocalDate oilDate;     /**      * 安装样品日期      */     @Excel(name = "安装样品日期")     private LocalDate installDate;     /**      * 软装摆场日期      */     @Excel(name = "软装摆场日期")     private LocalDate softDate;     /**      * 暂不装修时间      */     @Excel(name = "暂不装修时间")     private LocalDate notRepairDate;     /**      * 暂不装修原因      */     @Excel(name = "暂不装修原因")     private String notRepairReason;     /**      * 不装修时间      */     @Excel(name = "不装修时间")     private LocalDate noRepairDate;     /**      * 不装修原因      */     @Excel(name = "不装修原因")     private String noRepairReason;     /**      * 装修完成时间      */     @Excel(name = "装修完成时间")     private LocalDate repairFinishDate;     /**      * 装修周期      */     @Excel(name = "装修周期")     private Integer repairCycle;     /**      * 验收完成时间      */     @Excel(name = "验收完成时间")     private LocalDate checkDate;     /**      * 验收周期      */     @Excel(name = "验收周期")     private Integer checkCycle;     /**      * 验收状态 0:不合格 1:合格 2:无验收      */     @Excel(name = "验收状态", filterText = {"0:不合格", "1:合格", "2:无验收"})     private Integer checkState;     /**      * 装修单状态      */     @Excel(name = "装修单状态", filterText = "${getEnumRepairSate}")     private Integer repairState;     @Excel(name = "状态")     private String state;     /**      * 支付时间      */     @Excel(name = "支付时间")     private LocalDateTime payTime;     /**      * 支付单号      */     @Excel(name = "支付单号")     private String cfmPayCode;     /**      * 设计费金额(分)      */     @Excel(name = "设计费金额(分)")     private Long sumDesignCost;     /**      * 支付方式      */     @Excel(name = "支付方式")     private String payMethod;     /**      * 评价状态 0:未评价 1:已评价      */     @Excel(name = "评价状态", filterText = {"0:未评价", "1:已评价"})     private Integer evaluateState;     /**      * 评价信息  图纸设计      */     @Excel(name = "评价信息-图纸设计")     private BigDecimal evaluateDesign;     /**      * 评价信息  装修效果      */     @Excel(name = "评价信息-装修效果")     private BigDecimal evaluateRepair;     /**      * 评价信息  服务沟通      */     @Excel(name = "评价信息-服务沟通")     private BigDecimal evaluateCommunication;     /**      * 评价内容      */     @Excel(name = "评价内容")     private String evaluateContent;     /**      * 设计师负责人名称      */     @Excel(name = "设计师负责人名称")     private String responseName;     /**      * 设计师名称      */     @Excel(name = "设计师名称")     private String designName;     /**      * 表单备注      */     @Excel(name = "表单备注")     private String formRemark;     /**      * 备注      */     @Excel(name = "备注")     private String remark;     /**      * 关联店面id      */     private String storeId;     /**      * 店面编码      */     private String storeCode;     /**      * 申请人id      */     private Long applyId;       /**将一串key-value数据转换成key:value数据并返回成list      * @Author du_zy      * @Description //TODO       * @Date 4:12 PM 2022/3/23      * @Param []      * @return java.util.List<java.lang.Object>      **/     @Bean     public List<Object> getEnumRepairSate() {         List<Object> resultList = new ArrayList<>();         for (Map.Entry<String, Object> stringObjectEntry : CfmRepairCodeEnum.getEnum().entrySet()) {             JSONObject j = JSONObject.parseObject(JSON.toJSONString(stringObjectEntry.getValue()));             resultList.add(j.get("code") + ":" + j.get("msg"));         }       return resultList;     }          /**      * 装修性质code      */     private String repairLevelCode;     /**      * 联系电话      */     private String mobile;     /**      * 省份编码      */     private String provinceCode;     /**      * 城市编码      */     private String cityCode;     /**      * 区编码      */     private String areaCode;     /**      * 装修模式code      */     private String repairModelCode;     /**      * 设计费单价(分)      */     private Long designCost;     /**      * 设计部数据员id      */     private Long dataId;     /**      * 设计部数据员名称      */     private String dataName;     /**      * 设计师负责人id      */     private Long responseId;     /**      * 设计师id      */     private Long designId;     /**      * 验收图片 0:未上传 1:已上传      */     private Integer checkPhotoState;     /**      * 关闭时间      */     private LocalDateTime closeTime;     /**      * 是否删除(0:未删除、1:删除)      */     private String delFg;     /**      * 创建人      */     private String createBy;     /**      * 创建人编码      */     private String createCode;     /**      * 创建时间      */     private LocalDateTime createDt;     /**      * 更新人      */     private String updateBy;     /**      * 更新人编码      */     private String updateCode;     /**      * 更新时间      */     private LocalDateTime updateDt;     /**      * 时间戳      */     private Long timeStamp;  }  7.导出工具类(创建列头和数据以及提交写入流方法)都在注释里了 package 这个你们放你们自己的包吧;  import com.alibaba.fastjson.JSONObject; import 这个你们放你们自己的包吧; import 这个你们放你们自己的包吧; import 这个你们放你们自己的包吧; import 这个你们放你们自己的包吧; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.util.WorkbookUtil; import org.springframework.beans.TypeMismatchException; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.util.StringUtils;  import java.io.ByteArrayOutputStream; import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import java.util.regex.Pattern;  /**  * @ClassName ExportUtils  * @Description TODO  * @Author du_zy  * @Date 2022/3/8 11:03 AM  * @Version 1.0  **/ public class ExportUtils {      /** 导出Excel      * @Author du_zy      * @Description //TODO      * @Date 3:04 PM 2022/3/8      * @Param [fileOut, wb, listData, fileName, consumer]      * @return org.springframework.http.ResponseEntity<byte[]>      **/     public static <T> ResponseEntity<byte[]> exportExcel(ByteArrayOutputStream fileOut,                                                          Workbook wb,                                                          List<T> listData,                                                          String fileName,                                                          ExportConsumer<ByteArrayOutputStream, Workbook, List<T>> consumer) {         HttpHeaders headers = new HttpHeaders();         try {             consumer.accept(fileOut, wb, listData); // 函数接口数据配置             headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); // 配置请求头类型             headers.setContentDispositionFormData("attachment", new String((fileName + ".xlsx").getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1)); //配置表单信息,字符转换             return new ResponseEntity<>(fileOut.toByteArray(), headers, HttpStatus.OK);         } catch (Exception e) {             return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);          }      }      /** 创建标头和数据      * @Author du_zy      * @Description //TODO      * @Date 2:59 PM 2022/3/8      * @Param [wb, data, className]      * @return org.apache.poi.ss.usermodel.Workbook      **/     public static <T> Workbook createCell(Workbook wb, List<T> data, Class<?> className) {         try {             String safeName = WorkbookUtil.createSafeSheetName("sheet1");             Sheet sheet = wb.createSheet(safeName);  // 创建工作空间             //第一行标题             Row title = sheet.createRow(0);             List<Map<String, Object>> cols = new LinkedList<>();             for (int i = 0; i < className.getDeclaredFields().length; i++) { // 循环如果反射里的类里有注解name配置就存入到标头list里                 boolean is = className.getDeclaredFields()[i].isAnnotationPresent(Excel.class);                 if (is) {                     Map<String, Object> coverMap = new HashMap<>();                     Excel annotation = className.getDeclaredFields()[i].getAnnotation(Excel.class);                     coverMap.put("name", annotation.name());                     coverMap.put("width", annotation.width());                     cols.add(coverMap);                 }             }              for (int i = 0; i < cols.size(); i++) { //第一行放标题                 title.createCell(i).setCellValue(cols.get(i).get("name").toString()); //                sheet.autoSizeColumn(i, true);                 sheet.setColumnWidth(i,  (int)((Integer.parseInt(cols.get(i).get("width").toString()) + 0.72) * 256));              } //            //如果list里有数组数据,加入到list里 //            String dataJson = JSONArray.toJSONString(data);// 先把list转换成json str数组格式 //            JSONArray objects = JSONArray.parseArray(dataJson); // 在转成json 数组 //            for (Object o : objects) { //                String s = JSONObject.toJSONString(o); //Object 对象转成jsonString //                JSONObject jsonObject = JSONObject.parseObject(s); //                jsonObject.forEach( (key, value) -> { //                    if (jsonObject.get(key) instanceof List) { // 如果这一行数据里有list数据,那么循环他并给给他放初始list里一并导出 //                        JSONArray jsonArray = jsonObject.getJSONArray(key); //                        for (Object o1 : jsonArray) { //                            data.add((T) o1); //                        } //                        System.out.println("是list类型"); //                    } else { //                        System.out.println("不是list类型"); //                    } //                }); //            } //            if (1==1) { //                throw new NullPointerException(); //            }             Field[] fields = className.getDeclaredFields();// 获取到反射类里所有的属性             Map<Integer, FieldExcel> fieldMap = new HashMap<>(); // integer: 存放的Excel列下标, map:存放的属性名称、类型、日期格式转换             data.forEach( o -> { // 数据拼接                 Row row = sheet.createRow(data.indexOf(o) + 1);// 创建一行数据                 String s = JSONObject.toJSONString(o); //Object 对象转成jsonString                 JSONObject jsonObject = JSONObject.parseObject(s); //jsonString转成JSONObject                 int mapIndex = 0; // 定义Excel列下标                 for (int i = 0; i < fields.length; i++) {// 循环找出如果有注解配置的字段添加到数组里                     boolean is = fields[i].isAnnotationPresent(Excel.class);                     if (is) {                         Excel annotation = fields[i].getAnnotation(Excel.class); // 获取注解里的属性                         fields[i].setAccessible(true); // 设置私有属性可见                         FieldExcel fieldExcel = new FieldExcel();                         if (!StringUtils.isEmpty(jsonObject.get(fields[i].getName()))) { // 如果反射里的字段可以在传入list里json对象找到就存找的数据,没有存空                             if (annotation.filterText().length > 0) { // 如果注解里配置了数组类型属性,将会被替换自负分割0 替换 1                                 String s1 = annotation.filterText()[0];                                 if (s1.startsWith("${") && s1.endsWith("}")) {// 用第一个元素做校验                                     try {                                         int startIndex = s1.indexOf("${") + 2;                                         int endIndex = s1.indexOf("}");                                         String methodName = s1.substring(startIndex, endIndex); // 获取到反射类里的方法名称这个是在注解上定义的名称不包含El表达式的名称                                         List<String> beanList = SpringUtils.getBean(methodName, List.class); // 获取实体里的list方法方式1:通过spring bean装载方式实现映射方法返回(实体类规范: 1.类必须交给spring管理 2.方法必须加注解Bean(name = "方法名")) //                                        Method getEnumRepairSate = className.getMethod(methodName); // 获取实体里的list方法方式2: 获取到方法实例通过java反射机制的方式加载射射方法返回 //                                        Object obj = className.newInstance(); // 生成反射加载类实例(调用实体类里带有无参数的构造方法) 从jvm角度看此类必须保证是加载了才能够使用此方法,而new不同new可以不用加载 //                                        List<Object> fieldCoverList = (List<Object>) getEnumRepairSate.invoke(obj); // 指定类实例转List<Object>类型                                         String[] fieldArr = beanList.toArray(new String[beanList.size()]); // List 转 数组                                         fieldExcel.setFieldArr(fieldArr);                                     } catch (Exception e) {                                         e.printStackTrace();                                     }                                 } else {                                     fieldExcel.setFieldArr(annotation.filterText());                                 }                             }                             fieldExcel.setFieldName(fields[i].getName());                             if (fields[i].getGenericType().toString().contains("Date")) { //如果属性类型包含Date说明是日期类型放入注解的format的属性                                 fieldExcel.setFieldFormat(annotation.format());                             }                         }                         fieldExcel.setFieldType(fields[i].getGenericType().toString());                         fieldMap.put(mapIndex, fieldExcel);                         mapIndex++;                     }                 }                 for (Map.Entry<Integer, FieldExcel> i : fieldMap.entrySet()) { //循环带有注解的字段依次排放数据(在加个循环的原因是:第一次循环有注解的for拼接数据会导致数据顺序错乱,因为注解的顺序不一定每个属性都有)                     FieldExcel value = i.getValue();                     if (value.getFieldArr().length > 0) {                         for (String s1 : value.getFieldArr()) {                             String[] split = s1.split(":"); // ":"号分割                             if (split[0].equals(jsonObject.get(value.getFieldName()).toString())) { // 0 替换 1                                 row.createCell(i.getKey()).setCellValue(split[1]);                             }                         }                     } else if (!StringUtils.isEmpty(value.getFieldFormat()) && value.getFieldType().contains("Date")) { // 如果日期格式转换属性不为空并且字段类型包含Date进行格式转换,如果没有就正常赋值                         SimpleDateFormat dateFormat = getDateFormat(jsonObject.get(value.getFieldName()).toString()); // 验证日期格式这里做了一个正则日期校验 斜杠和- 任意转换加时间或者不加时间                         Date fieldNameDate = null;                         try {                              fieldNameDate = dateFormat.parse(jsonObject.get(value.getFieldName()).toString());                         } catch (ParseException e) {                             e.printStackTrace();                         }                         SimpleDateFormat format = new SimpleDateFormat(value.getFieldFormat()); // 转换成注解定义的格式 yyyy/MM/dd HH:mm:ss yyyy-MM-dd HH:mm:ss ……                         String strFieldName = format.format(fieldNameDate);                         row.createCell(i.getKey()).setCellValue(strFieldName);                     } else {                         row.createCell(i.getKey()).setCellValue(value.getFieldName().equals("null_flag") ? "" : jsonObject.get(value.getFieldName()).toString());                     }                  }                 mapIndex = 0;                 fieldMap.clear();// 下标清0 map清空 进行下一次循环             });             return wb;         }catch (Exception e) {             e.printStackTrace();             return null;         }     }      private static SimpleDateFormat getDateFormat(String source) {         SimpleDateFormat sdf = new SimpleDateFormat();         sdf.setLenient(false);         if(Pattern.matches("^\\d{4}-\\d{2}-\\d{2}$",source)){             sdf = new SimpleDateFormat("yyyy-MM-dd");         }else if(Pattern.matches("^\\d{4}/\\d{2}/\\d{2}$",source)){             sdf = new SimpleDateFormat("yyyy/MM/dd");         }else if(Pattern.matches("^\\d{4}\\d{2}\\d{2}$",source)){             sdf = new SimpleDateFormat("yyyyMMdd");         }else if(Pattern.matches("^\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}$",source)) {             sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");         }else if(Pattern.matches("^\\d{4}/\\d{2}/\\d{2}\\s\\d{2}:\\d{2}:\\d{2}$",source)) {             sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");         }else if(Pattern.matches("^\\d{4}\\d{2}\\d{2}\\s\\d{2}:\\d{2}:\\d{2}$",source)) {             sdf = new SimpleDateFormat("yyyyMMdd HH:mm:ss");         }else {             //这里抛出一个异常,             throw new TypeMismatchException("",Date.class);         }         return sdf;     } } 8.自定义接口调用 到了这一步这个方法已经注入到spring的管辖范围内了,就可以根据业务需求处理list然后导出啦!  package  这个你们放你们自己的包吧;  import  这个你们放你们自己的包吧; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Workbook; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service;  import javax.annotation.Resource; import java.io.ByteArrayOutputStream; import java.util.List;  @Service public class OrdCfmExport implements IOrdCfmExport {      @Override     public ResponseEntity<byte[]> export(List<Object> list, String className){         try {             ByteArrayOutputStream fileout = new ByteArrayOutputStream();             Workbook wb = new HSSFWorkbook();             Class<?> aClass = Class.forName(className); // 反射加载类实体             return ExportUtils.exportExcel(                     fileout,                     wb,                     list,                     "",                     (out, workbook, data) -> ExportUtils.createCell(                             wb,                             data,                             aClass).write(fileout));         } catch (Exception e) {             e.printStackTrace();             return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);         }     }          @Override     public ResponseEntity<byte[]> export(List<Object> list, Class<?> className)                    {         try {             ByteArrayOutputStream fileout = new ByteArrayOutputStream();             Workbook wb = new HSSFWorkbook();             Class<?> aClass = className; // 反射加载类实体             return ExportUtils.exportExcel(                     fileout,                     wb,                     list,                     "",                     (out, workbook, data) -> ExportUtils.createCell(                             wb,                             data,                             aClass).write(fileout));         } catch (Exception e) {             e.printStackTrace();             return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);         }     } }  9.最后贴一下vue代码         export() {             let that = this             let params = {}             params.data = that.exportBatchWrapper             params.baseUrl = that.url.baseUrlExport             framework.save(that.url.data.saveExport, params, 'EXPORT_POST').then((res) => {                 let blob = new Blob([res.data], { type: 'application/vnd.ms-excel' })                 // 其他浏览器                 let link = document.createElement('a') // 创建a标签                 link.style.display = 'none'                 let objectUrl = URL.createObjectURL(blob)                 link.href = objectUrl                 link.click()                 URL.revokeObjectURL(objectUrl)             }).catch((e) => {                 that.$message.error('导出失败,内部服务器错误!')             })         }      save: function (url, params, method) {         if (method === 'GET') {             return http.get(url, params.data, null, params.baseUrl)         } else if (method === 'POST') {             return http.post(url, params.data, null, params.baseUrl)         } else if (method === 'EXPORT_POST') {             return http.post(url, params.data, { responseType: 'blob' }, params.baseUrl)         }     } /**  * http post请求  * @param url请求的url地址  * @param params 基于post请求参数,格式必须是json,例如{id: '1111'}  * @param config 其他配置,一般是用于配置其他属性  */ export function post(url, data, config, baseUrl) {     let options = { url: url, data: data, method: 'post' }     let opt = config ? Object.assign(config, options) : options     if (baseUrl === '' || baseUrl === null || baseUrl === undefined) {         axios = new HttpRequest(getBaseUrl())         return axios.request(opt)     } else {         if (nodeEnv === 'production') {             axios = new HttpRequest(getBaseUrl() + baseUrl)         } else {             axios = new HttpRequest(baseUrl)         }         return axios.request(opt)     } } axios配置这里不做展示了,相信到这里就已经能明白了。后期如果有代码技术上的问题请后台联系我发起技术讨论大家相互学习。最后希望大家学无止境,欲穷千里目,更上一层楼。———————————————— 原文链接:https://blog.csdn.net/weixin_44556981/article/details/123677796 
  • [技术干货] Java中操作Excel的3种方法,太好用了!
    一、介绍在平时的业务系统开发中,少不了需要用到导出、导入excel功能,今天我们就一起来总结一下,如果你正为此需求感到困惑,那么阅读完本文,你一定会有所收获!二、poi大概在很久很久以前,微软的电子表格软件 Excel 以操作简单、存储数据直观方便,还支持打印报表,在诞生之初,可谓深得办公室里的白领青睐,极大的提升了工作的效率,不久之后,便成了办公室里的必备工具。随着更多的新语言的崛起,例如我们所熟悉的 java,后来便有一些团队开始开发一套能与 Excel 软件无缝切换的操作工具!这其中就有我们所熟悉的 apache 的 poi,其前身是 Jakarta 的 POI Project项目,之后将其开源给 apache 基金会!当然,在java生态体系里面,能与Excel无缝衔接的第三方工具还有很多,因为 apache poi 在业界使用的最广泛,因此其他的工具不做过多介绍!话不多说,直接开撸!2.1、首先引入apache poi的依赖<dependencies>    <!--xls(03)-->    <dependency>        <groupId>org.apache.poi</groupId>        <artifactId>poi</artifactId>        <version>4.1.2</version>    </dependency>    <!--xlsx(07)-->    <dependency>        <groupId>org.apache.poi</groupId>        <artifactId>poi-ooxml</artifactId>        <version>4.1.2</version>    </dependency>    <!--时间格式化工具-->    <dependency>        <groupId>joda-time</groupId>        <artifactId>joda-time</artifactId>        <version>2.10.6</version>    </dependency></dependencies>2.2、导出excel导出操作,即使用 Java 写出数据到 Excel 中,常见场景是将页面上的数据导出,这些数据可能是财务数据,也可能是商品数据,生成 Excel 后返回给用户下载文件。在 poi 工具库中,导出 api 可以分三种方式HSSF方式:这种方式导出的文件格式为office 2003专用格式,即.xls,优点是导出数据速度快,但是最多65536行数据XSSF方式:这种方式导出的文件格式为office 2007专用格式,即.xlsx,优点是导出的数据不受行数限制,缺点导出速度慢SXSSF方式:SXSSF 是 XSSF API的兼容流式扩展,主要解决当使用 XSSF 方式导出大数据量时,内存溢出的问题,支持导出大批量的excel数据2.2.1、HSSF方式导出HSSF方式,最多只支持65536条数据导出,超过这个条数会报错!public class ExcelWrite2003Test {    public static String PATH = "/Users/hello/Desktop/";    public static void main(String[] args) throws Exception {        //时间        long begin = System.currentTimeMillis();        //创建一个工作簿        Workbook workbook = new HSSFWorkbook();        //创建表        Sheet sheet = workbook.createSheet();        //写入数据        for (int rowNumber = 0; rowNumber < 65536; rowNumber++) {            //创建行            Row row = sheet.createRow(rowNumber);            for (int cellNumber = 0; cellNumber < 10; cellNumber++) {                //创建列                Cell cell = row.createCell(cellNumber);                cell.setCellValue(cellNumber);            }        }        System.out.println("over");        FileOutputStream fileOutputStream = new FileOutputStream(PATH + "用户信息表2003BigData.xls");        workbook.write(fileOutputStream);        fileOutputStream.close();        long end = System.currentTimeMillis();        System.out.println((double) (end - begin) / 1000);//4.29s    }}2.2.2、XSSF方式导出XSSF方式支持大批量数据导出,所有的数据先写入内存再导出,容易出现内存溢出!public class ExcelWrite2007Test {    public static String PATH = "/Users/hello/Desktop/";    public static void main(String[] args) throws Exception {        //时间        long begin = System.currentTimeMillis();        //创建一个工作簿        Workbook workbook = new XSSFWorkbook();        //创建表        Sheet sheet = workbook.createSheet();        //写入数据        for (int rowNumber = 0; rowNumber < 65537; rowNumber++) {            Row row = sheet.createRow(rowNumber);            for (int cellNumber = 0; cellNumber < 10; cellNumber++) {                Cell cell = row.createCell(cellNumber);                cell.setCellValue(cellNumber);            }        }        System.out.println("over");        FileOutputStream fileOutputStream = new FileOutputStream(PATH + "用户信息表2007BigData.xlsx");        workbook.write(fileOutputStream);        fileOutputStream.close();        long end = System.currentTimeMillis();        System.out.println((double) (end - begin) / 1000);//15.87s    }}2.2.3、SXSSF方式导出SXSSF方式是XSSF方式的一种延伸,主要特性是低内存,导出的时候,先将数据写入磁盘再导出,避免报内存不足,导致程序运行异常,缺点是运行很慢!public class ExcelWriteSXSSFTest {    public static String PATH = "/Users/hello/Desktop/";    public static void main(String[] args) throws Exception {        //时间        long begin = System.currentTimeMillis();        //创建一个工作簿        Workbook workbook = new SXSSFWorkbook();        //创建表        Sheet sheet = workbook.createSheet();        //写入数据        for (int rowNumber = 0; rowNumber < 100000; rowNumber++) {            Row row = sheet.createRow(rowNumber);            for (int cellNumber = 0; cellNumber < 10; cellNumber++) {                Cell cell = row.createCell(cellNumber);                cell.setCellValue(cellNumber);            }        }        System.out.println("over");        FileOutputStream fileOutputStream = new FileOutputStream(PATH + "用户信息表2007BigDataS.xlsx");        workbook.write(fileOutputStream);        fileOutputStream.close();        long end = System.currentTimeMillis();        System.out.println((double) (end - begin) / 1000);//6.39s    }}2.3、导入excel导入操作,即将 excel 中的数据采用java工具库将其解析出来,进而将 excel 数据写入数据库!同样,在 poi 工具库中,导入 api 也分三种方式,与上面的导出一一对应!五、小结总体来说,easypoi和easyexcel都是基于apache poi进行二次开发的。不同点在于:1、easypoi 在读写数据的时候,优先是先将数据写入内存,优点是读写性能非常高,但是当数据量很大的时候,会出现oom,当然它也提供了 sax 模式的读写方式,需要调用特定的方法实现。2、easyexcel 基于sax模式进行读写数据,不会出现oom情况,程序有过高并发场景的验证,因此程序运行比较稳定,相对于 easypoi 来说,读写性能稍慢!easypoi 与 easyexcel 还有一点区别在于,easypoi 对定制化的导出支持非常的丰富,如果当前的项目需求,并发量不大、数据量也不大,但是需要导出 excel 的文件样式千差万别,那么我推荐你用 easypoi;反之,使用 easyexcel !六、参考1、apache poi - 接口文档2、easypoi - 接口文档3、easyexcel - 接口文档原文链接:https://blog.csdn.net/sufu1065/article/details/115301974
  • [技术干货] Java操作Excel三种方式POI、Hutool、EasyExcel
    Java操作Excel三种方式POI、Hutool、EasyExcel 1. Java操作Excel概述 1.1 Excel需求概述 1.2 Excel操作三种方式对比 2. ApachePOIExcel 2.1 ApachePOI简介 2.2 ApachePOI功能结构 2.3 ApachePOI官网说明 2.4 ApachePOI实现验证 3. HutoolExcel 3.1 Hutool简介 3.2 Hutool组件 3.1 HutoolExcel实现验证 4. EasyExcel 4.1 EasyExcel简介 4.2 HutoolExcel实现验证 5. 源码地址 1. Java操作Excel概述 1.1 Excel需求概述 Excel作为常用的报表文件,项目开发中经常用到Excel操作功能,对于Excel操作目前常见的有三种方式,Apache的POI,Hutool的ExcelUtil,Ali的EasyExcel。  1.2 Excel操作三种方式对比 Apache的POI相对比较原生,功能强大,使用复杂繁琐,内存占用比较大。 Hutool的ExcelUtil在POI基础上进行封装,简单易用,功能不太全,对于一些特殊操作,如标注等不具备。 Ali的EasyExcel也是在POI基础上进行封装,简单易用,功能相对比较全面,且性能十分强悍,最为推荐。  2. ApachePOIExcel 2.1 ApachePOI简介 Apache POI [1] 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsoft Office格式档案读和写的功能。POI为“Poor Obfuscation Implementation”的首字母缩写,意为“简洁版的模糊实现”。  2.2 ApachePOI功能结构 HSSF [1]  - 提供读写Microsoft Excel XLS格式档案的功能。 XSSF [1]  - 提供读写Microsoft Excel OOXML XLSX格式档案的功能。 HWPF [1]  - 提供读写Microsoft Word DOC格式档案的功能。 HSLF [1]  - 提供读写Microsoft PowerPoint格式档案的功能。 HDGF [1]  - 提供读Microsoft Visio格式档案的功能。 HPBF [1]  - 提供读Microsoft Publisher格式档案的功能。 HSMF [1]  - 提供读Microsoft Outlook格式档案的功能。 2.3 ApachePOI官网说明 POI官网地址:https://poi.apache.org/index.html POI下载地址:https://poi.apache.org/download.html POI文档说明:https://poi.apache.org/apidocs/index.html  2.4 ApachePOI实现验证 Maven依赖配置          <!-- poi 相关 -->         <dependency>             <groupId>org.apache.poi</groupId>             <artifactId>poi</artifactId>             <version>4.0.1</version>         </dependency>         <dependency>             <groupId>org.apache.poi</groupId>             <artifactId>poi-ooxml</artifactId>             <version>4.0.1</version>         </dependency>         <dependency>             <groupId>org.apache.poi</groupId>             <artifactId>poi-ooxml-schemas</artifactId>             <version>4.0.1</version>         </dependency>  代码测试  package com.zrj.easyexcel.excel;  import com.google.common.collect.Lists; import org.junit.Test;  import java.io.File; import java.util.HashMap; import java.util.List; import java.util.Map;  /**  * 原生Excel测试类  *  * @author zrj  * @since 2021/12/29  **/ public class EasyExcelPoi {      @Test     public void createExcelTest() throws Exception {         //文件的基础路径         String fileBasePath = System.getProperty("user.dir") + File.separator + "excel" + File.separator;         System.out.println("文件的基础路径:" + fileBasePath);          String filepath = fileBasePath + "writeexcel.xlsx";         String sheetName = "模板名称";         List<String> titles = Lists.newArrayList("name", "sex", "age");         List<Map<String, Object>> values = Lists.newArrayList();          Map map2 = new HashMap(6);         map2.put("name", "王五");         map2.put("sex", "女");         map2.put("age", "20");         values.add(map2);          Map map3 = new HashMap(6);         map3.put("name", "阿萨");         map3.put("sex", "女");         map3.put("age", "66");         values.add(map3);          ExcelUtils.writeExcel(filepath, sheetName, titles, values);         System.out.println("创建Excel完成");     }      @Test     public void copyExcelTest() throws Exception {         //文件的基础路径         String fileBasePath = System.getProperty("user.dir") + File.separator + "excel" + File.separator;         System.out.println("文件的基础路径:" + fileBasePath);          String filepath = fileBasePath + "writeexcel.xlsx";         String fileNewPath = fileBasePath + "writeexcel2.xlsx";         ExcelUtils.writeExcel(filepath, fileNewPath);         System.out.println("复制Excel完成");     }      @Test     public void readExcelTest() throws Exception {         //文件的基础路径         String fileBasePath = System.getProperty("user.dir") + File.separator + "excel" + File.separator;         String filepath = fileBasePath + "writeexcel.xlsx";         System.out.println("文件路径:" + fileBasePath);          String readExcel = ExcelUtils.readExcel(filepath);         System.out.println(readExcel);         System.out.println("读取Excel完成");     }  } 3. HutoolExcel 3.1 Hutool简介 Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 Hutool中的工具方法来自每个用户的精雕细琢,它涵盖了Java开发底层代码中的方方面面,它既是大型项目开发中解决小问题的利器,也是小型项目中的效率担当; Hutool是项目中“util”包友好的替代,它节省了开发人员对项目中公用类和公用工具方法的封装时间,使开发专注于业务,同时可以最大限度的避免封装不完善带来的bug。  项目中使用非常广泛的一个小工具,没有依赖,非常干净好用。 官网地址:https://www.hutool.cn/  参考文档:https://www.hutool.cn/docs/#/ API文档:https://apidoc.gitee.com/dromara/hutool/  GITEE:https://gitee.com/dromara/hutool/ GITHUB:https://github.com/dromara/hutool/  视频介绍:https://player.bilibili.com/player.html?aid=710062843&bvid=BV1bQ4y1M7d9&cid=170377135&page=2  3.2 Hutool组件 3.1 HutoolExcel实现验证 Mave依赖配置  <dependency>     <groupId>cn.hutool</groupId>     <artifactId>hutool-all</artifactId>     <version>5.7.18</version> </dependency> 代码实现验证  package com.zrj.easyexcel.excel;  import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.poi.excel.ExcelReader; import cn.hutool.poi.excel.ExcelUtil; import cn.hutool.poi.excel.ExcelWriter; import com.zrj.easyexcel.utils.FileUtils; import org.junit.Test;  import java.io.ByteArrayOutputStream; import java.io.File; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map;  /**  * HutoolExcel测试类  * 注意:不要到错包了,easyexcel与hutool的类名一样!!!  *  * @author zrj  * @since 2021/12/29  **/ public class EasyExcelHutool {      /*******************************Excel读取-ExcelReader*******************************************/      /**      * 1. 读取Excel中所有行和列,都用列表表示      */     @Test     public void readExcelObjectTest() {         //定义文件路径         String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeBeanKeyTest.xlsx";         //通过工具类创建ExcelReader         ExcelReader reader = ExcelUtil.getReader(filepath);         List<List<Object>> readAll = reader.read();         System.out.println("Excel读取结果:" + readAll);     }      /**      * 2. 读取为Map列表,默认第一行为标题行,Map中的key为标题,value为标题对应的单元格值。      */     @Test     public void readExcelMapTest() {         //定义文件路径         String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeBeanKeyTest.xlsx";         //通过工具类创建ExcelReader         ExcelReader reader = ExcelUtil.getReader(filepath);         List<Map<String, Object>> readAll = reader.readAll();         System.out.println("Excel读取结果:" + readAll);     }      /**      * 3. 读取为Bean列表,Bean中的字段名为标题,字段值为标题对应的单元格值。      */     @Test     public void readExcelBeanTest() {         //定义文件路径         String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeBeanKeyTest.xlsx";         //通过工具类创建ExcelReader         ExcelReader reader = ExcelUtil.getReader(filepath);         List<UserBean> readAll = reader.readAll(UserBean.class);         System.out.println("Excel读取结果:" + readAll);     }      /*******************************Excel生成-ExcelWriter*******************************************/      /**      * 1. 将行列对象写出到Excel      */     @Test     public void writeExcelObjectTest() {         //定义一个嵌套的List,List的元素也是一个List,内层的一个List代表一行数据,每行都有4个单元格,最终list对象代表多行数据。         List<String> row1 = CollUtil.newArrayList("aa", "bb", "cc", "dd");         List<String> row2 = CollUtil.newArrayList("aa1", "bb1", "cc1", "dd1");         List<String> row3 = CollUtil.newArrayList("aa2", "bb2", "cc2", "dd2");         List<String> row4 = CollUtil.newArrayList("aa3", "bb3", "cc3", "dd3");         List<String> row5 = CollUtil.newArrayList("aa4", "bb4", "cc4", "dd4");          List<List<String>> rows = CollUtil.newArrayList(row1, row2, row3, row4, row5);          //定义文件路径         String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeTest.xlsx";         //通过工具类创建writer         ExcelWriter writer = ExcelUtil.getWriter(filepath);         //通过构造方法创建writer         //ExcelWriter writer = new ExcelWriter("d:/writeTest.xls");          //跳过当前行,既第一行,非必须,在此演示用         writer.passCurrentRow();          //合并单元格后的标题行,使用默认标题样式         writer.merge(row1.size() - 1, "测试标题");         //一次性写出内容,强制输出标题         writer.write(rows, true);         //关闭writer,释放内存         writer.close();         System.out.println("创建完成,文件路径:" + filepath);     }      /**      * 2. 写出Map数据      */     @Test     public void writeExcelMapTest() {         Map<String, Object> row1 = new LinkedHashMap<>();         row1.put("姓名", "张三");         row1.put("年龄", 23);         row1.put("成绩", 88.32);         row1.put("是否合格", true);         row1.put("考试日期", DateUtil.date());          Map<String, Object> row2 = new LinkedHashMap<>();         row2.put("姓名", "李四");         row2.put("年龄", 33);         row2.put("成绩", 59.50);         row2.put("是否合格", false);         row2.put("考试日期", DateUtil.date());          ArrayList<Map<String, Object>> rows = CollUtil.newArrayList(row1, row2);          //定义文件路径         String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeMapTest.xlsx";         // 通过工具类创建writer         ExcelWriter writer = ExcelUtil.getWriter(filepath);         // 合并单元格后的标题行,使用默认标题样式         writer.merge(row1.size() - 1, "一班成绩单");         // 一次性写出内容,使用默认样式,强制输出标题         writer.write(rows, true);         // 关闭writer,释放内存         writer.close();         System.out.println("创建完成,文件路径:" + filepath);     }      /**      * 3. 写出Bean数据      */     @Test     public void writeExcelBeanTest() {         UserBean bean1 = UserBean.builder().name("张三").age(22).isPass(true).score(66.30).examDate(DateUtil.date()).build();         UserBean bean2 = UserBean.builder().name("李四").age(30).isPass(false).score(38.50).examDate(DateUtil.date()).build();          List<UserBean> rows = CollUtil.newArrayList(bean1, bean2);          //定义文件路径         String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeBeanTest.xlsx";         // 通过工具类创建writer         ExcelWriter writer = ExcelUtil.getWriter(filepath);         // 合并单元格后的标题行,使用默认标题样式         writer.merge(4, "一班成绩单");         // 一次性写出内容,使用默认样式,强制输出标题         writer.write(rows, true);         // 关闭writer,释放内存         writer.close();         System.out.println("创建完成,文件路径:" + filepath);     }      /**      * 4. 自定义Bean的key别名(排序标题)      * 注意:与3的区别是,3中标题是英文,4中标题是自定义的别名      */     @Test     public void writeExcelBeanKeyTest() {         UserBean bean1 = UserBean.builder().name("张三").age(22).isPass(true).score(66.30).examDate(DateUtil.date()).build();         UserBean bean2 = UserBean.builder().name("李四").age(30).isPass(false).score(38.50).examDate(DateUtil.date()).build();          List<UserBean> rows = CollUtil.newArrayList(bean1, bean2);          //定义文件路径         String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeBeanKeyTest.xlsx";         // 通过工具类创建writer         ExcelWriter writer = ExcelUtil.getWriter(filepath);          //自定义标题别名         writer.addHeaderAlias("name", "姓名");         writer.addHeaderAlias("age", "年龄");         writer.addHeaderAlias("score", "分数");         writer.addHeaderAlias("isPass", "是否通过");         writer.addHeaderAlias("examDate", "考试时间");          // 默认的,未添加alias的属性也会写出,如果想只写出加了别名的字段,可以调用此方法排除之         writer.setOnlyAlias(true);          // 合并单元格后的标题行,使用默认标题样式         writer.merge(4, "一班成绩单");         // 一次性写出内容,使用默认样式,强制输出标题         writer.write(rows, true);         // 关闭writer,释放内存         writer.close();         System.out.println("创建完成,文件路径:" + filepath);     }      /**      * 5. 写出到流      */     @Test     public void writeExcelStreamTest() {         //定义内容         UserBean bean1 = UserBean.builder().name("张三").age(22).isPass(true).score(66.30).examDate(DateUtil.date()).build();         UserBean bean2 = UserBean.builder().name("李四").age(30).isPass(false).score(38.50).examDate(DateUtil.date()).build();         List<UserBean> rows = CollUtil.newArrayList(bean1, bean2);          //定义输出流         ByteArrayOutputStream out = FileUtils.cloneInputStream(IoUtil.toUtf8Stream(rows.toString()));          //定义文件路径         String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "writeStreamTest.xlsx";         if (!FileUtil.isFile(filepath)) {             FileUtil.touch(filepath);         }         //通过工具类创建writer         ExcelWriter writer = ExcelUtil.getWriter();         //创建xlsx格式的         //ExcelWriter writer = ExcelUtil.getWriter(true);         //一次性写出内容,使用默认样式,强制输出标题         writer.write(rows, true);         //out为OutputStream,需要写出到的目标流         writer.flush(out);         //关闭writer,释放内存         writer.close();         System.out.println("创建完成,文件路径:" + filepath);     }      /**      * 6. 写出到客户端下载(写出到Servlet)      */     @Test     public void writeExcelDownloadTest() {         //定义内容         UserBean bean1 = UserBean.builder().name("张三").age(22).isPass(true).score(66.30).examDate(DateUtil.date()).build();         UserBean bean2 = UserBean.builder().name("李四").age(30).isPass(false).score(38.50).examDate(DateUtil.date()).build();         List<UserBean> rows = CollUtil.newArrayList(bean1, bean2);          // 通过工具类创建writer,默认创建xls格式         ExcelWriter writer = ExcelUtil.getWriter();         // 一次性写出内容,使用默认样式,强制输出标题         writer.write(rows, true);         //out为OutputStream,需要写出到的目标流          //response为HttpServletResponse对象         //response.setContentType("application/vnd.ms-excel;charset=utf-8");         //test.xls是弹出下载对话框的文件名,不能为中文,中文请自行编码         //response.setHeader("Content-Disposition", "attachment;filename=test.xls");         //ServletOutputStream out = response.getOutputStream();          //writer.flush(out, true);         //关闭writer,释放内存         writer.close();         //此处记得关闭输出Servlet流         //IoUtil.close(out);     }  } 4. EasyExcel 4.1 EasyExcel简介 EasyExcel是阿里巴巴开源的一款Excel操作工具,简单易用,功能强大,节省内存,性能彪悍。  语雀地址:https://www.yuque.com/easyexcel/doc/easyexcel Github地址: https://github.com/alibaba/easyexcel mvn依赖地址:https://mvnrepository.com/artifact/com.alibaba/easyexcel  4.2 HutoolExcel实现验证 Mave依赖配置          <dependency>             <groupId>com.alibaba</groupId>             <artifactId>easyexcel</artifactId>             <version>3.0.5</version>         </dependency> EasyExcel基本实现案例 Github案例地址:https://github.com/alibaba/easyexcel 项目路径:test -> read/write -> ReadTest与WriteTest类,实现基本Excel各种读写功能。  EasyExcel批量添加批注 ByteToInputStreamUtil  package com.zrj.easyexcel.excel;  import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.InputStream;  /**  * 输入流与字节转换  *  * @author zrj  * @since 2021/12/27  **/ public class ByteToInputStreamUtil {     /**      * 字节转输入流      *      * @param buf      * @return java.io.InputStream      */     public static InputStream byte2Input(byte[] buf) {         return new ByteArrayInputStream(buf);     }      /**      * 输入流转字节      *      * @param inStream      * @return byte[]      */     public static byte[] input2byte(InputStream inStream) throws Exception {         ByteArrayOutputStream swapStream = new ByteArrayOutputStream();         byte[] buff = new byte[100];         int rc = 0;         while ((rc = inStream.read(buff, 0, 100)) > 0) {             swapStream.write(buff, 0, rc);         }         byte[] in2b = swapStream.toByteArray();         return in2b;     } } CommentWriteHandler  package com.zrj.easyexcel.excel;  import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.excel.write.handler.AbstractRowWriteHandler; import com.alibaba.excel.write.metadata.holder.WriteSheetHolder; import com.alibaba.excel.write.metadata.holder.WriteTableHolder; import org.apache.poi.hssf.usermodel.HSSFClientAnchor; import org.apache.poi.hssf.usermodel.HSSFRichTextString; import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFClientAnchor; import org.apache.poi.xssf.usermodel.XSSFRichTextString;  import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors;  /**  * 批注处理器  *  * @author zrj  * @since 2021/12/29  **/ public class CommentWriteHandler extends AbstractRowWriteHandler {      /**      * sheet名称KEY      */     public static final String SHEETNAME_NAME = "sheetName";      /**      * 文档后缀名      */     private String extension;      /**      * 列索引key      */     public static final String COLINDEX_NAME = "colIndex";      /**      * 行索引key      */     public static final String ROWINDEX_NAME = "rowIndex";      /**      * 批注内容key      */     public static final String COMMENTCONTENT_NAME = "commentContent";      /**      * sheet页名称列表      */     private List<String> sheetNameList;      /**      * 批注集合      */     List<Map<String, String>> commentList = new ArrayList<>(10);      /**      * CommentWriteHandler      *      * @param commentList      * @param extension      */     public CommentWriteHandler(List<Map<String, String>> commentList, String extension) {         this.commentList = commentList != null && commentList.size() > 0                 ? commentList.stream().filter(x ->                 x.keySet().contains(SHEETNAME_NAME) == true && x.get(SHEETNAME_NAME) != null && StrUtil.isNotBlank(x.get(SHEETNAME_NAME).toString())                         && x.keySet().contains(COLINDEX_NAME) == true && x.get(COLINDEX_NAME) != null && StrUtil.isNotBlank(x.get(COLINDEX_NAME).toString())                         && x.keySet().contains(ROWINDEX_NAME) == true && x.get(ROWINDEX_NAME) != null && StrUtil.isNotBlank(x.get(ROWINDEX_NAME).toString())                         && x.keySet().contains(COMMENTCONTENT_NAME) == true && x.get(COMMENTCONTENT_NAME) != null && StrUtil.isNotBlank(x.get(COMMENTCONTENT_NAME).toString())         ).collect(Collectors.toList()) : new ArrayList<>();         sheetNameList = this.commentList.stream().map(x -> x.get(SHEETNAME_NAME).toString()).collect(Collectors.toList());         this.extension = extension;     }      /**      * 生成批注信息      *      * @param sheetName      sheet页名称      * @param rowIndex       行号      * @param columnIndex    列号      * @param commentContent 批注内容      * @return      */     public static Map<String, String> createCommentMap(String sheetName, int rowIndex, int columnIndex, String commentContent) {         Map<String, String> map = new HashMap<>();         //sheet页名称         map.put(SHEETNAME_NAME, sheetName);         //行号         map.put(ROWINDEX_NAME, rowIndex + "");         //列号         map.put(COLINDEX_NAME, columnIndex + "");         //批注内容         map.put(COMMENTCONTENT_NAME, commentContent);         return map;     }      /**      * 功能描述      * @param writeSheetHolder      * @param writeTableHolder      * @param row      * @param relativeRowIndex      * @param isHead      * @return void      */     @Override     public void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,                                 Integer relativeRowIndex, Boolean isHead) {         Sheet sheet = writeSheetHolder.getSheet();         //不需要添加批注,或者当前sheet页不需要添加批注         if (commentList == null || commentList.size() <= 0 || sheetNameList.contains(sheet.getSheetName()) == false) {             return;         }         //获取当前行的批注信息         List<Map<String, String>> rowCommentList = commentList.stream().filter(x ->                 StrUtil.equals(x.get(SHEETNAME_NAME).toString(), sheet.getSheetName())                         && relativeRowIndex == Integer.parseInt(x.get(ROWINDEX_NAME))).collect(Collectors.toList());         //当前行没有批注信息         if (rowCommentList == null || rowCommentList.size() <= 0) {             return;         }         List<String> colIndexList = rowCommentList.stream().map(x -> x.get(COLINDEX_NAME)).distinct().collect(Collectors.toList());         for (String colIndex : colIndexList) {             //同一单元格的批注信息             List<Map<String, String>> cellCommentList = rowCommentList.stream().filter(x ->                     StrUtil.equals(colIndex, x.get(COLINDEX_NAME))).collect(Collectors.toList());             if (CollectionUtil.isEmpty(cellCommentList)) {                 continue;             }             //批注内容拼成一条             String commentContent = cellCommentList.stream().map(x -> x.get(COMMENTCONTENT_NAME)).collect(Collectors.joining());             Cell cell = row.getCell(Integer.parseInt(colIndex));             addComment(cell, commentContent, extension);         }         //删除批注信息         commentList.remove(rowCommentList);         //重新获取要添加的sheet页姓名         sheetNameList = commentList.stream().map(x -> x.get(SHEETNAME_NAME).toString()).collect(Collectors.toList());     }      /**      * 给Cell添加批注      *      * @param cell      单元格      * @param value     批注内容      * @param extension 扩展名      */     public static void addComment(Cell cell, String value, String extension) {         Sheet sheet = cell.getSheet();         cell.removeCellComment();         if ("xls".equals(extension)) {             ClientAnchor anchor = new HSSFClientAnchor();             // 关键修改             anchor.setDx1(0);             anchor.setDx2(0);             anchor.setDy1(0);             anchor.setDy2(0);             anchor.setCol1(cell.getColumnIndex());             anchor.setRow1(cell.getRowIndex());             anchor.setCol2(cell.getColumnIndex() + 5);             anchor.setRow2(cell.getRowIndex() + 6);             // 结束             Drawing drawing = sheet.createDrawingPatriarch();             Comment comment = drawing.createCellComment(anchor);             // 输入批注信息             comment.setString(new HSSFRichTextString(value));             // 将批注添加到单元格对象中             cell.setCellComment(comment);         } else if ("xlsx".equals(extension)) {             ClientAnchor anchor = new XSSFClientAnchor();             // 关键修改             anchor.setDx1(0);             anchor.setDx2(0);             anchor.setDy1(0);             anchor.setDy2(0);             anchor.setCol1(cell.getColumnIndex());             anchor.setRow1(cell.getRowIndex());             anchor.setCol2(cell.getColumnIndex() + 5);             anchor.setRow2(cell.getRowIndex() + 6);             // 结束             Drawing drawing = sheet.createDrawingPatriarch();             Comment comment = drawing.createCellComment(anchor);             // 输入批注信息             comment.setString(new XSSFRichTextString(value));             // 将批注添加到单元格对象中             cell.setCellComment(comment);         }     } } EasyExcelUtils  package com.zrj.easyexcel.excel;  import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.write.builder.ExcelWriterBuilder; import com.alibaba.excel.write.metadata.WriteSheet; import com.zrj.easyexcel.entity.DemoData; import com.zrj.easyexcel.excel.read.ReadTest; import com.zrj.easyexcel.excel.write.WriteTest; import org.junit.Test;  import java.io.File; import java.util.*;  /**  * EasyExcel测试类  *  * @author zrj  * @since 2021/12/29  **/ public class EasyExcelUtils {     /**      * EasyExcel读写测试类      */     @Test     public void easyExcelReadWriteTest() {         //EasyExcel读测试类         ReadTest readTest = new ReadTest();         //EasyExcel写测试类         WriteTest writeTest = new WriteTest();     }     /**      * EasyExcel 批量添加批注      */     @Test     public void batchAddCommentTest() {         try {             //构建数据             List<DemoData> demoDataList = new ArrayList<>(10);             demoDataList.add(DemoData.builder().string("张三").date(new Date()).doubleData(3.14).build());             demoDataList.add(DemoData.builder().string("王五").date(new Date()).doubleData(6.68).build());             demoDataList.add(DemoData.builder().string("赵六").date(new Date()).doubleData(8.32).build());             demoDataList.add(DemoData.builder().string("李四").date(new Date()).doubleData(8.66).build());              //定义文件路径             String filepath = System.getProperty("user.dir") + File.separator + "excel" + File.separator + "EasyExcelCommentWriteTest3.xlsx";             String sheetName = "批注模板";             List<Map<String, String>> commentList = new ArrayList<>();             commentList.add(CommentWriteHandler.createCommentMap(sheetName, 0, 1, "第一条批注。"));             commentList.add(CommentWriteHandler.createCommentMap(sheetName, 1, 1, "第二条批注。"));              // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭             // 这里要注意inMemory 要设置为true,才能支持批注。目前没有好的办法解决 不在内存处理批注。这个需要自己选择。             EasyExcel.write(filepath, DemoData.class).inMemory(Boolean.TRUE)                     .registerWriteHandler(new CommentWriteHandler(commentList, "xlsx"))                     .sheet(sheetName).doWrite(demoDataList);              System.out.println("批注模板完成,模板地址:" + filepath);         } catch (Exception e) {             e.printStackTrace();         }     } } 5. 源码地址 测试源码地址:https://gitee.com/rjzhu/opencode/tree/master/easyexcel ———————————————— 原文链接:https://blog.csdn.net/m0_37583655/article/details/122233640 
总条数:1643 到第
上滑加载中