Excel xls列号数字转字母

https://blog.csdn.net/lf124/article/details/53432817?utm_source=itdadao&utm_medium=referral

最近遇到导出的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个字母的组合如此循环...  。

于是发现了规律: AAAZ是在AZ前面加了A,BABZ是在AZ前面加了B,... ZAZZ是在AZ前面加了Z,这时两个字母组合完毕;到3个字母 AAAAAZ是在AAAZ前面加了A,ABAABZ是在BABZ前面加了A,... AZAAZZ是在ZAZZ前面加了A,这时AAZZ遍历完了一次(这里的AAZZ是前面两个字母组合里出现过的);接下来到下一轮遍历 在前面加B,BAABAZ是在AAAZ前面加了B,... BZABZZ是在ZAZZ前面加了B,这时AA~ZZ又遍历完了一次;再进行下一轮遍历 一直到在所有两个字母的组合前都加过A~Z,这时3个字母的组合就全部组合完毕了。接下来到4个字母的组合 跟前面的2个、3个字母的组合类似,都是在上一个组合的基础上分别在前面加上A~Z。如下图

字母组合是从第27列开始,每次的组合都是在上一个组合的基础上分别在前面加上AZ。比如计算n=3个字母的组合AAAZZZ(上图红色竖线箭头部分):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 会递增,所以第一轮 在递增前是-1
int letterIdx = -1;
int currentLen = 26;//第一轮组合(2个字母的组合)是分别与A-Z进行拼接 所以是26
int remainder;
int lastLen = 0; //用于定位上文提到的i%currentLen实际在数组中的位置
int totalLen = 26; //totalLen=currentLen+lastLen
int 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=264+263+262+261,BGQCV 的第一个字母是B,说明前面有Axxxx的全组合了,于是有该组合数T1=1(264)。再来看第二个字母是G,说明前面已经有BAxxx~BFxxx的全组合了,于是有该组合数T2=6*(263)。第三个字母是Q,说明前面已经有BGAxxBGPxx的全组合了,于是有该组合数T3=16*(26^2)。第四个字母是C,说明前面已有BGQAxBGQBx的全组合了,该组合数T4=2(26^1)。最后一个字母是V,说明前缀是BGQC的组合BGQCA~BGQCV共有T5=22个。

好了,BGQCV 所处的列数W=W1+T1+T2+T3+T4+T5=(264+263+262+261)+1(264)+6*(263)+16(262)+2*(261)+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)+...+262+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)+...+262+261)+(num_1-1)*26(n-1)+(num_2-1)26(n-2)+...+(num_(n-1)-1)*261+(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)+...+262+261)+(num_1-1)*26(n-1)+(num_2-1)
26(n-2)+...+(num_(n-1)-1)*261

然后两边除以26:(W-(num_n-1))/26=(26(n-2)+26(n-3)+26(n-4)+...+261+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)+...+261)+(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个字母是Z

    sb.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列:IV

xlsx第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);
}

}

微软白板

微软白板Excel xls列号数字转字母的更多相关文章

  1. 把EXCEL列号数字变成字母

    把Excel 列号数字变成字母 private static string ToName(int index) { if (index < 0) { throw new Exception(&q ...

  2. Java 导出 Excel 列号数字与字母互相转换工具

    package test; /** * Deal with Excel column indexToStr and strToIndex * @author * @version 2015-7-8 * ...

  3. Excel的列数以数字格式查看

    1.Excel中的列数默认是以字母形式显示的,当我们有大量数据并想知道任一数据是第多少行多少列时这样就不方便了,我们可以通过如下设置来达到让EXCEL以数字形式显示行数和列数的效果. 2.点击文件-- ...

  4. Excel中数字和字母混合时提取某些字符进行排序

    在excel中,当数字和字母混合在一起的时候,会出现排序错误的情况 比如下图的这种情况.我们希望的是2排在1后面,但是实际上10却排在了1的后面.这时候我们就需要把字符串中的数字提取出来进行排序 第一 ...

  5. [原创] [C#] 转换Excel数字列号为字母列号

    转换Excel数字列号为字母列号 例如: 0 -> A 26 -> AA private static string GetColumnChar(int col) { ; ; ) ) + ...

  6. Excel 改变列表头显示方式, Excel显示列数字

    '显示数字列号 Sub showCellNumber() Application.ReferenceStyle = xlR1C1 End Sub '显示字母列号 Sub showCellZimu() ...

  7. Java将Excel的列数以字母表示的字符串转换成数字表示

    我们知道,在 Excel 中,行数用数字表示,而列数是用字母表示的(如下图所示),有时候需要把它转换成数字来使用,或者把数字转换成字母.(例如使用POI操作Excel) 下面是转换代码,用来进行字母和 ...

  8. excel VBA返回选中单元格区域的行数、列数,以及活动单元格的行号和列号

    Private Sub Worksheet_SelectionChange(ByVal Target As Range) '可以直接sub(),不然选择就会触发vba    Dim rows_coun ...

  9. 【VBA研究】用VBA取得EXCEL随意列有效行数

    作者:iamlaosong 用VBA对Excel文件进行处理的时候,keyword段的列号编程时往往是不知道的.须要通过參数设定才干知道,因此.我们编程的时候,就不能用这种语句取有效行数: linen ...

随机推荐

  1. JSONP跨站访问

    js中几种实用的跨域方法原理详解 这里说的js跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架中(iframe)的数据.只要协 ...

  2. ubuntu16.04 虚拟机 安装win7/win10

    http://www.xitongcheng.com/jiaocheng/xtazjc_article_26588.html https://blog.csdn.net/sunyao_123/arti ...

  3. 小技巧|使用Vue.js的Mixins复用你的代码

    Vue中的混入 mixins 是一种提供分发 Vue 组件中可复用功能的非常灵活的方式.听说在3.0版本中可能会用Hooks的形式实现,但这并不妨碍它的强大. 这里主要来讨论 mixins 如何优化我 ...

  4. 20145109 《Java程序设计》第五周学习总结

    20145109 <Java程序设计>第五周学习总结 教材学习内容总结 Chapter 8 Exception Handling try, catch All Exceptions are ...

  5. 添加一个vue全局守卫,主要用于用户登录时候验证

    //注册一个全局守卫,作用是在路由跳转钱,对路由进行判断,防止未登录用户跳转到其他页面 router.beforeEach((to, from, next) => { let token = l ...

  6. 做Webservice时报错java.util.List是接口, 而 JAXB 无法处理接口。

    Caused by: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExc ...

  7. Linux系统中使用netcat命令的奇技淫巧

    netcat是网络工具中的瑞士军刀,它能通过TCP和UDP在网络中读写数据.通过与其他工具结合和重定向,你可以在脚本中以多种方式使用它.使用netcat命令所能完成的事情令人惊讶. netcat所做的 ...

  8. Metasploit – 内网连接

    0x00 问题描述 在渗透测试时,metasploit往往作为后渗透工具,(因为远程溢出越来越少).我一般都是在获得一个webshell后,来使用metasploit进行信息采集,或者内网扫描等操作. ...

  9. AppLocker Pro FAQ

    How to use AppLocker Pro: 1. Start AppLocker Pro, create a password.2. In the main console, click &q ...

  10. VS 安装部署项目自解压程序解压后按顺序执行多个程序

    这篇blog介绍了如何用VS创建安装部署方案,以及如何制作自解压程序.然后我的程序中需要解压后按照顺序先后安装2个exe.winrar的解压后执行,虽然可以用分号填写多个应用,但貌似是同时执行的.为了 ...