POI与easyExcel

这个东西一般用来做什么?

  • 将用户信息导出为Excel表格(导出数据)
  • 将Excel表中的信息录入到网站数据库(比如一些习题上传)
  • 在开发过程中会遇到对Excel的处理,比如导出Excel,导入Excel到数据库中..
  • 操作Excel目前比较流行的就是Apache POI 和阿里巴巴的 easyExcle

Apache POI

Apache POI 官网:https://poi.apache.org/

easyExcel

easyExcel 官网:https://github.com/alibaba/easyexcel

easyExcel 是阿里巴巴开源的一个excel处理框架,以使用简单,节省内存著称。

可以看一下两者的对比

POI-Excel写

创建项目

  • 建立一个空的maven项目方便导入依赖直接测试或使用
  • 导入pom依赖
<!--导入依赖-->
<dependencies>
<!--xls(03)-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.9</version>
</dependency>
<!--xls(07)-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.9</version>
</dependency>
<!--日期格式化工具-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.1</version>
</dependency>
<!--Test-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>

03 | 07版本的写,就是对象不同,方法一样!

需要注意的是:2003版本和2007版本存在兼容性的问题!03最多只有65536行!

1、工作簿 --> 2、工作表 --> 3、行 -->4、列

03版本!

public class ExcelWriteTest {

    String PATH ="D:/MyNewWorld/ExcelTest/kuang-poi";

    @Test
public void testWrite03() throws IOException {
//1、创建一个工作簿
Workbook workbook = new HSSFWorkbook();
//2、创建一个工作表
Sheet sheet = workbook.createSheet("论坛用户统计表");
//3、创建行 (1,1)
Row row1 = sheet.createRow(0);
//4、创建单元格
Cell cell11 = row1.createCell(0);
cell11.setCellValue("今日新增用户");
//(1,2)
Cell cell12 = row1.createCell(1);
cell12.setCellValue(999); //第二行(2,1)
Row row2 = sheet.createRow(1);
Cell cell21 = row2.createCell(0);
cell21.setCellValue("统计日期");
//(2,2)
Cell cell22 = row2.createCell(1);
String time = new DateTime().toString("yyyy-MM-ss HH:mm:ss");
cell22.setCellValue(time); //生成一张表(IO 流) 03 版本就是使用.xls结尾!
FileOutputStream outputStream = new FileOutputStream(PATH + "论坛用户统计表03.xls");
//输出
workbook.write(outputStream);
//关闭流
outputStream.close(); System.out.println("over!");
}
}

07版本!

 @Test
public void testWrite07() throws IOException {
//1、创建一个工作簿 07版本的!
Workbook workbook = new XSSFWorkbook();
//2、创建一个工作表
Sheet sheet = workbook.createSheet("论坛用户统计表");
//3、创建行 (1,1)
Row row1 = sheet.createRow(0);
//4、创建单元格
Cell cell11 = row1.createCell(0);
cell11.setCellValue("今日新增用户");
//(1,2)
Cell cell12 = row1.createCell(1);
cell12.setCellValue(999); //第二行(2,1)
Row row2 = sheet.createRow(1);
Cell cell21 = row2.createCell(0);
cell21.setCellValue("统计日期");
//(2,2)
Cell cell22 = row2.createCell(1);
String time = new DateTime().toString("yyyy-MM-ss HH:mm:ss");
cell22.setCellValue(time); //生成一张表(IO 流) 03 版本就是使用.xlsx结尾!
FileOutputStream outputStream = new FileOutputStream(PATH + "论坛用户统计表07.xlsx");
//输出
workbook.write(outputStream);
//关闭流
outputStream.close(); System.out.println("over!");
}

注意的是对象的区别与文件后缀的区别!

大文件写HSSF 03

  • 缺点:最多只能处理65536行!否则会抛出异常!

java.lang.IllegalArgumentException: Invalid row number(65536) outside allowable range (0..65535)

  • 优点:过程中写入缓存;不操作磁盘,最后一次一次性写入磁盘,速度快!
  @Test
public void testWrite03BigData() 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 cellNum = 0; cellNum < 10; cellNum++) {
Cell cell = row.createCell(cellNum);
cell.setCellValue(cellNum);
}
}
System.out.println("over-->" + "testWrite03BigData");
FileOutputStream outputStream = new FileOutputStream(PATH + "testWrite03BigData.xls");
workbook.write(outputStream);
outputStream.close();
long end = System.currentTimeMillis();
System.out.println((double) (end-begin)/1000); //按秒来计算
}

