文章目录
对Excel进行解析\生成\查询\计算等处理是Java下较常见的任务,但Excel的文件格式很复杂,自行编码读写太困难,有了POI\EasyExcel\JExcel等类库就方便多了,其中POI最为出色。
POI可读写多种Excel文件格式,既支持古老的二进制格式(xls),也支持现代的OOXML格式(xlsx),既支持全内存一次性读写,也支持小内存流式读写。POI为大量Excel元素设计了相应的JAVA类,包括workbook、printer、sheet、row、cell,其中,与cell相关的类包括单元格样式、字体、颜色、日期、对齐、边框等。仅单元格样式类,方法就超过了四十个,可进行最全面最细致的读写操作。
POI的读写功能全面而细致,但细致也意味着过于底层,开发者必须从头写起,自己处理每一处细节,即使简单的操作也要编写大量代码。比如,读入首行为列名的行式xls:
FileInputStream fileInputStream = new FileInputStream("d:\\Orders.xls");
// get the excel book
Workbook workbook = new HSSFWorkbook(fileInputStream);
if (workbook != null) {
// get the first sheet
Sheet sheet = workbook.getSheetAt(0);
if (sheet != null) {
//get the col name/first line
Row rowTitle = sheet.getRow(0); // first line
if (rowTitle != null) {
int cellTitles = rowTitle.getPhysicalNumberOfCells(); // get column number
for (int i = 0; i < cellTitles; i++) {
Cell cell = rowTitle.getCell(i); //the cell!
if (cell != null) {
System.out.print(cell.getStringCellValue() + " | ");
}
}
}
//get the value/other lines
int rows = sheet.getPhysicalNumberOfRows(); // get line number
for (int i = 1; i < rows; i++) {
Row row = sheet.getRow(i); // get row i
if (row != null) {
int cells = row.getPhysicalNumberOfCells(); // get column number
for (int j = 0; j < cells; j++) {
// line number and row number
System.out.print("[" + i + "-" + j + "]");
Cell cell = row.getCell(j); // the cell!
if (cell != null) {
int cellType = cell.getCellType();
Object value = "";
switch (cellType) {
case HSSFCell.CELL_TYPE_STRING: // string
value = cell.getStringCellValue();
break;
case HSSFCell.CELL_TYPE_BLANK: // 空
break;
case HSSFCell.CELL_TYPE_BOOLEAN: // boolean
value = cell.getBooleanCellValue();
break;
case HSSFCell.CELL_TYPE_NUMERIC: // number
if (HSSFDateUtil.isCellDateFormatted(cell)) { // date number
Date date = cell.getDateCellValue();
value = new DateTime(date).toString("yyyy-MM-dd HH:mm:ss");
}else { // normal number
// change to string to avoid being too long
cell.setCellType(HSSFCell.CELL_TYPE_STRING);
value = cell;
}
break;
case HSSFCell.CELL_TYPE_ERROR:
throw new RuntimeException("data type mistaken");
}
System.out.println(value);
}
}
}
System.out.println("end of the "+i+" line");
}
System.out.println("end of the value lines=======================================");
}
}
行式xls是最常见的格式,但POI并没有为此提供方便的处理方法,只能按照workbook->sheet->line->cell的顺序进行循环解析,造成了如此繁琐的代码。
这还只是将数据简单读出来,如果下一步想再处理数据,还要事先转为结构化数据对象,比如ArrayList<实体类>或HashMap,代码就更繁琐了。
解析Excel并不是目标,我们通常还要对这些文件进查询计算,但POI作为Excel的解析类,没有也不合适再提供相关的方法,只能用JAVA手工硬写。比如基础的分组汇总运算,JAVA代码大概这样:
Comparator<salesRecord> comparator = new Comparator<salesRecord>() {
public int compare(salesRecord s1, salesRecord s2) {
if (!s1.salesman.equals(s2.salesman)) {
return s1.salesman.compareTo(s2.salesman);
} else {
return s1.ID.compareTo(s2.ID);
}
}
};
Collections.sort(sales, comparator);
ArrayList<resultRecord> result=new ArrayList<resultRecord>();
salesRecord standard=sales.get(0);
float sumValue=standard.value;
for(int i = 1;i < sales.size(); i ++){
salesRecord rd=sales.get(i);
if(rd.salesman.equals(standard.salesman)){
sumValue=sumValue+rd.value;
}else{
result.add(new resultRecord(standard.salesman,sumValue));
standard=rd;
sumValue=standard.value;
}
}
result.add(new resultRecord(standard.salesman,sumValue));
Java编码实现计算不仅繁琐,而且存在架构性缺陷。代码很难复用,数据结构和计算代码通常会耦合在一起,如果数据结构发生变化,代码就要重写。查询计算的要求灵活多变,而Java作为编译型语言,每次修改代码都要重启应用,维护工作量大,系统稳定性差。
POI成熟稳定,但读写能力过于底层,且未提供查询计算能力,直接基于POI完成Excel文件的处理(特别是查询计算)的开发效率很低。如果针对POI进行封装,形成简单易用的高级读写函数,并额外提供查询计算能力,就能大幅度提高开发效率了。
esProc SPL就是其中的佼佼者。

