java POI 读写 excel(项目实际需求)

本贴最后更新于 4149 天前,其中的信息可能已经时过境迁

这几天由于工作需要,需要做一个报表比对工具(找出两张报表的差异)。需求如下

1、做一个客户端工具,采用java,可以不用GUI(考虑到,我目前只懂j2ee相关的技术,其实这个需求用python(python版本)来做是最合适的,可是我目前不会,当然后面我会学习下这们语言)

2、两张报表的比队列需要灵活配置,通过配置文件来控制需要比对的列(可以是一列,可以是多列)

3、需要找出表A中有的一条数据,但是表B中没有的。或者表B中有的,表A中没有的。或者表A和表B同时都存在的一条数据,但是里面的某一些字段不一样。

4、两张报表的格式可能不一样,有可能为csv,xls,xlsx。两张报表的列数也是不固定的(这两张报表可以是任意的两张报表)

5、将比对结果再汇总到一张excel中(格式没有要求,我这里默认的是xls)

6、主键可能为复合主键,比对的报表,可能需要一次比对多组报表(由于这个需求,是今天才加上的,所以目前代码还处理这个问题,当然会很快加上)

思路一:

POI1-1

1、采用二维数组为主要的数据结构。因为,报表的列数不确定,所以不能构造对象,采用下标来控制每一列

2、将A表中的第一行的主键,然后搜索表B的主键,如果没搜到记下来,如果搜到了,比较所配置的列看是否相同,如果不同记下来,再反搜,表B中的主键来搜素表A,如果表B中有的,表A中没有的,记下来。

将记下来的数据重组,再导出excel。

效率:两张表都是30多列,52行,大概一秒左右比对完导出到excel中。

思路二

 POI1-2

1、主要采用了循环加map的方式,再比较是否有相等行的时候,直接用,表A的mapA.get(mapB.get("key"))如果数据不为空,说明在表A中有数据和表B匹配,这里的get有效的减少了一次循环。利用了map.get的方法速度更快。其他的。但是主要逻辑,还是如思路相同

接下来贴出,处理这个需求的主要代码。贴出代码的目的,主要是希望各位网友看到后,能够给指点一二,我这代码,肯定还需要优化,重构。或者说换思路。当然你有什么想法,通过评论,或者邮件告诉我。谢谢

ReadCSVExcel方法

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map;

import au.com.bytecode.opencsv.CSVReader;

import com.qly.report.operate.facade.IReadExcel;