大文件写XSSF 07

  • 缺点:写数据时速度非常慢,非常耗内存;也会发生内存溢出;比如100W条数据
  • 优点:可以写较大的数据量,比如20W条数据
// 耗时较长! 怎样优化 --> 可以通过缓存
@Test
public void testWrite07BigData() throws Exception {
//计算时间差
long begin = System.currentTimeMillis(); //创建一个簿
Workbook workbook = new XSSFWorkbook();
//创建表
Sheet sheet = workbook.createSheet();
//写入数据
for (int rowNumber = 0; rowNumber < 100000; rowNumber++) {
Row row = sheet.createRow(rowNumber);
for (int cellNum = 0; cellNum < 10; cellNum++) {
Cell cell = row.createCell(cellNum);
cell.setCellValue(cellNum);
}
}
System.out.println("over-->" + "testWrite07BigData");
FileOutputStream outputStream = new FileOutputStream(PATH + "testWrite07BigData.xlsx");
workbook.write(outputStream);
outputStream.close();
long end = System.currentTimeMillis();
System.out.println((double) (end-begin)/1000); //按秒来计算
}

大文件写SXSSF

优点:可以写非常大的数据量,如100W条数据甚至更多!写的数据速度快!占用更少的内存!

注意的点是:

  • 过程中仍然会产生临时文件,需要清理这些文件!
  • 默认由100条记录保存在内存中,如果超过这数量,则前面的数据被写入临时文件;
  • 如果想要自定义内存中数的数量,可以使用new SXSSFWorkbook(数据量);
  @Test
public void testWrite07BigDataS() 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 cellNum = 0; cellNum < 10; cellNum++) {
Cell cell = row.createCell(cellNum);
cell.setCellValue(cellNum);
}
}
System.out.println("over-->" + "testWrite07BigDataS");
FileOutputStream outputStream = new FileOutputStream(PATH + "testWrite07BigDataS.xlsx");
workbook.write(outputStream);
outputStream.close();
//清除临时文件!
((SXSSFWorkbook) workbook).dispose();
long end = System.currentTimeMillis();
System.out.println((double) (end-begin)/1000); //按秒来计算
}

但SXSSFWorkbook可能仍然会消耗大量的内存,这些内存基于您正在使用的功能,例如合并区域、注释..仍然只存储在内存中,因此如果广泛使用,可能需要大量的内存。

POI-Execl读

03/07

03版本

public class ExcelReadTest {

    String PATH ="D:/MyNewWorld/ExcelTest/kuang-poi/";

    @Test
public void testRead03() throws IOException { //获取文件流
FileInputStream fileInputStream = new FileInputStream(PATH + "kuang-poi论坛用户统计表.xls");
//1、创建一个工作簿;使用Excel能操作的这个对象也是可以操作的!
Workbook workbook = new HSSFWorkbook(fileInputStream);
//2、得到表
Sheet sheet = workbook.getSheetAt(0);
//3、得到行
Row row = sheet.getRow(0);
//4、得到列
Cell cell = row.getCell(1);
//读取值的话,一定要注意类型问题!
//getStringCellValue 字符串类型
System.out.println(cell.getNumericCellValue());
}
}

07版本

 @Test
public void testRead07() throws IOException { //获取文件流
FileInputStream fileInputStream = new FileInputStream(PATH + "kuang-poi论坛用户统计表07.xlsx");
//1、创建一个工作簿;使用Excel能操作的这个对象也是可以操作的!
Workbook workbook = new XSSFWorkbook(fileInputStream);
//2、得到表
Sheet sheet = workbook.getSheetAt(0);
//3、得到行
Row row = sheet.getRow(0);
//4、得到列
Cell cell = row.getCell(1);
//读取值的话,一定要注意类型问题!
//getStringCellValue 字符串类型
System.out.println(cell.getNumericCellValue());
}

注意获取的类型

获取不同的数据类型

