EasyExcel识别表头

本文将介绍使用EasyExcel实现识别单行表头的一种实现方式。主要是通过实现AnalysisEventListener 抽象类,并覆写其 hasNext 和 invokeHead 方法。

Jar依赖

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

<dependency>
	<groupId>org.reflections</groupId>
	<artifactId>reflections</artifactId>
	<version>0.10.2</version>
</dependency>

实现代码

一个表头基类和2个子类,一个用于表头字段的类。

注意:代码使用反射,使用了@ExcelProperty(value = “地址”, index = 0)注解,index用于表头字段排序,value用于表头显示的名称。

1.表头基类
package top.jyokiyi.my.test.excel;
import lombok.Data;

@Data
public class ExcelBaseModel {
}
2.表头实现类1

继承表头基类:

package top.jyokiyi.my.test.excel;

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

@Data
public class ExcelHeadTest1Model extends ExcelBaseModel {

    @ExcelProperty(value = "地址", index = 0)
    private String address;

    @ExcelProperty(value = "名称", index = 1)
    private String name;
}
3.表头实现类2

继承表头基类:

package top.jyokiyi.my.test.excel;

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;

@Data
public class ExcelHeadTest2Model extends ExcelBaseModel {

    @ExcelProperty(value = "邮件",index = 0)
    private String email;

    @ExcelProperty(value = "内容", index = 1)
    private String content;
}
4. 表头字段类
package top.jyokiyi.my.test.excel;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@NoArgsConstructor
@AllArgsConstructor
@Data
public class ExcelHeadFieldModel {
    /**
     * 列号
     */
    private Integer column;
    /**
     * 表头描述
     */
    private String headDesc;
}
5. 使用Reflections实现查找所有子类

注意:这里直接指定了包名,且子类和基类在同一个包中。(如有需要可自行修改,以实现自己的需求)


/**
 * 获取指定类的所有子类
 * @param clazz
 * @param <T>
 * @return
 */
public static <T>Set<Class<? extends T>> getAllSubTypes(Class<T> clazz) {
	Set<Class<? extends T>> subTypeClassSet = new HashSet<>();

	Reflections reflections = new Reflections("top.jyokiyi.my.test.excel");
	Set<Class<? extends T>> subTypesOf = reflections.getSubTypesOf(clazz);
	if (CollectionUtils.isNotEmpty(subTypesOf)) {
		subTypeClassSet.addAll(subTypesOf);
	}

	return subTypeClassSet;
}
6.表头类型与表头字段集合映射
/**
 * 将表头与对于的类映射起来
 * @param sets
 * @param <T>
 * @return
 */