public class ReadCsvImpl implements IReadExcel{
static String qlyStringArr[][] = new String[1000][1000];
static String otherStringArr[][] = new String[1000][1000];
static Map<String,Integer > qlycontent = new HashMap<String,Integer >();
static Map<String,Integer > othercontent = new HashMap<String,Integer >();

@Override
public Map&lt;String, Object&gt; readExcel(String path, String flag) {
	CSVReader reader = null;
	// 取特定列放到集合
	try {
		InputStream ins = new FileInputStream(new File(path));
		InputStreamReader in = new InputStreamReader(ins, &quot;gbk&quot;);
		reader = new CSVReader(in);
		int len = reader.readNext().length;
		String nextline[];
		int counter = 0;
		// 将不同的来源的csv文件分别存放到不同的二维数组中
		if (&quot;qly&quot;.equals(flag)) {
			Map&lt;String, Object&gt; qlyMap = new HashMap&lt;String, Object&gt;();
			while ((nextline = reader.readNext()) != null) {
				for (int i = 0; i &lt; len; i++) {
					qlyStringArr[counter][i] = nextline[i];
				}
				counter++;
			}
			qlycontent.put(&quot;colNum&quot;, len);
			qlycontent.put(&quot;rowNum&quot;, counter);
			qlyMap.put(&quot;qlyStringArr&quot;, qlyStringArr);
			qlyMap.put(&quot;qlycontent&quot;, qlycontent);
			return qlyMap;
		}

		if (&quot;other&quot;.equals(flag)) {
			Map&lt;String, Object&gt; otherMap = new HashMap&lt;String, Object&gt;();
			while ((nextline = reader.readNext()) != null) {
				for (int i = 0; i &lt; len; i++) {
					otherStringArr[counter][i] = nextline[i];
				}
				counter++;
			}
			othercontent.put(&quot;colNum&quot;, len);
			othercontent.put(&quot;rowNum&quot;, counter);
			otherMap.put(&quot;othercontent&quot;, othercontent);
			otherMap.put(&quot;otherStringArr&quot;, otherStringArr);
			return otherMap;
		}

	} catch (FileNotFoundException e) {
		e.printStackTrace();
	} catch (IOException e) {
		e.printStackTrace();
	} finally {
		try {
			reader.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	return null;
}

}

ReadXlsExcel 方法

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;

import org.apache.poi.hssf.usermodel.HSSFCell;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import com.qly.report.operate.facade.IReadExcel;
import com.qly.report.util.ReportUtil;

public class ReadXlsImpl implements IReadExcel{

public static HSSFWorkbook wb = null;
public static HSSFSheet hssfSheet =null;
public static HSSFRow hssfRow = null;
public static XSSFWorkbook xb = null;
public static XSSFSheet xssfSheet =null;
public static XSSFRow xssfRow =null;
public static String qlyStringArr[][] = new String[1000][1000];
public 	static String otherStringArr[][] = new String[1000][1000];
static Map&lt;String,Integer &gt; qlycontent = new HashMap&lt;String,Integer &gt;();
static Map&lt;String,Integer &gt; othercontent = new HashMap&lt;String,Integer &gt;();
@SuppressWarnings({ &quot;deprecation&quot; })
@Override
public Map&lt;String ,Object&gt; readExcel(String path, String flag) {
	int counter = 0;
	try {
		InputStream is = new FileInputStream(new File(path));

		wb = new HSSFWorkbook(is);
		hssfSheet = wb.getSheetAt(0);
		// 得到总行数
		int rowNum = hssfSheet.getLastRowNum();
		hssfRow = hssfSheet.getRow(1);

		int colNum = hssfRow.getPhysicalNumberOfCells();
		// 正文内容应该从第二行开始,第一行为表头内容
		if(&quot;qly&quot;.equals(flag)){
			Map&lt;String,Object&gt;qlyMap = new HashMap&lt;String, Object&gt;();
			for (int i = 1; i &lt; rowNum; i++) {
				hssfRow = hssfSheet.getRow(i);
				counter = 0;
				while (counter &lt; colNum) {
					qlyStringArr[i][counter] = get2003StringCellValue(
							hssfRow.getCell((short)counter)).trim();
					counter++;
				}
			}
			qlycontent.put(&quot;colNum&quot;, counter);
			qlycontent.put(&quot;rowNum&quot;, rowNum);
			qlyMap.put(&quot;qlyStringArr&quot;, qlyStringArr);
			qlyMap.put(&quot;qlycontent&quot;, qlycontent);
			return  qlyMap;
		}
		if(&quot;other&quot;.equals(flag)){
			Map&lt;String,Object&gt;otherMap = new HashMap&lt;String, Object&gt;();
			for (int i =1; i &lt; rowNum; i++) {
				hssfRow = hssfSheet.getRow(i);
				counter = 0;
				while (counter &lt; colNum) {
					otherStringArr[i][counter] = get2003StringCellValue(hssfRow.getCell((short) counter)).trim();
					
					counter++;
				}
			}
			othercontent.put(&quot;colNum&quot;, counter);
			othercontent.put(&quot;rowNum&quot;, rowNum);
			otherMap.put(&quot;othercontent&quot;, othercontent);
			otherMap.put(&quot;otherStringArr&quot;, otherStringArr);
			return otherMap;
		}


	} catch (FileNotFoundException e) {
		e.printStackTrace();
	} catch (IOException e) {
		e.printStackTrace();
	}	
	return null;
}
private static String get2003StringCellValue(HSSFCell cell) {
	String strCell = &quot;&quot;;
	
	if (null == cell) {
		return &quot;&quot;;
	}
	switch (cell.getCellType()) {
	case HSSFCell.CELL_TYPE_STRING:
		strCell = cell.getStringCellValue();
		break;
	case HSSFCell.CELL_TYPE_NUMERIC:
		//使用DecimalFormat类对科学计数法格式的数字进行格式化
		DecimalFormat df = new DecimalFormat(&quot;#&quot;);
		String str = String.valueOf(cell.getNumericCellValue());
		if(ReportUtil.isNumber(str)){
			strCell =  df.format(cell.getNumericCellValue());
		}else{
			strCell = str;
		}
		
		break;
	case HSSFCell.CELL_TYPE_BOOLEAN:
		
		strCell = String.valueOf(cell.getBooleanCellValue());
		break;
	case HSSFCell.CELL_TYPE_BLANK:
		strCell = &quot;&quot;;
		break;
	default:
		strCell = &quot;&quot;;
		break;
	}
	if (strCell.equals(&quot;&quot;) || null == strCell) {
		return &quot;&quot;;
	}
	return strCell;

}

}

ReadXlsxExcel方法

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;

import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import com.qly.report.operate.facade.IReadExcel;
import com.qly.report.util.ReportUtil;

public class ReadXlsxImpl implements IReadExcel {
public static HSSFWorkbook wb = null;
public static HSSFSheet hssfSheet =null;
public static HSSFRow hssfRow = null;
public static XSSFWorkbook xb = null;
public static XSSFSheet xssfSheet =null;
public static XSSFRow xssfRow =null;
public static String qlyStringArr[][] = new String[1000][1000];
public static String otherStringArr[][] = new String[1000][1000];
static Map<String,Integer > qlycontent = new HashMap<String,Integer >();
static Map<String,Integer > othercontent = new HashMap<String,Integer >();
@Override
public Map<String,Object> readExcel(String path, String flag) {
int counter = 0;
try {
InputStream is = new FileInputStream(new File(path));

		xb = new XSSFWorkbook(is);
		xssfSheet = xb.getSheetAt(0);
		// 得到总行数
		int rowNum = xssfSheet.getLastRowNum();
		xssfRow = xssfSheet.getRow(1);

		int colNum = xssfRow.getPhysicalNumberOfCells();
		// 正文内容应该从第二行开始,第一行为表头内容
		if(&quot;qly&quot;.equals(flag)){
			Map&lt;String,Object&gt;qlyMap = new HashMap&lt;String, Object&gt;();
			for (int i = 1; i &lt; rowNum; i++) {
				xssfRow = xssfSheet.getRow(i);
				counter = 0;
				while (counter &lt; colNum) {
					qlyStringArr[i][counter] = get2007StringCellValue(
							xssfRow.getCell((short) counter)).trim();
					counter++;
				}
			}
			qlycontent.put(&quot;colNum&quot;, counter);
			qlycontent.put(&quot;rowNum&quot;, rowNum);
			qlyMap.put(&quot;qlyStringArr&quot;, qlyStringArr);
			qlyMap.put(&quot;qlycontent&quot;, qlycontent);
			return  qlyMap;
		}
		if(&quot;other&quot;.equals(flag)){
			Map&lt;String,Object&gt;otherMap = new HashMap&lt;String, Object&gt;();
			for (int i = 1; i &lt; rowNum; i++) {
				xssfRow = xssfSheet.getRow(i);
				counter = 0;
				while (counter &lt; colNum) {
					otherStringArr[i][counter] = get2007StringCellValue(
							xssfRow.getCell((short) counter)).trim();
					counter++;
				}
			}
			othercontent.put(&quot;colNum&quot;, counter);
			othercontent.put(&quot;rowNum&quot;, rowNum);
			otherMap.put(&quot;othercontent&quot;, othercontent);
			otherMap.put(&quot;otherStringArr&quot;, otherStringArr);
			return otherMap;
		}
		
	} catch (FileNotFoundException e) {
		e.printStackTrace();
	} catch (IOException e) {
		e.printStackTrace();
	}
	return null;
}
private static String get2007StringCellValue(XSSFCell cell) {
	String strCell = &quot;&quot;;
	if (null == cell) {
		return &quot;&quot;;
	}
	switch (cell.getCellType()) {
	case XSSFCell.CELL_TYPE_STRING:
		strCell = cell.getStringCellValue();
		break;
	case XSSFCell.CELL_TYPE_NUMERIC:
		//使用DecimalFormat类对科学计数法格式的数字进行格式化
		
		DecimalFormat df = new DecimalFormat(&quot;#&quot;);
		String str = String.valueOf(cell.getNumericCellValue());
		if(ReportUtil.isNumber(str)){
			strCell =  df.format(cell.getNumericCellValue());
		}else{
			strCell = str;
		}
		
		break;
	case XSSFCell.CELL_TYPE_BOOLEAN:
		strCell = String.valueOf(cell.getBooleanCellValue());
		break;
	case XSSFCell.CELL_TYPE_BLANK:
		strCell = &quot;&quot;;
		break;
	default:
		strCell = &quot;&quot;;
		break;
	}
	if (strCell.equals(&quot;&quot;) || null == strCell) {
		return &quot;&quot;;
	}
	return strCell;

}

}

 

 

CompareExcel方法,比较算法在这里

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.qly.report.operate.facade.ICompareExcel;
import com.qly.report.util.ReportUtil;
/**

  • by guop 2013-7-12 上午 9:11:06

  • 比较 excel
    */
    public class CompareExcelImpl implements ICompareExcel{

    @Override
    public Map<String ,Object> compareExcel(String [][] qlyString, String [][] otherString,
    int[] qlycolumn, int[] othercolumn,
    Map<String,Integer> qlycontent, Map<String,Integer> othercontent,
    int qlyflag, int otherflag, int qlystartRow, int otherstartRow,
    String outReportPath) {

     // 761报表的行数
     int qlyRowNum = qlycontent.get(&quot;rowNum&quot;);
     // 761报表的列数
     int otherRowNum = othercontent.get(&quot;rowNum&quot;);
    
     List&lt;Map&lt;String, Integer&gt;&gt; listErrorStr = new ArrayList&lt;Map&lt;String, Integer&gt;&gt;();
     // 在这里比较下,用a表的标记字段扫描b表从1开始,0为表头
     for (int i = qlystartRow; i &lt; qlyRowNum; i++) {
     	boolean boolflag = false;
     	boolean boolother = true;
     	Map&lt;String, Integer&gt; errorMap = new HashMap&lt;String, Integer&gt;();
    
     	for (int j = otherstartRow; j &lt; otherRowNum; j++) {
     		// 按照配置的标记列,如761中的票号,和腾邦的票号
     		// 表a中的第一行某字段,遍历表b中的行中的这一字段,看是否相同0
     		if (ReportUtil.handleSpecialCharacter(qlyString[i][qlyflag].trim()).equals(ReportUtil.handleSpecialCharacter(otherString[j][otherflag].trim()))) {
     			boolother = false;
     			int rowother = j;
     			// 在寻找到b表中,有相同的字段后,比较其他字段是否相同
     			for (int a1 = 0; a1 &lt; qlycolumn.length;a1++) {
     				if(null == qlyString[i][qlycolumn[a1]] || null == otherString[rowother][othercolumn[a1]]){
     					break;
     				}
    
     				// 当a表和b表中的 票面号相同,比较里面的规定的字段的值是否相同
     					if (!ReportUtil.handleSpecialCharacter(qlyString[i][qlycolumn[a1]].trim()).equals(ReportUtil.handleSpecialCharacter(otherString[rowother][othercolumn[a1]].trim()))) {
     						errorMap.put(&quot;qlylinenumber&quot;, i);
     						errorMap.put(&quot;otherlinenumber&quot;, rowother);
     						listErrorStr.add(errorMap);
     						boolflag = true;
     						break;
     					} 
     				}
    
     			if (boolflag) {
     				break;
     			}
     		}
     	}
     	if (boolother) {
     		errorMap.put(&quot;qlylinenumber&quot;, i);
     		listErrorStr.add(errorMap);
     	}
     }
     //用b表扫描a表的某一个字段
     		// 在这里比较下,用a表的标记字段扫描b表
     for (int i = otherstartRow; i &lt; otherRowNum; i++) {
     	boolean boolother = true;
     	Map&lt;String, Integer&gt; errorMap = new HashMap&lt;String, Integer&gt;();
     	for (int j = qlystartRow; j &lt; qlyRowNum; j++) {
    
     		// 按照配置的标记列,如761中的票号,和腾邦的票号
     		// 表a中的第一行某字段,遍历表b中的行中的这一字段,看是否相同0
     		if (ReportUtil.handleSpecialCharacter(qlyString[j][qlyflag].trim()).equals(ReportUtil.handleSpecialCharacter(otherString[i][otherflag].trim()))) {
     			boolother = false;
     			// 在寻找到b表中,有相同的字段后,比较其他字段是否相同
     		}
     	}
     	if (boolother) {
     		errorMap.put(&quot;otherlinenumber&quot;, i);
     		listErrorStr.add(errorMap);
     	}
     }
    
     Map&lt;String,Object&gt; compareMap = new HashMap&lt;String, Object&gt;();
     compareMap.put(&quot;listErrorStr&quot;, listErrorStr);
    

    return compareMap;
    }

}

全部代码在这里,github,请多多指教

  • POI
    23 引用 • 21 回帖
  • Excel
    30 引用 • 28 回帖
  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3190 引用 • 8214 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • someone

    老船长这个不是专栏是博客吧。哈哈。专栏不是这个意思吧。

推荐标签 标签

  • 996
    13 引用 • 200 回帖 • 11 关注
  • Q&A

    提问之前请先看《提问的智慧》,好的问题比好的答案更有价值。

    8447 引用 • 38477 回帖 • 154 关注
  • danl
    146 关注
  • 笔记

    好记性不如烂笔头。

    308 引用 • 793 回帖
  • 禅道

    禅道是一款国产的开源项目管理软件,她的核心管理思想基于敏捷方法 scrum,内置了产品管理和项目管理,同时又根据国内研发现状补充了测试管理、计划管理、发布管理、文档管理、事务管理等功能,在一个软件中就可以将软件研发中的需求、任务、bug、用例、计划、发布等要素有序的跟踪管理起来,完整地覆盖了项目管理的核心流程。

    5 引用 • 15 回帖 • 102 关注
  • V2Ray
    1 引用 • 15 回帖 • 1 关注
  • SpaceVim

    SpaceVim 是一个社区驱动的模块化 vim/neovim 配置集合,以模块的方式组织管理插件以
    及相关配置,为不同的语言开发量身定制了相关的开发模块,该模块提供代码自动补全,
    语法检查、格式化、调试、REPL 等特性。用户仅需载入相关语言的模块即可得到一个开箱
    即用的 Vim-IDE。

    3 引用 • 31 回帖 • 105 关注
  • 安装

    你若安好,便是晴天。

    132 引用 • 1184 回帖 • 1 关注
  • Git

    Git 是 Linux Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

    209 引用 • 358 回帖 • 1 关注
  • 爬虫

    网络爬虫(Spider、Crawler),是一种按照一定的规则,自动地抓取万维网信息的程序。

    106 引用 • 275 回帖 • 1 关注
  • Sandbox

    如果帖子标签含有 Sandbox ,则该帖子会被视为“测试帖”,主要用于测试社区功能,排查 bug 等,该标签下内容不定期进行清理。

    409 引用 • 1246 回帖 • 587 关注
  • Ubuntu

    Ubuntu(友帮拓、优般图、乌班图)是一个以桌面应用为主的 Linux 操作系统,其名称来自非洲南部祖鲁语或豪萨语的“ubuntu”一词,意思是“人性”、“我的存在是因为大家的存在”,是非洲传统的一种价值观,类似华人社会的“仁爱”思想。Ubuntu 的目标在于为一般用户提供一个最新的、同时又相当稳定的主要由自由软件构建而成的操作系统。

    126 引用 • 169 回帖
  • JavaScript

    JavaScript 一种动态类型、弱类型、基于原型的直译式脚本语言,内置支持类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

    728 引用 • 1273 回帖 • 1 关注
  • 面试

    面试造航母,上班拧螺丝。多面试,少加班。

    325 引用 • 1395 回帖 • 1 关注
  • Notion

    Notion - The all-in-one workspace for your notes, tasks, wikis, and databases.

    7 引用 • 40 回帖
  • TensorFlow

    TensorFlow 是一个采用数据流图(data flow graphs),用于数值计算的开源软件库。节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。

    20 引用 • 19 回帖 • 1 关注
  • 创造

    你创造的作品可能会帮助到很多人,如果是开源项目的话就更赞了!

    178 引用 • 997 回帖
  • Latke

    Latke 是一款以 JSON 为主的 Java Web 框架。

    71 引用 • 535 回帖 • 789 关注
  • 酷鸟浏览器

    安全 · 稳定 · 快速
    为跨境从业人员提供专业的跨境浏览器

    3 引用 • 59 回帖 • 26 关注
  • 七牛云

    七牛云是国内领先的企业级公有云服务商,致力于打造以数据为核心的场景化 PaaS 服务。围绕富媒体场景,七牛先后推出了对象存储,融合 CDN 加速,数据通用处理,内容反垃圾服务,以及直播云服务等。

    27 引用 • 225 回帖 • 163 关注
  • 新人

    让我们欢迎这对新人。哦,不好意思说错了,让我们欢迎这位新人!
    新手上路,请谨慎驾驶!

    52 引用 • 228 回帖
  • Markdown

    Markdown 是一种轻量级标记语言,用户可使用纯文本编辑器来排版文档,最终通过 Markdown 引擎将文档转换为所需格式(比如 HTML、PDF 等)。

    167 引用 • 1520 回帖
  • ZooKeeper

    ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 HBase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。

    59 引用 • 29 回帖 • 14 关注
  • Webswing

    Webswing 是一个能将任何 Swing 应用通过纯 HTML5 运行在浏览器中的 Web 服务器,详细介绍请看 将 Java Swing 应用变成 Web 应用

    1 引用 • 15 回帖 • 637 关注
  • Sillot

    Insights(注意当前设置 master 为默认分支)

    汐洛彖夲肜矩阵(Sillot T☳Converbenk Matrix),致力于服务智慧新彖乄,具有彖乄驱动、极致优雅、开发者友好的特点。其中汐洛绞架(Sillot-Gibbet)基于自思源笔记(siyuan-note),前身是思源笔记汐洛版(更早是思源笔记汐洛分支),是智慧新录乄终端(多端融合,移动端优先)。

    主仓库地址:Hi-Windom/Sillot

    文档地址:sillot.db.sc.cn

    注意事项:

    1. ⚠️ 汐洛仍在早期开发阶段,尚不稳定
    2. ⚠️ 汐洛并非面向普通用户设计,使用前请了解风险
    3. ⚠️ 汐洛绞架基于思源笔记,开发者尽最大努力与思源笔记保持兼容,但无法实现 100% 兼容
    29 引用 • 25 回帖 • 86 关注
  • InfluxDB

    InfluxDB 是一个开源的没有外部依赖的时间序列数据库。适用于记录度量,事件及实时分析。

    2 引用 • 76 关注
  • VirtualBox

    VirtualBox 是一款开源虚拟机软件,最早由德国 Innotek 公司开发,由 Sun Microsystems 公司出品的软件,使用 Qt 编写,在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。

    10 引用 • 2 回帖