@Test
public void testCellType() throws IOException {
//获取文件流
FileInputStream fileInputStream = new FileInputStream(PATH + "明细表.xls"); //1、创建一个工作簿;使用Excel能操作的这个对象也是可以操作的!
Workbook workbook = new HSSFWorkbook(fileInputStream); Sheet sheet = workbook.getSheetAt(0);
// 获取标题的内容
Row rowTitle = sheet.getRow(0);
if(rowTitle != null){
// 一定要掌握
int cellCount = rowTitle.getPhysicalNumberOfCells();
for (int cellNum = 0; cellNum < cellCount; cellNum++) {
Cell cell = rowTitle.getCell(cellNum);
if(cell != null){
int cellType = cell.getCellType();
String cellValue = cell.getStringCellValue();
System.out.print(cellValue + " | ");
}
}
System.out.println();
} //获取表中的内容
int rowCount = sheet.getPhysicalNumberOfRows();
for (int rowNum = 0; rowNum < rowCount; rowNum++) {
Row rowData = sheet.getRow(rowNum);
if(rowData != null){
//读取列
int cellCount = rowTitle.getPhysicalNumberOfCells();
for (int cellNum = 0; cellNum < cellCount; cellNum++) {
System.out.print("[" + (rowNum + 1) + "-" + (cellNum+1) + "]"); Cell cell = rowData.getCell(cellNum); //匹配列的数据类型
if(cell != null){
int cellType = cell.getCellType();
String cellValue = ""; switch (cellType){
case HSSFCell.CELL_TYPE_STRING: //字符串
System.out.print("[String]");
cellValue = cell.getStringCellValue();
break;
case HSSFCell.CELL_TYPE_BOOLEAN: //布尔值
System.out.print("[BOOLEAN]");
cellValue = String.valueOf(cell.getBooleanCellValue());
break;
case HSSFCell.CELL_TYPE_BLANK: //如果为空
System.out.print("[BLANK]");
break;
case HSSFCell.CELL_TYPE_NUMERIC: // 数字(日期或普通的数字)
System.out.print("[NUMERIC]");
if(HSSFDateUtil.isCellDateFormatted(cell)){ //日期
System.out.print("[日期]");
Date date = cell.getDateCellValue();
cellValue = new DateTime(date).toString("yyyy-MM-dd");
}else{
// 如果不是日期格式,防止数字过长!
System.out.print("[转换为字符串输出]");
cell.setCellValue(HSSFCell.CELL_TYPE_STRING);
cellValue = cell.toString();
}
break;
case HSSFCell.CELL_TYPE_ERROR:
System.out.print("[数据类型错误]");
break;
}
System.out.println(cellValue);
}
}
}
}
fileInputStream.close();
}

注意类型转换的一个问题

也可以把上面这段代码提取为工具类:

 @Test
public void testCellType(FileInputStream inputStream) throws IOException {
//获取文件流 //1、创建一个工作簿;使用Excel能操作的这个对象也是可以操作的!
Workbook workbook = new HSSFWorkbook(inputStream); Sheet sheet = workbook.getSheetAt(0);
// 获取标题的内容
Row rowTitle = sheet.getRow(0);
if(rowTitle != null){
// 一定要掌握
int cellCount = rowTitle.getPhysicalNumberOfCells();
for (int cellNum = 0; cellNum < cellCount; cellNum++) {
Cell cell = rowTitle.getCell(cellNum);
if(cell != null){
int cellType = cell.getCellType();
String cellValue = cell.getStringCellValue();
System.out.print(cellValue + " | ");
}
}
System.out.println();
} //获取表中的内容
int rowCount = sheet.getPhysicalNumberOfRows();
for (int rowNum = 0; rowNum < rowCount; rowNum++) {
Row rowData = sheet.getRow(rowNum);
if(rowData != null){
//读取列
int cellCount = rowTitle.getPhysicalNumberOfCells();
for (int cellNum = 0; cellNum < cellCount; cellNum++) {
System.out.print("[" + (rowNum + 1) + "-" + (cellNum+1) + "]"); Cell cell = rowData.getCell(cellNum); //匹配列的数据类型
if(cell != null){
int cellType = cell.getCellType();
String cellValue = ""; switch (cellType){
case HSSFCell.CELL_TYPE_STRING: //字符串
System.out.print("[String]");
cellValue = cell.getStringCellValue();
break;
case HSSFCell.CELL_TYPE_BOOLEAN: //布尔值
System.out.print("[BOOLEAN]");
cellValue = String.valueOf(cell.getBooleanCellValue());
break;
case HSSFCell.CELL_TYPE_BLANK: //如果为空
System.out.print("[BLANK]");
break;
case HSSFCell.CELL_TYPE_NUMERIC: // 数字(日期或普通的数字)
System.out.print("[NUMERIC]");
if(HSSFDateUtil.isCellDateFormatted(cell)){ //日期
System.out.print("[日期]");
Date date = cell.getDateCellValue();
cellValue = new DateTime(date).toString("yyyy-MM-dd");
}else{
// 如果不是日期格式,防止数字过长!
System.out.print("[转换为字符串输出]");
cell.setCellValue(HSSFCell.CELL_TYPE_STRING);
cellValue = cell.toString();
}
break;
case HSSFCell.CELL_TYPE_ERROR:
System.out.print("[数据类型错误]");
break;
}
System.out.println(cellValue);
}
}
}
}
inputStream.close();
}

