基于 java 注解的 csv 读写框架更加简单灵活

CSV

基于 java 注解的 csv 读写框架。

相关框架

Apache commons-csv

super-csv

简单看了下,这两个框架提供的特性都非常的基础。

创作原由

以前觉得 csv 文件的读写非常简单,就懒得封装。

最近一个月写了两次 csv 文件相关的东西,发现要处理的细节还是有的,还浪费比较多的时间。

比如:

  1. UTF-8 中文编码使用 excel 打开乱码,因为缺少 BOM 头。
  2. 不同类型字段转化为字符串,顺序的指定,head 头的指定,如果手写都会很繁琐。
  3. 读取的时候最后 , 后无元素,split 会缺失等。

为了解决上述问题,此框架应运而生。

特性

  • Fluent 流式写法
  • 基于 java 注解,支持自定义的转换和灵活配置
  • 内置 8 大基本类型以及 String 类型转换
  • 解决 Excel 直接打开,utf-8 乱码问题
  • 支持集合、数组、Map 的存取
  • 支持对象中内嵌其他对象
  • 支持特殊字符转义

变更日志

CHANGE_LOG.md

开源地址

csv

快速开始

环境

jdk7+

maven 3.x

maven 引入

<dependency>
    <groupId>com.github.houbb</groupId>
    <artifactId>csv</artifactId>
    <version>0.0.6</version>
</dependency>

示例代码

  • User.java

演示基本类型的转换

public class User {

    private String name;

    private int age;

    private float score;

    private double money;

    private boolean sex;

    private short level;

    private long id;

    private char status;

    private byte coin;

    //Getter & Setter & toString()
}
  • 对象列表构建
    /**
     * 构建通用测试列表
     * @return 列表
     */
    private List<User> buildCommonList() {
        User user = new User();
        short s = 4;
        byte b = 1;
        user.age(10)
        .name("你好")
        .id(1L)
        .score(60)
        .coin(b)
        .level(s)
        .money(200)
        .sex(true)
        .status(‘Y‘);
        return Arrays.asList(user);
    }

写入

  • 测试代码
public void commonTest() {
    final String path = "src\\test\\resources\\common.csv";
    CsvWriteBs.newInstance(path)
            .write(buildCommonList());
}
  • 文件生成
name,age,score,money,sex,level,id,status,coin
你好,10,60.0,200.0,true,4,1,Y,1

读取

public void commonTest() {
    final String path = "src\\test\\resources\\common.csv";
    List<User> userList = CsvReadBs.newInstance(path)
            .read(User.class);
    System.out.println(userList);
}
  • 日志信息
[User{name=‘你好‘, age=10, score=60.0, money=200.0, sex=true, level=4, id=1, status=Y, coin=1}]

引导类

为什么需要引导类

为了灵活的配置和默认配置并存,使用工具类会大大降低灵活性。

为了用户使用的便利性,和后期拓展的灵活性。

引导类

CSV 有两个引导类:

名称 作用
CsvWriteBs csv 文件写入引导类
CsvReadBs csv 文件读取引导类

CsvWriteBs

方法 默认值 说明
newInstance(final String path) 必填 创建实例,并且指定待写入文件路径。
path (final String path) 配置文件路径,只有重新指定 path 路径时需要调用。
writeHead(boolean writeBom) true 是否写入 head 头,如果想指定名称,可以结合注解。只有无 head 信息时,会写入。
writeBom(boolean writeBom) true 是否写入 UTF8 BOM 头,只有文件为空时才会写入。
charset(String charset) UTF-8 指定文件编码
sort(ISort sort) NoSort 默认不进行字段排序
write(List<T> list) 待写入的文件列表
escape false 是否进行特殊字符的转换

CsvReadBs