SPL是JVM下开源的计算引擎,它对POI也进行了封装,内置简单易用的高级函数,可解析\生成各类格式规则或不规则的xls,并自动生成结构化数据对象。
解析格式规则的行式Excel,SPL提供了T函数。比如解析前面的xls文件,用封装前的POI要几十行,封装后只要一句:
=T(“d:\Orders.xls”)
解析行式Excel是很常见的任务,SPL用T函数封装了POI的功能,接口简单易用。无论xls还是xlsx,T函数都可以统一解析。可自动进行类型转换,开发者无须在细节浪费时间。T函数可自动区分首行的列名和其他行的数据,并根据列名创建序表(SPL的结构化数据对象)并填入数据:

读入并解析成序表后,就可以使用SPL提供的丰富的结构化数据处理方法了:
取第3条记录:A1(3)
取后3条记录:A1.m([-1,-2,-3])
取记录的字段值:A1(3).Amount*0.05
修改记录的字段值:A1(3).Amount = A1(3). Amount*1.05
取一列,返回集合:A1.(Amount)
取几列,返回集合的集合:A1.([CLIENT,AMOUNT])
追加记录:A1.insert(200,“APPL”,10,2400.4,date(“2010-10-10”))
先按字段取再按记录序号取:A1.(AMOUNT)(2);等价于先按记录序号取再按字段取:A1(2).AMOUNT
解析格式较不规则的行式xls,SPL提供了xlsimport函数,内置丰富而简洁的读取功能:
没有列名,首行直接是数据:file(“D:\Orders.xlsx”).xlsimport()
跳过前2行的标题区:file(“D:/Orders.xlsx”).xlsimport@t(;,3)
从第3行读到第10行:file(“D:/Orders.xlsx”).xlsimport@t(;,3:10)
只读取其中3个列:file(“D:/Orders.xlsx”).xlsimport@t(OrderID,Amount,OrderDate)
读取名为"sales"的特定sheet:file(“D:/Orders.xlsx”).xlsimport@t(;“sales”)
函数xlsimport还具有读取倒数N行、密码打开文件、读大文件等功能,这里不再详述。
解析格式很不规则的xls,SPL提供了xlscell函数,可以读写指定sheet里指定片区的数据,比如读取第1个sheet里的A2格:
=file(“d:/Orders.xlsx”).xlsopen().xlscell(“C2”)
配合SPL灵活的语法,就可以解析自由格式的xls,比如将下面的文件读为规范的二维表(序表):