公式计算

 @Test
public void testFormula() throws Exception {
FileInputStream inputStream = new FileInputStream(PATH + "公式.xls");
Workbook workbook = new HSSFWorkbook(inputStream);
Sheet sheet = workbook.getSheetAt(0);
Row row = sheet.getRow(4);
Cell cell = row.getCell(0); // 拿到计算公式 eavl
FormulaEvaluator formulaEvaluator = new HSSFFormulaEvaluator((HSSFWorkbook) workbook); //输出单元格的内容
int cellType = cell.getCellType();
switch (cellType){
case Cell.CELL_TYPE_FORMULA: //公式
String formula = cell.getCellFormula();
System.out.println(formula); //计算
CellValue evaluate = formulaEvaluator.evaluate(cell);
String cellValue = evaluate.formatAsString();
System.out.println(cellValue);
break;
}
}

EsayExcel

导入依赖

 <dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.6</version>
</dependency>

写入测试

public class DemoData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore; public String getString() {
return string;
} public void setString(String string) {
this.string = string;
} public Date getDate() {
return date;
} public void setDate(Date date) {
this.date = date;
} public Double getDoubleData() {
return doubleData;
} public void setDoubleData(Double doubleData) {
this.doubleData = doubleData;
} public String getIgnore() {
return ignore;
} public void setIgnore(String ignore) {
this.ignore = ignore;
} @Override
public String toString() {
return "DemoData{" +
"string='" + string + '\'' +
", date=" + date +
", doubleData=" + doubleData +
", ignore='" + ignore + '\'' +
'}';
}
}
 public class EasyTest {

    String PATH ="D:/MyNewWorld/ExcelTest/kuang-poi/";

    private List<DemoData> data() {
List<DemoData> list = new ArrayList<DemoData>();
for (int i = 0; i < 10; i++) {
DemoData data = new DemoData();
data.setString("字符串" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
return list;
} //根据list 写入Excel /**
* 最简单的写
* <p>1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>2. 直接写即可
*/
@Test
public void simpleWrite() {
// 写法
String fileName = PATH + "EasyTest.xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
//write(fileName,格式类)
//sheet(表名)
//doWrite(数据)
//data()(一般是从前端读取的数据或从数据库拿的数据)
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
}
}

读取测试

// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class DemoDataListener extends AnalysisEventListener<DemoData> {
private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class);
/**
* 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 5;
List<DemoData> list = new ArrayList<DemoData>();
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private DemoDAO demoDAO;
public DemoDataListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
demoDAO = new DemoDAO();
}
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param demoDAO
*/
public DemoDataListener(DemoDAO demoDAO) {
this.demoDAO = demoDAO;
}
/**
* 这个每一条数据解析都会来调用
*
* @param data
* one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
//读取数据的时候会执行 invoke 方法
// DemoData 类型
// AnalysisContext分析上下文
@Override
public void invoke(DemoData data, AnalysisContext context) {
System.out.println(JSON.toJSONString(data));
list.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData(); //持久化逻辑
// 存储完成清理 list
list.clear();
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
LOGGER.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
LOGGER.info("{}条数据,开始存储数据库!", list.size());
demoDAO.save(list);
LOGGER.info("存储数据库成功!");
}
}
public class EasyTest {

    String PATH ="D:/MyNewWorld/ExcelTest/kuang-poi/";

    /**
* 最简单的读
* <p>1. 创建excel对应的实体对象 参照{@link DemoData}
* <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
* <p>3. 直接读即可
*/
@Test
public void simpleRead() {
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
// 写法1:
String fileName = PATH + "EasyTest.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭 //重点注意读取的逻辑
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
}
}

可以参考github的官方文档

https://www.yuque.com/easyexcel/doc/easyexcel

固定套路:

1、写入、固定类格式进行写入

2、读取、根据监听器的设置的规则进行读取

总结一下:

了解面向对象的思想,学会面向接口编程!