方法 默认值 说明
newInstance(final String path) 必填 创建实例,并且指定待读取文件路径。
path (final String path) 配置文件路径,只有重新指定 path 路径时需要调用。
charset(String charset) UTF-8 指定文件编码
sort(ISort sort) NoSort 默认不进行字段排序
startIndex(int startIndex) 1 文件的第二行,默认第一行是 head
endIndex(int endIndex) 文件的最后一行
escape false 是否进行特殊字符的转换

Csv 注解

注解属性说明

用于待处理对象的字段上。

    /**
     * 字段显示名称
     * 1. 默认使用 field.name
     * @return 显示名称
     */
    String label() default "";

    /**
     * 读取是否需要
     * @return 是
     */
    boolean readRequire() default true;

    /**
     * 写入是否需要
     * @return 是
     */
    boolean writeRequire() default true;

    /**
     * 读取转换
     * @return 处理实现类
     */
    Class<? extends IReadConverter> readConverter() default CommonReadConverter.class;

    /**
     * 写入转换
     * @return 处理实现类
     */
    Class<? extends IWriteConverter> writeConverter() default StringWriteConverter.class;

属性概览表

属性 默认值 说明
label 字段名称 用于 csv 头生成
readRequire true 是否需要从 csv 文件读取
writeRequire true 当前字段是否需要写入 csv 文件
readConverter CommonReadConverter 将 csv 中的字符串转化为当前字段类型,支持 8 大基本类型+String
writeConverter StringWriteConverter 直接调用当前字段值 toString() 方法,null 直接为空字符串

其中 readConverter/writeConverter 支持用户自定义

字段注解

对象定义

public class UserAnnotation {

    @Csv(label = "名称")
    private String name;

    @Csv(label = "密码", readRequire = false, writeRequire = false)
    private String password;

    @Csv(label = "生日", readConverter = ReadDateConvert.class, writeConverter = WriteDateConvert.class)
    private Date birthday;

    //Getter & Setter & toString()
}

ReadDateConvert/WriteDateConvert

使我们自定义的针对 Date 的转换实现。

  • Write
public class WriteDateConvert implements IWriteConverter<Date> {

    @Override
    public String convert(Date value) {
        DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
        return dateFormat.format(value);
    }

}
  • ReadDateConvert
public class ReadDateConvert implements IReadConverter<Date> {

    @Override
    public Date convert(String value, Class fieldType) {
        try {
            DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
            return dateFormat.parse(value);
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }

}

写入文件

public void annotationTest() {
    final String path = "src\\test\\resources\\annotation.csv";
    CsvWriteBs.newInstance(path)
            .write(buildAnnotationList());
}

其中列表构建:

/**
 * 构建基于注解的测试列表
 * @return 列表
 */
private List<UserAnnotation> buildAnnotationList() {
    UserAnnotation user = new UserAnnotation();
    user.name("你好")
            .password("123")
            .birthday(new Date());
    return Arrays.asList(user);
}
  • 生成文件内容
名称,生日
你好,20190603

读取文件测试

public void annotationTest() {
     final String path = "src\\test\\resources\\annotation.csv";
     List<UserAnnotation> userList = CsvReadBs.newInstance(path)
             .read(UserAnnotation.class);
     System.out.println(userList);
}
  • 日志信息
[UserAnnotation{name=‘你好‘, password=‘null‘, birthday=Mon Jun 03 00:00:00 CST 2019}]

集合类

有时候对象中会包含数组、Map、Collection 等常见集合。

为了存储的便利性,默认提供集合的相关支持。

特性和普通字段保持一致,如果指定注解转换,则以注解为准。

使用示例

  • UserCollection.java

用于演示集合的对象

public class UserCollection {

    private String[] arrays;

    private LinkedList<String> lists;

    private Map<String, String> maps;

    private Set<String> sets;

    //Getter/Setter/toString()
}

存储

