微软白板Excel xls列号数字转字母

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个字母的组合如此循环... ?。

于是发现了规律: 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 会递增,所以第一轮 在递增前是-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=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个字母是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);
      }

}

微软白板

原文地址:https://www.cnblogs.com/Leo_wl/p/9797147.html

时间: 2024-11-06 09:38:19

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

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

package test; /** * Deal with Excel column indexToStr and strToIndex * @author * @version 2015-7-8 * @see */ public class ExcelColumn { public static void main(String[] args) { String colstr = "AA"; int colIndex = excelColStrToNum(colstr, colstr

Excel的列数以数字格式查看

1.Excel中的列数默认是以字母形式显示的,当我们有大量数据并想知道任一数据是第多少行多少列时这样就不方便了,我们可以通过如下设置来达到让EXCEL以数字形式显示行数和列数的效果. 2.点击文件-->选择"选项" 3.公式-->"R1C1引用样式" 4.保存后,就可以看见列数都用数字来表示了. 原文地址:https://www.cnblogs.com/fulucky/p/9636033.html

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

转换Excel数字列号为字母列号 例如: 0 -> A 26 -> AA private static string GetColumnChar(int col) { var a = col / 26; var b = col % 26; if (a > 0) return GetColumnChar(a - 1) + (char)(b + 65); return ((char)(b + 65)).ToString(); }

一道有趣的算法题:仿照Excel的列编号,给定一个数字,输出该列编号字符串

       By Long Luo 最近遇到一个算法题: 仿照Excel的列编号,给出一个数字,输出该列编号字符串. 例如:A对应1,Z对应26,AA对应27,AZ对应52 ...... 这个题目是一个典型的26进制思路去处理,但是这个题目里面有很多陷阱,在1, 26, 52等特殊情况进行考虑,经过晚上接近1个小时的编写,完成的代码如下: C++代码如下: #include <iostream> #include <string.h> using namespace std; /

&lt;原创&gt;两个数字列号转字母列名的小程序

我在工作中遇到一个问题,在编写excel宏代码的时候,有时候,某一个数据需要定位放在某一个单元格中,但是这个单元格并不是本身就存在的单元格,而是在程序执行过程中,一步一步填写数据之后生成出来的单元格,只知道应该是第几列,却不知道列名字母是什么,所以,我就编写了下面的小程序,两个小程序的效果一样,只不过在编写思路上有差别. 这两个小程序都采用了函数的方式,方便在其他的程序中调用. Function ColNoToColName1(ColNo As Integer) As String  '列号转为

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

作者:iamlaosong 用VBA对Excel文件进行处理的时候,keyword段的列号编程时往往是不知道的.须要通过參数设定才干知道,因此.我们编程的时候,就不能用这种语句取有效行数: lineno = [B65536].End(xlUp).Row          '从下至上找有效行数 上述语句中的列名"B"假设是变量.能够用字符串连接的方式实现,即: pos_ems = "C" lineno = Range(pos_ems & "65536

【华为2015暑期实习生上机题】仿照Excel的列编号

这是本人上个月做的,武汉地区的上机题中的第三个,三个题目都是字符串的,共450分,两个小时内完成. 仿照Excel的列编号,给出该列编号字符串,输出一个数字. 例如:a对应1,z对应26,aa对应27,az对应52 -- #include <iostream> #include <string> using namespace std; //字符串到数字的转换,相当于26进制 int stoi(char *s) { int n=0;//字符串长度 int i=0;//循环变量 in

金山WPS、微软Office EXCEL表格通用C++接口

金山WPS.微软Office EXCEL表格通用C++接口 之前有个小项目在win32框架下,有个导出报表的小需求excel,小小的整理了下几个接口.最近闲着了解了下VAB宏,扩展了几个实用接口,适用于金山wps办公软件,至于微软的excel版本是否能够忽略,这个还真没试过,理论上某些接口是可以的(只要VAB宏没变,这些信息是从一些专业的VAB群中得来,感谢群里的兄弟支持).源码往下拉,供学习交流之用.后边有时间再做word的. 描述: 对Excel常用操作封装类,适用于Microexcel.W

【VBA研究】用VBA取得EXCEL任意列有效行数

作者:iamlaosong 用VBA对Excel文件进行处理的时候,关键字段的列号编程时往往是不知道的,需要通过参数设定才能知道,因此,我们编程的时候,就不能用这样的语句取有效行数: lineno = [B65536].End(xlUp).Row 上述语句中的列名"B"如果是变量,可以用字符串连接的方式实现,即: pos_ems = "C" lineno = Range(pos_ems & "65536").End(xlUp).Row 如