最近遇到导出的xls中 列是动态生成的,且单元格中需要用到公式,而xls公式不是用数字列号而是用列字母来表示的,这时需要把数字的列号转成该列对应的字母。因为是按月导出 一个月最多31天,所以刚开始采用的办法是定义一个包含1到31列字母的数组。后来想想这样总不是个办法 万一列数更多 且是不确定的呢。于是研究了下 怎么把xls数字列号转成对应的字母。

先来看xlsx中的字母规律。在xls中,1到26列是A~Z,从第27列开始 是2个以上的字母组合 AA AB ... AZ 然后到BA BB ... BZ 直到 ZA ZB ... ZZ 这时两个字母的组合完了 接下来到3个字母 AAA AAB ... AAZ 然后到ABA ABB ... ABZ 一直到AZZ 然后是BAA BAB ... BZZ最后到ZAA ... ZZZ,接下来又是4个字母的组合如此循环...  。

于是发现了规律: AA~AZ是在A~Z前面加了A,BA~BZ是在A~Z前面加了B,... ZA~ZZ是在A~Z前面加了Z,这时两个字母组合完毕;到3个字母 AAA~AAZ是在AA~AZ前面加了A,ABA~ABZ是在BA~BZ前面加了A,... AZA~AZZ是在ZA~ZZ前面加了A,这时AA~ZZ遍历完了一次(这里的AA~ZZ是前面两个字母组合里出现过的);接下来到下一轮遍历 在前面加B,BAA~BAZ是在AA~AZ前面加了B,... BZA~BZZ是在ZA~ZZ前面加了B,这时AA~ZZ又遍历完了一次;再进行下一轮遍历 一直到在所有两个字母的组合前都加过A~Z,这时3个字母的组合就全部组合完毕了。接下来到4个字母的组合 跟前面的2个、3个字母的组合类似,都是在上一个组合的基础上分别在前面加上A~Z。如下图

字母组合是从第27列开始,每次的组合都是在上一个组合的基础上分别在前面加上A~Z。比如计算n=3个字母的组合AAA~ZZZ(上图红色竖线箭头部分):2个字母组合所在的范围是AA~ZZ(上图红色的“currentLen”部分),当前数组的位置(比如AAB)i与currentLen进行取余运算(i%currentLen)得到的结果就是要与2个字母组合的哪个种组合进行字符串拼接(该值还要与“lastLen”(上图红色lastLen)相加才能定位到该位置对应的字母),比如当前是第一轮遍历 则是A与AB进行拼接成AAB,这也就是上面举例的当前数组位置对应的字母(AAB)。假设我们用变量letterIdx表示第几轮,每上个组合遍历完一次letterIdx都要+1,表示下一个要拼接在前面的字母。因为拼接在前面的字母是A~Z 所以每次取该轮字母时都要跟26取余(letterIdx%26),当letterIdx%26=0时 说明A~Z都与上个组合拼接过了 也就是当前n个字母组合所有情况都组合过了,再进行下一轮n+1个(4个)字母的组合(上图黑色竖线箭头部分),这时currentLen变成了上一个组合的长度(上图黑色的currentLen),而lastLen变成了“上个组合的currentLen+lastLen”(上图黑色的lastLen部分),接下来的循环遍历跟上个组合一样进行。

上代码:(代码里的注释请结合上文来看,注释里说到的“组合的情形”是指该组合的某一种组合,如BK、AH、XI都是2个字母组合的一种情形。因为实在也不知道应该用哪个词来表达)