  • 待存储对象的构建
/**
 * 构建基于集合的测试列表
 * @return 列表
 * @since 0.0.3
 */
private List<UserCollection> buildCollectionList() {
    UserCollection user = new UserCollection();
    String[] arrays = new String[]{"a", "b", "c"};
    LinkedList<String> lists = new LinkedList<>(Arrays.asList(arrays));
    Map<String, String> maps = new HashMap<>();
    maps.put("key", "value");
    maps.put("key2", "value2");
    Set<String> sets = new HashSet<>();
    sets.add("set1");
    sets.add("set2");

    user.setLists(lists);
    user.setArrays(arrays);
    user.setMaps(maps);
    user.setSets(sets);
    return Arrays.asList(user);
}
  • 执行存储
public void collectionTest() {
    final String path = "src\\test\\resources\\collection.csv";
    CsvWriteBs.newInstance(path)
            .write(buildCollectionList());
}
  • 存储效果
?arrays,lists,maps,sets
a|b,a|b|c,key2=value2|key=value,set1|set2

读取

  • 测试类
public void collectionTest() {
    final String path = "src\\test\\resources\\collection.csv";
    List<UserCollection> userList = CsvReadBs.newInstance(path)
            .read(UserCollection.class);
    System.out.println(userList);
}
  • 测试日志
[UserCollection{arrays=[a, b], lists=[a, b, c], maps={key=value, key2=value2}, sets=[set2, set1]}]

注意

为了保证 csv 以 , 分隔的统一性。

集合使用 | 进行分隔,其中 map 的 key/value 分隔,用到了 =

在使用时要注意,不要包含上述的符号,否则会出现解析错乱。

ps: 如果确实用到这些字符,可以见后面的特殊字符转义功能

支持内嵌对象

有时候我们希望像使用 mongoDB 一样,非常方便的存取 csv 的嵌套对象。

对于普通的 csv 都没有实现这个特性,本次做了一个尝试,支持内嵌对象的存取。

取舍

就像 csv 的简单,需要用到符号 , 一样。

内嵌对象为了不破坏 csv 的规范,使用了符号 :

换言之,也就是对象内容中不能使用这个符号。

后期会针对出现的符号进行转义,避免这种冲突。

测试案例

示例对象

  • UserEntry.java
public class UserEntry {

    /**
     * 名称
     */
    private String name;

    /**
     * 内嵌的用户信息
     */
    @CsvEntry
    private User user;

    //Getter/Setter/ToString
}

这里在需要内嵌的对象上使用注解 @CsvEntry 表示需要进行内嵌的对象转换。

  • User.java

其中 User 对象是原来使用的普通 java 对象

public class User {

    private String name;

    private int age;

    private float score;

    private double money;

    private boolean sex;

    private short level;

    private long id;

    private char status;

    private byte coin;