public static <T>Map<Class<? extends T>, List<ExcelHeadFieldModel>> getHeadMapForExcelProperty(Set<Class<? extends T>> sets) {
	Map<Class<? extends T>, List<ExcelHeadFieldModel>> maps = new HashMap<>();

	for (Class<? extends T> clz : sets) {
		List<ExcelHeadFieldModel> fieldModelList = new ArrayList<>();
		try {
			Field[] fields = clz.getDeclaredFields();
			for (Field field : fields) {
				if (field.isAnnotationPresent(ExcelProperty.class)) {
					ExcelProperty annotation = field.getAnnotation(ExcelProperty.class);
					fieldModelList.add(ExcelHeadFieldModel.builder()
							.column(annotation.index()).headDesc(annotation.value()[0]).build());
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		if (CollectionUtils.isNotEmpty(fieldModelList)) {
			maps.put(clz, fieldModelList.stream()
                       .sorted(Comparator.comparing(ExcelHeadFieldModel::getColumn))
                       .collect(Collectors.toList()));
		}
	}
	return maps;
}
7.实现监听类

继承easy excel 的 AnalysisEventListener 抽象类,并覆写其 hasNext 和 invokeHead 方法。

package top.jyokiyi.my.test.excel;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.util.StringUtils;

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class ExcelHeaderIdentifyListener extends AnalysisEventListener<Map<Integer, String>> {

    private static ThreadLocal<Class<? extends ExcelBaseModel>> classResult = new ThreadLocal<>();

    public ThreadLocal<Class<? extends ExcelBaseModel>> getClassResult() {
        return classResult;
    }

    public void clear() {
        classResult.remove();
    }

    /**
     * 返回false,读取一行后,不再继续读取
     *
     * @param context
     * @return
     */
    @Override
    public boolean hasNext(AnalysisContext context) {
        return false;
    }

    /**
     * 根据表头解析获取对应的表头子类
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        List<ExcelHeadFieldModel> headFieldModels = headMap.values().stream().map(column -> ExcelHeadFieldModel.builder()
                .column(column.getColumnIndex()).headDesc(column.getStringValue()).build())
                .sorted(Comparator.comparing(ExcelHeadFieldModel::getColumn))
                .collect(Collectors.toList());
        // 将表头字段拼接起来
        String joinStr = headFieldModels.stream()
                .map(ExcelHeadFieldModel::getHeadDesc)
                .collect(Collectors.joining());

        // 获取表头基类的所有子类信息
        Set<Class<? extends ExcelBaseModel>> allSubTypes = ClassUtils.getAllSubTypes(ExcelBaseModel.class);
        // 将class和表头映射起来
        Map<Class<? extends ExcelBaseModel>, List<ExcelHeadFieldModel>> listMap = ClassUtils.getHeadMapForExcelProperty(allSubTypes);
        // 查找对应的子类
        for (Map.Entry<Class<? extends ExcelBaseModel>, List<ExcelHeadFieldModel>> entry : listMap.entrySet()) {
            if (entry.getValue().size() == headFieldModels.size() && StringUtils.equals(joinStr, entry.getValue()
                    .stream().map(ExcelHeadFieldModel::getHeadDesc).collect(Collectors.joining()))) {
                classResult.set(entry.getKey());
                break;
            }
        }
    }
    

    @Override
    public void invoke(Map<Integer, String> data, AnalysisContext context) {

    }


    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {

    }
}
8.测试
package top.jyokiyi.my.test.excel;

import com.alibaba.excel.EasyExcel;

import java.io.File;
import java.io.FileInputStream;

public class ReadTest {


    public static void main(String[] args) throws Exception {
        ExcelHeaderIdentifyListener listener = new ExcelHeaderIdentifyListener();
        String dir = System.getProperty("user.dir");
        String filePath = dir + File.separator + "src" + File.separator + "test_excel.xlsx";

        EasyExcel.read(new FileInputStream(filePath), listener)
                 .sheet().doRead();

        Class<? extends ExcelBaseModel> aClass = listener.getClassResult();
        listener.clear();
        System.out.println(aClass.getName());
    }
}

测试结果:

top.jyokiyi.my.test.excel.ExcelHeadTest1Model

测试文件的表头如下:

地址名称
12

x86-64汇编之Hello world

1. 环境

WSL子系统:Ubuntu

2. 工具

所需工具包列表:

程序名称检查命令所需的包
GUN Assembleras –versionbinutils
GUN Linkerld –versionbinutils
GUN Debuggergdb –versiongdb
Makemake –versionmake

3. 代码

代码使用语法风格是:AT&T。

.global _start
.section .data
mystr:
    .string "Hello, world!\n"
mystrend:
.section .text
_start:
    # 设置系统调用号,1 表示write
    movq $1, %rax
    # 标准输出stdout
    movq $1, %rdi
    # 字符串指针
    leaq mystr(%rip), %rsi
    # 字符串长度
    movq $(mystrend-mystr), %rdx
    # 执行系统调用
    syscall

    # 设置系统调用号,60 表示exit
    movq $60, %rax
    syscall

4.编译链接并执行

# 编译
as helloworld.s -o helloworld.o
# 链接
ld helloworld.o -o helloworld
# 执行,控制台会输出 :Hello, world!
./helloworld

参考资料:

  1. x64架构体系的系统调用列表:https://x64.syscall.sh/

No CMAKE_CXX_COMPILER could be found.

错误信息

在使用cmake 编译 flex&bison程序时,使用cmake的project()指令,编译报以下错误,错误具体信息:

CMake Error at CMakeLists.txt:3 (project):
  No CMAKE_CXX_COMPILER could be found.

  Tell CMake where to find the compiler by setting either the environment
  variable "CXX" or the CMake cache entry CMAKE_CXX_COMPILER to the full path
  to the compiler, or to the compiler name if it is in the PATH.


-- Configuring incomplete, errors occurred!

原因是:cmake 的project() 命令在没有指定 LANGUAGES 选项时,默认是C和CXX(注意cmake 3.0之前的版本不支持LANGUAGES 关键字),CXX编译时需要指定 g++编译器。

解决方法

有2种方式:

方式一

在project() 命令中指定LANGUAGES 选项为C:

project(test1 LANGUAGES C)

方式二

安装g++ :

apt install g++

flex的开始状态

是在flex定义部分中声明,开始状态(start states),也称为 开始条件(start conditions) 或者 开始规则(start rules)。

开始状态的作用

是用来限制特定规则的作用范围,或者针对文件的部分内容来改变扫描器的工作方式。即根据开始状态激活对应的规则(rules)。

开始状态的模式

开始状态有2种模式:

  1. %s :inclusive (包含模式),它允许未标记任何开始状态的rule 可以进行匹配。
  2. %x:exclusive(独占模式),它只允许标记为该开始状态下的rule 进行匹配。通常独占模式更有用。

注意:在 flex定义部分中声明时 使用非缩进的形式。例如:

/* definitions 定义部分 */
%{

%}
%s simple
%x test
%%
/*rule action  规则和执行动作 部分*/
<simple>[0-9]+ {}
<test>[a-z]+ {}
%%
/* helper function 辅助函数部分*/

注意在规则部分中:开始状态名称和rule之间是没有空格的

开始状态的激活

开始状态的激活需要使用:宏 BEGIN 进行激活。BEGIN的语法:

BEGIN  statename;

statename 是使用 %x 或 %s 声明的开始状态名称。Flex默认的开始状态是 零 状态,也称为 INITIAL。BEGIN(0)等价于 BEGIN(INITIAL)。

注意:宏 BEGIN 本身是没有任何参数的,状态名字也不应该被括号括起来,但是加括号是一种良好的风格。

示例

分别使用2种状态模式对字符串数字”123.45″进行识别:

%{
    #include <stdio.h>
    #include <math.h>
%}

%s expect

%%

"expect-floats" BEGIN(expect);

<expect>[0-9]+"."[0-9]+ {
    printf("found a float,=%f\n", atof(yytext));
}

<expect>\n {
    /* BEGIN(INITIAL); */
}

[0-9]+ {
    printf("found an integer, = %d\n", atoi(yytext));
}

"." {
    printf("found a dot \n");
}

%%

int main()
{
    yylex();
    return 1;
}

CMakeLists.txt

cmake_minimum_required(VERSION 3.20)

project(startConditionTest LANGUAGES C)

find_package(FLEX)

FLEX_TARGET(myscanner startcondition_test.l ${CMAKE_CURRENT_BINARY_DIR}/startConditionTest.lex.c)

# 设置主要包含的c文件
set(MAIN_SRC ${CMAKE_CURRENT_BINARY_DIR}/startConditionTest.lex.c)

# 使用源文件生成可以执行文件
add_executable(conditionTest01 ${MAIN_SRC})

# libfl.a
find_library(LEX_LIB fl)
TARGET_LINK_LIBRARIES(conditionTest01 ${LEX_LIB})

包含模式%s

使用包含模式:%s expect.

编译并执行:

mkdir build
cd build
cmake ../
make
# 执行
./conditionTest01 
123.45
found an integer, = 123
found a dot 
found an integer, = 45

"expect-floats" 123.45
"" found a float,=123.450000
123
found an integer, = 123

执行结果如下图所示:

独占模式%x

使用独占模式:%x expect

编译执行:

└─# ./conditionTest01 
123.456
found an integer, = 123
found a dot 
found an integer, = 456

"expect-floats"
""123.45
found a float,=123.450000
123456
123456

789
789

如下图所示:

总结

从执行结果上面可以看到:

  1. 独占模式被激活时,没有标记任何开始状态的rule,将不会被匹配。
  2. 包含模式被激活后,未标记开始状态的rule 也是可以进行匹配的。
  3. 和开始状态的模式小节中描述的是一致的。

使用cmake编译flex

环境

Linux系统

cmake版本:cmake version 3.25.1

make版本:GNU Make 4.3

flex版本:flex 2.6.4

flex源文件

catcot.l 文件

%{
    #include <stdio.h>    
%}

%%
c.t { printf("mumble mumble"); }
cot { printf("portable bed"); }
cat { printf("thankless pet"); }
cats { printf("anti-herd");}

%%

int main()
{
    /* 调用获取token */
    yylex();
    return 0;
}

CMakeLists.txt文件

# 指定cmake 最低版本号
cmake_minimum_required(VERSION 3.20)

# 指定项目名称 和 语言
project(catcot1 LANGUAGES C)

# 查找依赖包
find_package(FLEX)

# 使用宏定义生成规则
FLEX_TARGET(catcotScanner catcot.l ${CMAKE_CURRENT_BINARY_DIR}/catcot.lex.c)

# 设置主要包含的c文件
set(MAIN_SRC ${CMAKE_CURRENT_BINARY_DIR}/catcot.lex.c)

# 使用源文件生成可以执行文件
add_executable(catcotTest ${MAIN_SRC})

编译

# 第一步
mkdir build
# 第二步
cd build
# 第三步
cmake ../
# 第四步
make 

问题

在编译部分 第四步 时会出现如下的错误提示:

/usr/bin/ld: CMakeFiles/catcotTest.dir/catcot.lex.c.o: in function `yylex':
catcot.lex.c:(.text+0x4e0): undefined reference to `yywrap'
/usr/bin/ld: CMakeFiles/catcotTest.dir/catcot.lex.c.o: in function `input':
catcot.lex.c:(.text+0x10de): undefined reference to `yywrap'
collect2: error: ld returned 1 exit status
make[2]: *** [CMakeFiles/catcotTest.dir/build.make:101: catcotTest] Error 1
make[1]: *** [CMakeFiles/Makefile2:83: CMakeFiles/catcotTest.dir/all] Error 2
make: *** [Makefile:91: all] Error 2

问题解决

解决上面的undefined reference to `yywrap’问题,有以下三种方法:

  • 方法1:在 catcot.l 文件头部添加:%option noyywrap
  • 方法2:在 catcot.l 文件中添加自己的yywrap()函数,返回值1
int yywrap()
{
   return 1;
}
  • 方法3:在CMakeLists.txt文件末尾追加:
# 添加 libfl.a 库
find_library(LEX_LIB fl)
# 链接 libfl.a 库
TARGET_LINK_LIBRARIES(catcotTest ${LEX_LIB})

这样可以使用默认flex的库libfl.a 生成的默认yywrap函数。

以上3种方法,均已验证是可以的。

推荐使用第 1 种方法

使用Flex&Bison时可能的问题和解决方法

问题1:cannot find ll

意思是找不到 libl.a库文件。

问题2:cannot find fl

意思是找不到 libfl.a库文件。

问题3:cannot find ly

意思是找不到 liby.a库文件。

解决方法

下面是在Ubuntu系统上的解决方法介绍:

  1. 安装apt-file:apt install apt-file
  2. 使用apt-file命令查找库在哪个工具包中:
apt-file search libl.a
apt-file search libfl.a
apt-file search liby.a

查找结果:

3. 安装对应的工具包就可以了

apt install libfl-dev libbison-dev

apt-cache 是使用包名或包描述 检索仓库。

apt-file 是 使用包中文件名检索存储库(工具包名)。

Linux centos 对应的是 yum whatprovides ,具体使用方法请自行查阅。

语法分析器Bison安装

Bison源码地址:https://github.com/akimd/bison

Linux环境安装

方式一

到Bison官网上给定的下载地址:https://ftp.gnu.org/gnu/bison/

下载完成后,解压后进入文件夹目录按照INSTALL文件内容中的安装步骤即可。

  • 执行配置:./configure ,执行后可能会检查出来缺少依赖,按照提示的内容,安装缺少的依赖就可以了。
  • 执行编译和检查:make && make check
  • 执行安装:sudo make install ,默认是安装在 /usr/local/bin目录下。
  • 安装成功后,输入bison --version ,输出版本号就表示成功了。

方式二

直接命令安装:sudo apt install bison

Windows环境安装

在上一篇文章中,Flex安装中已经说明安装了MinGW。下面直接说安装Bison。

选择最新的版本下载,并选择安装到和Flex一个目录中。之前flex的bin目录已经添加到了系统环境path中了。这里直接打开cmd控制台,输入:bison --version 输出正确的版本即成功安装。

词法分析器Flex安装

Flex源码地址:https://github.com/westes/flex/

Linux环境环境安装

方式一

  • 环境说明

Linux Debian x86_64

注意:系统需要安装 cmake 和 make.

  • 安装依赖:sudo apt install help2man m4
  • 下载flex压缩包:wget https://github.com/westes/flex/releases/download/v2.6.4/flex-2.6.4.tar.gz
  • 解压文件:sudo tar -xvf flex-2.6.4.tar.gz
  • 进入文件夹:cd flex-2.6.4
  • 查看INSTALL.md文件中的安装步骤
  • 执行配置命令:./configure 会把列出提示缺少的依赖包,需要把这些缺少的依赖逐个安装一下就行。help2man,m4等
  • 执行编译和检查:make && make check
  • 执行安装:sudo make install 默认是安装在/usr/local/bin 路径下面的。
  • 检查是否安装成:flex --version 成功输出版本号,则是成功。

方式二

直接安装:sudo apt install flex

Windows环境安装

  • 环境说明:系统win10,需要安装MinGW(Minimalist GUN on Windows),是GCC的windows版本,MinGW有2个发行版本:MinGW-64 和 MinGW。区别在于MinGW只能编译生成32位可执行程序,而MinGW-64则可以 编译生成64位和32位的可执行程序。下载地址:https://sourceforge.net/projects/mingw-w64/files/

可以选择以下2种方式安装,如下图:

我选择的是【x86_64-win32-sjlj】,解压后直接将 bin目录配置到系统环境变量path中,然后将mingw32-make.exe重命名为make.exe。

目前这里只提供了32位的工具,如果需要64位的文件需要自己手动编译源码生成。

安装完成后,需要将安装目录下的bin文件夹目录添加到系统环境变量path中。

完成系统环境配置后打开cmd控制台,输入:flex --version 可以打印出版本号。

C语言安全地释放指针

安全释放指针

最近在读有关C语言指针方面的书籍,看到有关malloc系列函数申请内存释放使用宏定义的方式。自己也动手试了一下,感觉还不错记录一下分享给小伙伴们。

实现和应用

#include <stdlib.h>
#include <stdio.h>

void safeFreePtr(void **p_ptr)
{
    if (p_ptr != NULL && *p_ptr != NULL)
    {
        free(*p_ptr);
        *p_ptr = NULL;
    }
}

/** 宏定义 */
#define safeFree(ptr)  safeFreePtr((void **)&(ptr))

int main(int argc, char const *argv[])
{
    int *a = (int *)malloc(sizeof(int));
    *a = 10;
    printf("Before: %p\n", a);
    safeFree(a);
    printf("After: %p\n", a);
    safeFree(a);
    return 0;
}

运行结果:

Before: 0x5634b2e342a0
After: (nil)

可以看出第二次safeFree时打印是:nil,程序没有出错。

对于申请的指针,使用free函数释放时只能free一次,第二次free会报错。

指针free释放后,一定要记得把指针置为NULL,防止悬挂指针。

好处

如果程序中是直接调用safeFreePtr函数是需要显示转换为 void** 指针,若不显示转换则会报警告。在使用宏调用safeFreePtr函数可以避免强制转换

查看系统默认内存对齐字节数

本文使用C语言查看系统默认内存对齐字节数。

#pragma pack(show)

实现当前系统默认对齐字节数,是以警告的方式显示

使用vscode编辑器,写入以下代码:

// 默认是8
#pragma pack(show)

int main(int argc, char const *argv[])
{
   return 0;
}

运行上面的代码后,会在vscode的控制台 【PROBLEMS】tab中显示下面的警告提示信息。

value of #pragma pack(show) == 8 [Semantic Issue]

表示当前内存对齐字节数8.