理解使用测试API

POI和easyExcel的更多相关文章

  1. Java实现导入导出Excel:POI和EasyExcel

    文章与CSDN同步,欢迎访问:https://blog.csdn.net/qq_40280582/article/details/107300081 代码地址:https://gitee.com/il ...

  2. POI和EasyExcel的使用

    1.POI使用 1.1 什么是POI POI简介(Apache POI),Apache POI是Apache软件基金会的开放源码函式库,POI提供API给Java程序对Microsoft Office ...

  3. EasyExcel的基本使用方法

    在Java语言领域,说到Excel处理工具,大家首先想到的可能是阿帕奇的poi,poi在处理数据量不大的excel文件上确实非常强大,但是随着后来excel从03(一个excel文件中最多有65536 ...

  4. EasyExcel完成excel文件的导入导出

    简介 常见的Excel分析框架有Apache poi 和EasyExcel, poi在另一篇已经介绍过了,详情可以看[https://www.cnblogs.com/jasmine-e/p/16064 ...

  5. SpringBoot图文教程10—模板导出|百万数据Excel导出|图片导出「easypoi」

    有天上飞的概念,就要有落地的实现 概念十遍不如代码一遍,朋友,希望你把文中所有的代码案例都敲一遍 先赞后看,养成习惯 SpringBoot 图文教程系列文章目录 SpringBoot图文教程1「概念+ ...

  6. 搭建SSM基础环境>基于idea

    目录 搭建SSM基础环境 创建一个Web项目 导入所需要的jar包 在项目目录下创建一个Resources文件夹并设置为类路径 在src目录下创建项目的初始文件夹目录 在resources文件夹下创建 ...

  7. easyExcel+poi导出Excel出现乱码

    这种问题肯定是浏览器编码问题,修改官方给的util就好了

  8. 阿里巴巴excel工具easyexcel 助你快速简单避免OOM

    Java解析.生成Excel比较有名的框架有Apache poi.jxl.但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有 ...

  9. easyExcel导出excel的简单使用

    easyExcel导出excel的简单使用 Java解析.生成Excel比较有名的框架有Apache poi.jxl.但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定 ...

随机推荐

  1. Mybatis动态语句

    If元素If元素是简单的条件判断逻辑,满足制定条件时追加if元素的SQL,不满足条件时不追加,使用格式如下: <select ….> SQL语句1 <if test=“条件表达式”& ...

  2. 洛谷 P3951 NOIP 2017 小凯的疑惑

    洛谷 P3951 NOIP 2017 小凯的疑惑 题目描述 小凯手中有两种面值的金币,两种面值均为正整数且彼此互素.每种金币小凯都有 无数个.在不找零的情况下,仅凭这两种金币,有些物品他是无法准确支付 ...

  3. Java面试题(JVM篇)

    JVM 194.说一下 jvm 的主要组成部分?及其作用? 类加载器(ClassLoader) 运行时数据区(Runtime Data Area) 执行引擎(Execution Engine) 本地库 ...

  4. 【Pod Terminating原因追踪系列之三】让docker事件处理罢工的cancel状态码

    本篇为Pod Terminating原因追踪系列的第三篇,前两篇分别介绍了两种可能导致Pod Terminating的原因.在处理现网问题时,Pod Terminating属于比较常见的问题,而本系列 ...

  5. ssm简单整合

    pom.xml: <?xml version="1.0" encoding="UTF-8"?> <project xmlns="ht ...

  6. ARouter使用

    1. androidstudio3.0配置 javaCompileOptions { annotationProcessorOptions { arguments = [AROUTER_MODULE_ ...

  7. Unity3D如何有效地组织代码?

    本文整理自知乎,原文链接:http://www.zhihu.com/question/21070379 问题: Unity3D可以说是高度的Component-Based Architecture,同 ...

  8. 【C#】静态构造方法与静态变量

    扯下闲篇先,本来今天预计整理下委托.事件.Lamada的笔记,然后再把单例模式的懒汉.饿汉模式看完. 在看到懒汉的双重加锁设计时,向同桌贩卖了下该设计的优点,结果反被同桌的一个问题难倒了~! 一. 有 ...

  9. java初探(1)之登录再探

    https://www.cnblogs.com/lovejune/p/java_login_1.html 上一章内容搭建起了登录应用场景的环境,模拟实现了登录操作,页面与后端数据的交互过程,使用的是异 ...

  10. uap设置gradle和jdk