    //Getter/Setter/ToString
}

写入测试

public void entryTest() {
    final String path = "src\\test\\resources\\entry.csv";
    CsvWriteBs.newInstance(path)
            .write(buildEntryList());
}
  • buildEntryList()

负责对象构建代码,内容如下:

/**
 * 用户明细列表
 * @return 明细列表
 * @since 0.0.5
 */
private List<UserEntry> buildEntryList() {
    UserEntry userEntry = new UserEntry();
    userEntry.name("test");
    userEntry.user(buildCommonList().get(0));
    return Collections.singletonList(userEntry);
}
  • buildCommonList()
private List<User> buildCommonList() {
    User user = new User();
    short s = 4;
    byte b = 1;
    user.age(10)
    .name("你好")
    .id(1L)
    .score(60)
    .coin(b)
    .level(s)
    .money(200)
    .sex(true)
    .status(‘Y‘);
    return Arrays.asList(user);
}

生成文件效果

name,user
test,你好:10:60.0:200.0:true:4:1:Y:1

如你所见,这里内嵌对象的属性使用了 : 进行分隔。

读取测试

public void entryTest() {
    final String path = "src\\test\\resources\\entry.csv";
    List<UserEntry> userList = CsvReadBs.newInstance(path)
            .read(UserEntry.class);
    System.out.println(userList);
}

输出信息

[UserEntry{name=‘test‘, user=User{name=‘你好‘, age=10, score=60.0, money=200.0, sex=true, level=4, id=1, status=Y, coin=1}}]

特殊字符转义

在实际使用中,有时候我们会用到 ,|:=

这几个被使用的特殊字符。

如果你希望这些特殊的字符被正确的存取,那么可以使用 escape 属性执行。

特殊字符的转换

原始 转义后
, &CSV_COMMA;
| &CSV_OR;
: &CSV_COLON;
= &CSV_EUQAL;

下面演示一下如何使用

暂时转义字符不支持自定义。

测试代码

写入测试

public void escapeTest() {
    final String path = "src\\test\\resources\\escape.csv";
    CsvWriteBs.newInstance(path)
            .escape(true)
            .write(buildUserEscapeList());
}
  • 生成文件效果
name,map,nameList,user
one&CSV_COMMA;one,key&CSV_EUQAL;key=value&CSV_EUQAL;value,one&CSV_OR;one|two&CSV_OR;two,entry&CSV_COLON;name:0:0.0:0.0:false:0:0: :0

相关代码

  • UserEscape.java

其中用到的对象为:

public class UserEscape {

    /**
     * 使用 ,
     */
    private String name;

    /**
     * 使用 map =
     */
    private Map<String, String> map;

    /**
     * 使用 |
     */
    private List<String> nameList;

    /**
     * 使用 :
     */
    @CsvEntry
    private User user;