public final class Columns {private Columns() {}private static String[] sources = new String[]{"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"};/*** (256 for *.xls, 16384 for *.xlsx)* @param columnNum 列的个数,至少要为1* @throws IllegalArgumentException 如果 columnNum 超出该范围 [1,16384]* @return 返回[1,columnNum]共columnNum个对应xls列字母的数组*/public static String[] getColumnLabels(int columnNum) {if(columnNum<1||columnNum>16384)throw new IllegalArgumentException();String[] columns = new String[columnNum];if(columnNum<27){   //小于27列 不用组合System.arraycopy(sources, 0, columns, 0, columnNum);return columns;}        System.arraycopy(sources, 0, columns, 0, 26);   //前26列不需要进行组合//因为基于数组是从0开始,每到新一轮letterIdx 会递增,所以第一轮 在递增前是-1int letterIdx = -1;int currentLen = 26;//第一轮组合(2个字母的组合)是分别与A-Z进行拼接 所以是26int remainder;int lastLen = 0;  //用于定位上文提到的i%currentLen实际在数组中的位置        int totalLen = 26; //totalLen=currentLen+lastLenint currentLoopIdx = 0; //用来记录当前组合所有情形的个数for(int i=26;i<columnNum;i++){ //第27列(对应数组的第26个位置)开始组合//currentLen是上个组合所有情形的个数,与它取余找到要与上个组合的哪种情形进行拼接remainder = currentLoopIdx%currentLen;if(remainder==0){letterIdx++; //完成一次上个组合的遍历,转到下个字母进行拼接int j = letterIdx%26;//A-Z 26个子母都与上个组合所有情形都进行过拼接了,需要进行下个组合的拼接if(j==0&&letterIdx!=0){ lastLen = totalLen; //下个组合的lastLen是上个组合的totalLen/*** 下个组合的currentLen是上个组合的所有组合情形的个数* (等于上个组合的currentLen*26),26也就是拼接在前面的A-Z的个数*/             currentLen = 26*currentLen;totalLen = currentLen+lastLen; //为下一轮的开始做准备currentLoopIdx = 0; //到下一轮了 因此需要重置}}/*** sources[letterIdx%26]是该轮要拼接在前面的字母* columns[remainder+lastLen]是上个组合被拼接的情形*/     columns[i] = sources[letterIdx%26]+columns[remainder+lastLen];currentLoopIdx++;}return columns;}
}

测试:

public static void main(String[] args) {String[] columns = getColumnLabels(37 );System.out.println("1到37列:"+Arrays.toString(columns));System.out.println();long start = System.nanoTime();columns = getColumnLabels(256);System.out.println("创建"+columns.length+"列用时(纳秒):"+(System.nanoTime()-start));System.out.println("xls第"+columns.length+"列:"+columns[columns.length-1]);System.out.println();start = System.nanoTime();columns = getColumnLabels(16384);System.out.println("创建"+columns.length+"列用时(纳秒):"+(System.nanoTime()-start));System.out.println("xlsx第"+columns.length+"列:"+columns[columns.length-1]);
}

打印:

1到37列:[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T,
U, V, W, X, Y, Z, AA, AB, AC, AD, AE, AF, AG, AH, AI, AJ, AK]创建256列用时(纳秒):192833
xls第256列:IV创建16384列用时(纳秒):9574147
xlsx第16384列:XFD

------------------------------------------------------分隔线-----------------------------------------------------------------

后来有想到,如果是不需要(或者条件不允许)提前创建(这么大的)数组呢,这时需要通过列号直接获取该列对应的字母。于是又想 应该怎么转换。。。
先看看从字母转数字的,比如BGQCV 转成数字(虽然xlsx最后一列是XFD,这里只讨论数字与字母的互转):该列标有5个字母,说明前面已经有4个字母、3、2、1个字母的全组合了 才会到5个字母的组合,于是W1=26^4+26^3+26^2+26^1,BGQCV 的第一个字母是B,说明前面有Axxxx的全组合了,于是有该组合数T1=1*(26^4)。再来看第二个字母是G,说明前面已经有BAxxx~BFxxx的全组合了,于是有该组合数T2=6*(26^3)。第三个字母是Q,说明前面已经有BGAxx~BGPxx的全组合了,于是有该组合数T3=16*(26^2)。第四个字母是C,说明前面已有BGQAx~BGQBx的全组合了,该组合数T4=2*(26^1)。最后一个字母是V,说明前缀是BGQC的组合BGQCA~BGQCV共有T5=22个。
好了,BGQCV 所处的列数W=W1+T1+T2+T3+T4+T5=(26^4+26^3+26^2+26^1)+1*(26^4)+6*(26^3)+16*(26^2)+2*(26^1)+22 = 1048576。
String[] sources = new String[]{"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"};
数组sources作为我们解决问题的字母来源(当然也可以不定义数组而是用ASCII码)。
现在知道了通过字母列标是如何求出其对应的第几列,我们把问题一般化:求第W列对应的列标。设第W列对应的字母有n个,根据上面的分析可知W1=26^(n-1)+26^(n-2)+26^(n-3)+...+26^2+26^1。我们规定A对应1,B对应2 ... Z对应26。设第1个字母对应数字是num_1,第二个字母对应的数字是num_2,...,第n个字母对应数字num_n。根据上面的分析 于是有T1=(num_1-1)*26^(n-1),T2=(num_2-1)*26^(n-2),...,Tn=(num_n-1)*26^0。
整合起来,又因为26^0=1 于是有W=W1+T1+T2+...+Tn=(26^(n-1)+26^(n-2)+26^(n-3)+...+26^2+26^1)+(num_1-1)*26^(n-1)+(num_2-1)*26^(n-2)+...+(num_(n-1)-1)*26^1+(num_n-1)。
除了最右边的字母(也就是第n个字母)对应的位置的数字num_n-1除外,其它字母对应的位置的数字均是26的倍数。于是  第一次W对26取余W%26的结果所对应的字母(还记得吗 我们用1代表A,2代表B,... ,26代表Z)就是最右边的字母。好了 现在已经求出了最右边的字母,还剩n-1个未知字母,采用同样的办法可求出次右边的字母:W-(num_n-1)然后再除以26,得到的结果再减去1(因为W1中存在26^1,其除以26后结果就是1,为了保证除了次右边字母对应的位置的数字外,其余各字母对应的位置的数字均是26的倍数),把结果赋回给W,这时求次右边的字母就跟求最右边的字母类似了:
W-(num_n-1)=(26^(n-1)+26^(n-2)+26^(n-3)+...+26^2+26^1)+(num_1-1)*26^(n-1)+(num_2-1)*26^(n-2)+...+(num_(n-1)-1)*26^1
然后两边除以26:(W-(num_n-1))/26=(26^(n-2)+26^(n-3)+26^(n-4)+...+26^1+1)+(num_1-1)*26^(n-2)+(num_2-1)*26^(n-3)+...+(num_(n-1)-1)
然后两边再减去1:(W-(num_n-1))/26-1=(26^(n-2)+26^(n-3)+26^(n-4)+...+26^1)+(num_1-1)*26^(n-2)+(num_2-1)*26^(n-3)+...+(num_(n-1)-1)
然后把左边的(W-(num_n-1))/26-1看成整体的W,是不是跟最开始求最右边字母的很类似?
这样从右往左求出来的字符串 跟所要的结果恰好是相反的 所要需要反转。
上代码:

/*** 返回该列号对应的字母* @param columnNo (xls的)第几列(从1开始)*/
public static String getCorrespondingLabel(int columnNo){if(columnNo<1/**||columnNo>16384**/)throw new IllegalArgumentException();String[] sources = new String[]{"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"};StringBuilder sb = new StringBuilder(5);int remainder = columnNo%26;    //求最右边的字母if(remainder==0){    //说明(num_n-1)=26,第26个字母是Zsb.append("Z");remainder = 26;  //因为接下来W-(num_n-1)也就是columnNo-remainder,所以需要把remainder赋值回26}else{  //如果最右边字母不是Z的话,就去sources数组相应的位置取字母,remainder不用变sb.append(sources[remainder-1]);}columnNo = (columnNo-remainder)/26-1; //用来判断接下来是否还有其他字母//当 当前循环是求最后一个字母时(从右往左),(columnNo-remainder)/26就会是0,再减1也就是-1。//因此通过判断(columnNo-remainder)/26-1是否大于-1来判断结束while(columnNo>-1){remainder = columnNo%26;sb.append(sources[remainder]);columnNo = (columnNo-remainder)/26-1;}return sb.reverse().toString();   //因为是从右往左解析的 所以需要反转
}

测试:

public static void main(String[] args) {String label = getCorrespondingLabel(37 );System.out.println("第37列:"+label);System.out.println();long start = System.nanoTime();label = getCorrespondingLabel(256);System.out.println("查找第256列对应字母 用时(纳秒):"+(System.nanoTime()-start));System.out.println("xls第256列:"+label);System.out.println();label = getCorrespondingLabel(16384);System.out.println("xlsx第16384列:"+label);
}

打印:

第37列:AK查找第256列对应字母 用时(纳秒):7776
xls第256列:IVxlsx第16384列:XFD

这时又想到,求n列列标 用方法1好一点呢 还是用方法2循环n次好呢。。。
方法1求数组的 对于26+26*26=702列以下,可以将求组合部分抽出来,减少不必要的求余运算multiple%26,因为这时multiple不会超过26。方法2也可以改成从0开始的 毕竟poi列数是从0开始的。不预定义字母数组的话 也可以用ASCII码来转换。
-----------------------------------------------分隔线-----------------------------------------------------

最终代码:

/*** * Excel列号转字母工具类* */
public final class Columns {private Columns() {}private static String[] sources = new String[] { "A", "B", "C", "D", "E","F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R","S", "T", "U", "V", "W", "X", "Y", "Z" };/*** (256 for *.xls, 16384 for *.xlsx)* * @param columnNum*            列的个数,从1开始* @throws IllegalArgumentException*             如果 columnNum 超出该范围 [1,16384]* @return 返回[1,columnNum]共columnNum个对应xls列字母的数组*/public static String[] getColumnLabels(int columnNum) {if (columnNum < 1 || columnNum > 16384)throw new IllegalArgumentException();String[] columns = new String[columnNum];if (columnNum < 27) {System.arraycopy(sources, 0, columns, 0, columnNum);return columns;}int multiple = -1;int remainder;System.arraycopy(sources, 0, columns, 0, 26);int currentLoopIdx = 0;if (columnNum < 703) {for (int i = 26; i < columnNum; i++) {remainder = currentLoopIdx % 26;if (remainder == 0) {multiple++;}columns[i] = sources[multiple] + columns[remainder];currentLoopIdx++;}} else {int currentLen = 26;int totalLen = 26;int lastLen = 0;for (int i = 26; i < columnNum; i++) {remainder = currentLoopIdx % currentLen;if (remainder == 0) {multiple++;int j = multiple % 26;if (j == 0 && multiple != 0) {lastLen = totalLen;currentLen = 26 * currentLen;totalLen = currentLen + lastLen;currentLoopIdx = 0;}}columns[i] = sources[multiple % 26]+ columns[remainder + lastLen];currentLoopIdx++;}}return columns;}/*** 返回该列号对应的字母* * @param columnNo*            (xls的)第几列(从1开始)*/private static String getCorrespondingLabel(int columnNo) {if (columnNo < 1/** ||columnNo>16384 **/)throw new IllegalArgumentException();StringBuilder sb = new StringBuilder(5);int remainder = columnNo % 26;if (remainder == 0) {sb.append("Z");remainder = 26;} else {sb.append(sources[remainder - 1]);}while ((columnNo = (columnNo - remainder) / 26 - 1) > -1) {remainder = columnNo % 26;sb.append(sources[remainder]);}return sb.reverse().toString();}/*** 列号转字母* * @param columnIndex*            poi里xls的列号(从0开始)* @throws IllegalArgumentException*             if columnIndex less than 0* @return 该列对应的字母*/public static String getIndexLabel(int columnIndex) {return getCorrespondingLabel(columnIndex + 1);}}

Java poi Excel xls列号数字转字母相关推荐