这个文件格式很不规则,直接基于POI写Java代码是个浩大的工程,而SPL代码就简短得多:
| A | B | C | |
| 1 | =create(ID,Name,Sex,Position,Birthday,Phone,Address,PostCode) | ||
| 2 | =file("e:/excel/employe.xlsx").xlsopen() | ||
| 3 | [C,C,F,C,C,D,C,C] | [1,2,2,3,4,5,7,8] | |
| 4 | for | =A3.(~/B3(#)).(A2.xlscell(~)) | |
| 5 | if len(B4(1))==0 | break | |
| 6 | >A1.record(B4) | ||
| 7 | >B3=B3.(~+9) | ||
生成规则的行式xls,SPL提供了xlsexport函数,用法也很简单。比如,上面例子的解析结果是个序表,存在SPL的A1格中,下面将A1写入新xls的第一个sheet,首行为列名,只要一句代码:=file(“e:/result.xlsx”).xlsexport@t(A1)
xlsexport函数的功能丰富多样,可以将序表写入指定sheet,或只写入序表的部分行,或只写入指定的列:=file(“e:/scores.xlsx”).xlsexport@t(A1,No,Name,Class,Maths)
xlsexport函数还可以方便地追加数据,比如对于已经存在且有数据的xls,将序表A1追加到该文件末尾,外观风格与原文件末行保持一致:=file(“e:/scores.xlsx”).xlsexport@a(A1)
不规则片区写入数据,可以使用前面的xlscell函数。比如,xls中蓝色单元格是不规则的表头,需要在相应的白色单元格中填入数据,如下图:

直接用POI要大段冗长的代码,而SPL代码就简短许多:
| A | B | C | D | E | F | |
| 1 | Mengniu Funds | 2017 | 3 | 58.2 | 364 | 300 |
| 2 | 8.5 | 50 | 200 | 100 | 400 | 200 |
| 3 | 182.6 | 76.3 | 43.7 | 28.5 | 16.4 | |
| 4 | 120 | 1.07 | 30 | 0.27 | 90 | 0.8 |
| 5 | 154 | 6 | 4 | |||
| 6 | =file("e:/result.xlsx") | =A6.xlsopen() | ||||
| 7 | =C6.xlscell("B2",1;A1) | =C6.xlscell("J2",1;B1) | =C6.xlscell("L2",1;C1) | |||
| 8 | =C6.xlscell("B3",1;D1) | =C6.xlscell("G3",1;E1) | =C6.xlscell("K3",1;F1) | |||
| 9 | =C6.xlscell("B6",1;[A2:F2].concat("\t")) | =C6.xlscell("H6",1;[A3:E3].concat("\t")) | ||||
| 10 | =C6.xlscell("B9",1;[A4:F4].concat("\t")) | =C6.xlscell("B11",1;[A5:C5].concat("\t")) | ||||
| 11 | =A6.xlswrite(B6) | |||||
注意,第6、9、11行有连续单元格,SPL可以简化代码一起填入,POI只能依次填入。
查询计算是Excel处理任务的重点,SPL提供了丰富的计算函数、字符串函数、日期函数,以及标准SQL语法,不仅支持日常的xls计算,也能计算内容不规则的xls和逻辑复杂的xls。
SPL提供了丰富的计算函数,可直接完成基础计算。比如前面的分组汇总,只要一句:
A1.groups(SellerId;sum(Amount))
更多计算:
条件查询:A1.select(Amount>1000 && Amount<=3000 && like(Client,“S”))
排序:A1.sort(Client,-Amount)"
去重:A1.id(Client)"
关联两个xlsx:join(T(“D:/Orders.xlsx”)😮,SellerId; T(“D:/Employees.xls”):E,EId).new(O.OrderID,O.Client,O.SellerId,O.Amount,O.OrderDate, E.Name,E.Gender,E.Dept)"
TopN:T(“D:/Orders.xls”).top(-3;Amount)
组内TopN (开窗函数):T(“D:/Orders.xls”).groups(Client;top(3,Amount))
SPL支持大量日期函数和字符串函数,代码量更短,开发效率更高。比如:
时间类函数,日期增减:elapse(“2020-02-27”,5) //返回2020-03-03
星期几:day@w(“2020-02-27”) //返回5,即星期4
N个工作日之后的日期:workday(date(“2022-01-01”),25) //返回2022-02-04
字符串类函数,判断是否全为数字:isdigit(“12345”) //返回true
取子串前面的字符串:substr@l(“abCDcdef”,“cd”) //返回abCD
按竖线拆成字符串数组:“aa|bb|cc”.split(“|”) //返回[“aa”,“bb”,“cc”]
SPL还支持年份增减、求年中第几天、求季度、按正则表达式拆分字符串、拆出SQL的where或select部分、拆出单词、按标记拆HTML等功能。
SPL提供了标准SQL语法,可以像对数据库表一样直接对xls文件进行查询,极大地降低了数据库程序员的学习门槛:
filter: s e l e c t ∗ f r o m d : / s O r d e r . x l s x w h e r e C l i e n t l i k e ′ select * from d:/sOrder.xlsx where Client like '%S%' or (Amount>1000 and Amount<=2000)sort: select∗fromd:/sOrder.xlsxwhereClientlike′select * from sales.xls order by Client,Amont descdistinct:$ select distinct(sellerid) from sales.xls group by…having:KaTeX parse error: Expected 'EOF', got '&' at position 98: …ing sum(amount)&̲gt;=2000000join…select e.name, s.orderdate, s.amount from sales.xls s left join employee.xlsx e on s.sellerid= e.eid
SPL支持SQL-92标准中大部分语法,包括集合计算、case when、with、嵌套子查询等,详见《没有 RDB 也敢揽 SQL 活的开源金刚钻 SPL》
内容不规则的xls,一般的类库都无能为力,SPL语法灵活函数丰富,可轻松解决处理。比如Excel单元格里有很多”key=value”形式的字符串,需要整理成规范的二维表,以进行后续计算:
| A | |
| 1 | =file("D:/data/keyvalue.xlsx").xlsimport@w() |
| 2 | =A1.conj().(~.split("=")) |
| 3 | =A2.new(~(1),~(2)) |
逻辑复杂的计算,SQL和存储过程都难以实现,SPL的计算能力更强,可轻松解决此类问题。比如,计算某支股票最长的连续上涨天数:
| A | |
| 1 | =T("d:/AAPL.xlsx") |
| 2 | =a=0,A1.max(a=if(price>price[-1],a+1,0)) |
SPL是解释型语言,提供JDBC接口,可以用SQL或存储过程的形式被JAVA集成,不仅降低了架构的耦合性,还能支持热切换。SPL还支持多种数据源,并支持跨数据源计算。
SPL提供了JDBC接口,可被JAVA轻松调用。简单的SPL代码可以像SQL一样,直接嵌入JAVA,比如条件查询:
Class.forName("com.esproc.jdbc.InternalDriver");
Connection connection =DriverManager.getConnection("jdbc:esproc:local://");
Statement statement = connection.createStatement();
String str="=T(\"D:/Orders.xls\").select(Amount>1000 && Amount<=3000 && like(Client,\"*S*\"))";
ResultSet result = statement.executeQuery(str);
SPL支持计算外置,可降低计算代码和前端应用的耦合性。复杂的SPL代码可以先存为脚本文件,再以存储过程的形式被JAVA调用:
Class.forName("com.esproc.jdbc.InternalDriver");
Connection conn =DriverManager.getConnection("jdbc:esproc:local://");
CallableStatement statement = conn.prepareCall("{call scriptFileName(?, ?)}");
statement.setObject(1, "2020-01-01");
statement.setObject(2, "2020-01-31");
statement.execute();
SPL是解释型语言,通过外置代码可实现热切换。解释型语言无须编译,修改后可立即执行,无须重启JAVA应用,可降低维护工作量,提高系统稳定性。
SPL支持多种文件数据源,除了xls外,SPL还能读写csv\txt\XML\Json等文件,比如对txt进行条件查询:
T("sOrders.txt").groups(SellerId;sum(Amount))
$select * from d:/sOrders.txt where Client like '%S%' or (Amount>1000 and Amount<=2000)
SPL支持跨数据源计算,比如xls和txt的关联计算:
=join(T("D:/Orders.xlsx"):O,SellerId; T("D:/Employees.txt"):E,EId).new(O.OrderID,O.Client,O.SellerId,O.Amount,O.OrderDate, E.Name,E.Gender,E.Dept)"
SPL还能访问各类关系型数据库,WebService、Restful等网络服务, Hadoop、redis、Kafka、Cassandra等NoSQL。
POI只适合简单的xls解析\生成任务,且未提供查询计算能力。SPL对POI进行了封装,内置高级读写函数,不仅可以大幅简化代码,还能进行较不规则甚至很不规则的xls解析\生成任务。SPL额外提供了强大的计算能力,不仅支持日常的Excel查询计算,还可计算内容不规则的xls和逻辑复杂的xls。SPL支持更优的应用架构,可实现代码低耦合和热切换,支持多种数据源和跨数据源计算。
Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack
我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/
我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www
我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我
什么是ruby的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht
这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/
HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候
遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg
我基本上来自Java背景并且努力理解Ruby中的模运算。(5%3)(-5%3)(5%-3)(-5%-3)Java中的上述操作产生,2个-22个-2但在Ruby中,相同的表达式会产生21个-1-2.Ruby在逻辑上有多擅长这个?模块操作在Ruby中是如何实现的?如果将同一个操作定义为一个web服务,两个服务如何匹配逻辑。 最佳答案 在Java中,模运算的结果与被除数的符号相同。在Ruby中,它与除数的符号相同。remainder()在Ruby中与被除数的符号相同。您可能还想引用modulooperation.
Java的Collections.unmodifiableList和Collections.unmodifiableMap在Ruby标准API中是否有等价物? 最佳答案 使用freeze应用程序接口(interface):Preventsfurthermodificationstoobj.ARuntimeErrorwillberaisedifmodificationisattempted.Thereisnowaytounfreezeafrozenobject.SeealsoObject#frozen?.Thismethodretur