    //Getter & Setter & ToString()
}
  • buildUserEscapeList()

构建时,特意使用了特殊的字符。

private List<UserEscape> buildUserEscapeList() {
    UserEscape escape = new UserEscape();
    Map<String, String> map = new HashMap<>();
    map.put("key=key", "value=value");
    User user = new User();
    user.name("entry:name");

    escape.name("one,one");
    escape.nameList(Arrays.asList("one|one", "two|two"));
    escape.map(map);
    escape.user(user);

    return Collections.singletonList(escape);
}

读取测试

public void escapeTest() {
    final String path = "src\\test\\resources\\escape.csv";
    List<UserEscape> userList = CsvReadBs.newInstance(path)
            .escape(true)
            .read(UserEscape.class);
    System.out.println(userList);
}
  • 日志信息
[UserEscape{name=‘one,one‘, nameList=[one|one, two|two], user=User{name=‘entry:name‘, age=0, score=0.0, money=0.0, sex=false, level=0, id=0, status= , coin=0}, map={key=key=value=value}}]

后续设计

更丰富的类型支持

支持更多的 java 常见类型。

更灵活的配置

比如支持用户自定义转义字符

支持文件的写入模式等等。

开源地址

csv

可以查看相关代码。

为了便于其他人阅读和使用,代码拥有详细的注释。

原文地址:https://blog.51cto.com/9250070/2411709

时间: 2024-10-11 01:05:10

基于 java 注解的 csv 读写框架更加简单灵活的相关文章

基于java注解实现自己的orm框架

ORM即Object Relation Mapping,Object就是对象,Relation就是关系数据库,Mapping映射,就是说Java中的对象和关系数据库中的表存在一种对应关系. 现在常见的ORM框架比如Hibernate和mybatis,都是采用了ORM的方式,基本原则就是类-表(Table).属性-列(Column)这样的对应,所以一个对象就能表示数据表中的一行数据啦. 本文转自http://m.blog.csdn.net/woshisangsang/article/details

基于Java+Selenium的WebUI自动化测试框架(四)

基于上一篇的内容,这里我们开始写监听类Listener.我这里写监听类的思路是,继承TestListenerAdapter这个类,然后对其中的方法进行重写.网上也有很多资料,建议先学习一下,然后写出来. package webui.xUtils; import org.openqa.selenium.WebDriver; import org.testng.ITestContext; import org.testng.ITestResult; import org.testng.Reporte

基于Java+Selenium的WebUI自动化测试框架(七)--IE浏览器的设置

在上一篇我们讲了关于WebDriver的版本,浏览器初始化,以及下载的设定. 在设置IE浏览器进行WebDriver的测试时,通常会遇见以下几种错误: 1.没有关闭IE浏览器的保护模式. 当运行测试用例后出现类似以下内容的错误: Exception in thread "main" org.openqa.selenium.remote.SessionNotFoundException: Unexpected error launching Internet Explorer. Prot

JAVA注解在SSH开发中的简单应用

在系统开发过程中,出现错误在所难免.虽然系统出错时控制台也会报错,但是因为系统控制台输出太多,往往不能快速定位出现错误的功能点及原因.在此通过使用注解,结合spring的AOP,来制作一个错误输出拦截器. 首先写一个注解类Catcher: @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited public @interface Catcher {     String 

基于注解的 java 加签验签框架 checksum

checksum 基于 java 注解生成加签验签 checksum. 开源地址:github checksum 创作缘由 原来的代码中,checksum 的生成是用的工具类方法. 后来发现如下的问题: 有些字段太大,不想参与验签,但是无法方便的调整. 不同系统的 checksum 字段不同,只好把工具方法 copy 过去,改来改去. 感觉这样有很大的弊端,完全失去了灵活性. 特性 基于注解的 checksum 加签验签 Fluent 流式语法 支持灵活的策略自定义 更新记录 更新记录 快速开始

优于 swagger 的 java markdown 文档生成框架-01-入门使用

设计初衷 节约时间 Java 文档一直是一个大问题. 很多项目不写文档,即使写文档,对于开发人员来说也是非常痛苦的. 不写文档的缺点自不用多少,手动写文档的缺点也显而易见: 非常浪费时间,而且会出错. 无法保证及时更新.代码已经变了,但是文档还要同步修改.需要强制人来维护这一种一致性.这很难. 为什么不是 swagger-ui java 的文档有几类: jdk 自带的 doc 生成.这个以前实践给别人用过,别人用 C#,看到 java 的默认文档感觉很痛苦. 就算是我们 java 开发者,也很讨

自己写的基于java Annotation(注解)的数据校验框架

JavaEE6中提供了基于java Annotation(注解)的Bean校验框架,Hibernate也有类似的基于Annotation的数据校验功能,我在工作中,产品也经常需要使 用数据校验,为了方便和重用,自己写了一个简单的基于Annotation的校验框架.有兴趣的可以扩展. 框架说明: AnnotationValidable接口:所有需要使用该校验框架的类都要实现它,该类中没有任何方法需要实现,仅仅是一个表明那些类需要使用该校验框架的标识. GetFiledValue类:是一个工具类,对

9个基于Java的搜索引擎框架

在这个信息相当繁杂的互联网时代,我们已经学会了如何利用搜索引擎这个强大的利器来找寻目标信息,比如你会在Google上搜索情人节如何讨女朋友欢心,你也会在百度上寻找正规的整容医疗机构(尽管有很大一部分广告骗子).那么如果在你自己开发的网站系统中需要能让用户搜索一些重要的信息,并且能以结构化的结果展现给用户,下面分享的这9款Java搜索引擎框架或许就可以帮助到你了. 1.Java 全文搜索引擎框架 Lucene 毫无疑问,Lucene是目前最受欢迎的Java全文搜索框架,准确地说,它是一个全文检索引

框架 day37 Spring3,AOP,代理模式(动态/CGLIB/工厂bean),传统AOP,AspectJ框架(基于xml/注解),切入点表达式,jdbcTemplate

1     AOP 1.1   什么是AOP 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术.AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型.利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率. * AOP采取横向抽取机制,取代了传统纵向继承体系