  1. Java POI excel隐藏列

    /*** 隐藏列* @param sheet* @param hiddenColumns*/protected void hideColumns(Sheet sheet,List<Integer ...

  2. Java读取Excel表格以及读取数字列转为文本的解决办法

    Java读取Excel表格代码和把数字列转为文本的解决办法. 所需 jar 包:dom4j-1.6.1.jar ,poi-3.8-20120326.jar,poi-ooxml-3.8-20120326 ...

  3. Java POI Excel导入导出

    Java POI Excel导入导出 1.maven引入依赖 2.导入Excel 3.导出Excel 1.maven引入依赖 <!-- POI Excel 操作 --> <depen ...

  4. JAVA获取excel第一列数据

    JAVA获取excel第一列数据 提示 1:需要将.xlsx格式转为.xls(不懂得自行百度) 2:不要一次读取太多数据,导致内存溢出 一个简单的方法========== @GetMapping(&q ...

  5. matlab显示英文字母,#EXCEL函数判断是数字还是字母#excel表格列显示字母

    EXCEL如何筛选出包含字母的值? 可以用ISNUMBER后进行筛选. 1.以Excel2010版本为例,如下图,该列既有纯数字的值,又有包含字母的值,选出包含字母的值: 2.首先在B2单元格输入公式 ...

  6. Java POI Excel移动行和复制行的处理

    目录 Java POI Excel移动行和复制行的处理 坑点: 实现的代码 Java POI Excel移动行和复制行的处理 POI操作Excel时,不支持移动行的操作,因此在需要通过复制行+删除行+ ...

  7. Java POI——Excel导入导出的列英文字母与数字的互转方法记录

    因项目需要,写了递归实现POI--excel英文字母列转列编号,记录. 规则1:excel英文字母列转数字编号:A→0,B→1,Z→25,AA→26,ZZ→701,AAA→702 - public i ...

  8. JAVA poi合并任意列 相同数据单元格

    以下为poi合并单元格 所需要jar如下 commons-collections poi java 1.8 版本 以下为代码 import java.io.FileNotFoundException; ...

  9. Java POI Excel( pio:纯java操作excel的api )

    2010-03-17 21:35 POI官方网址:http://poi.apache.org/ POI的功能实在很强大,而且是apache的子项目,它下面又包含一些Component,比如处理Exce ...

最新文章

  1. 通过internet在计算机之间以用户名,第7_8章_计算机网络与internet应用.doc
  2. 【C语言学习】《C Primer Plus》第5章 运算符、表达式和语句
  3. Codeforces Round #309 (Div. 2) A. Kyoya and Photobooks 字符串水题
  4. STC89C52单片机 串口通信
  5. 写时拷贝(Copy On Write)方案详解
  6. 2008年汶川大地震中的SAP成都研究院
  7. MySql 缓存查询原理与缓存监控 和 索引监控
  8. 吉哥系列故事——完美队形II(hdu4513+Manacher)
  9. 深度优先搜索(DFS)
  10. 服务器回退本次提交的文件,Git提交完整步骤与CMD命令使用
  11. 一个分页的java工具类,可以参考
  12. vb.net它SqlHelper制备及应用
  13. 区块链软件搭建,区块链平台搭建
  14. usnews2015美国大学计算机排名,2019USNews美国大学计算机专业排名
  15. 【学习】笔记本电脑重新安装系统win10
  16. 给AWStats关键词统计增加去百度,谷歌等搜索引擎的站内查询链接
  17. 数字三角形求最大路径
  18. HTML页面为什么设置了UTF-8仍然中文乱码
  19. selenium常用的浏览器窗口操作
  20. 2021最新 阿里云服务器免费SSL证书配置方法(图文教程)

热门文章

  1. React 移动端翻牌抽奖
  2. python答题之:用1,2,3,4四个数字能组成多少个互不相同且不重复数字的三位数?
  3. xps in html5,戴尔XPS 13 2-in-1 7390次月上市 是你首部能买到的10nm CPU机型
  4. 微型计算机原理期末试题,微机原理期末考试试题及答案.doc
  5. 使用canvas.toDataURL把图片转为base64格式
  6. 小程序弹窗的几种形式
  7. 建材安装php源码,PHP响应式瓷砖大理石建材企业网站整站源码(自适应手机移动端) dedecms内核...
  8. html中怎么让一行显示,css一行显示_css 如何设置a标签占一行
  9. Linux常用命令——lvcreate命令
  10. 数字化转型需要什么样的“发动机”?