前端时间写了注解方式Excel的读取和写入,它是根据注解完成Excel的操作,虽说支持大数据,但对于超大数据就无能为力了,因为它的读写期间都是将所有数据放入系统内存的,除非你有超大的内存。

因项目需要对超大数据的Excel读写操作,于是网上找了个超大数据的读写代码,这个不需要太大内存。并对此进行了简单的修改。

原理如下:

Excel超大数据读取:抽象Excel2007读取器,excel2007的底层数据结构是xml文件,采用SAX的事件驱动的方法解析 xml,需要继承DefaultHandler,在遇到文件内容时,事件会触发,这种做法可以大大降低内存的耗费,特别使用于大数据量的文件。

Excel超大数据写入:抽象excel2007读入器,先构建.xlsx一张模板,改写模板中的sheet.xml, 使用这种方法 写入.xlsx文件,不需要太大的内存。

先看调用示例:

Java代码  
  1. String file = "E:/导入测试数据.xlsx";
  2. ExcelReader reader = new ExcelReader() {
  3. public void getRows(int sheetIndex, int curRow, List<String> rowList) {
  4. System.out.println("Sheet:" + sheetIndex + ", Row:" + curRow + ", Data:" +rowList);
  5. }
  6. };
  7. reader.process(file, 1);
Java代码  
  1. String file = "E:/导出测试数据.xlsx";
  2. ExcelWriter writer = new ExcelWriter() {
  3. public void generate() throws Exception {
  4. // 电子表格开始
  5. this.beginSheet();
  6. for (int rownum = 0; rownum < 100; rownum++) {
  7. // 插入新行
  8. this.insertRow(rownum);
  9. // 建立新单元格,索引值从0开始,表示第一列
  10. this.createCell(0, "第 " + rownum + " 行");
  11. this.createCell(1, 34343.123456789);
  12. this.createCell(2, "23.67%");
  13. this.createCell(3, "12:12:23");
  14. this.createCell(4, "2014-10-11 12:12:23");
  15. this.createCell(5, "true");
  16. this.createCell(6, "false");
  17. // 结束行
  18. this.endRow();
  19. }
  20. // 电子表格结束
  21. this.endSheet();
  22. }
  23. };
  24. writer.process(file);
  25. }

这里只展示了对数据的读取和写入,如果正式保存到数据库时建议读取一部分(如100条)再写入一次数据库,尽量不要读取一条就写入一条,这样会非常耗费资源。

源代码如下:

Java代码  
  1. import java.io.InputStream;
  2. import java.math.BigDecimal;
  3. import java.text.SimpleDateFormat;
  4. import java.util.ArrayList;
  5. import java.util.Date;
  6. import java.util.Iterator;
  7. import java.util.List;
  8. import org.apache.poi.hssf.usermodel.HSSFDateUtil;
  9. import org.apache.poi.openxml4j.opc.OPCPackage;
  10. import org.apache.poi.xssf.eventusermodel.XSSFReader;
  11. import org.apache.poi.xssf.model.SharedStringsTable;
  12. import org.apache.poi.xssf.usermodel.XSSFRichTextString;
  13. import org.xml.sax.Attributes;
  14. import org.xml.sax.InputSource;
  15. import org.xml.sax.SAXException;
  16. import org.xml.sax.XMLReader;
  17. import org.xml.sax.helpers.DefaultHandler;
  18. import org.xml.sax.helpers.XMLReaderFactory;
  19. /**
  20. * Excel超大数据读取,抽象Excel2007读取器,excel2007的底层数据结构是xml文件,采用SAX的事件驱动的方法解析
  21. * xml,需要继承DefaultHandler,在遇到文件内容时,事件会触发,这种做法可以大大降低 内存的耗费,特别使用于大数据量的文件。
  22. * @version 2014-9-2
  23. */
  24. public abstract class ExcelReader extends DefaultHandler {
  25. // 共享字符串表
  26. private SharedStringsTable sst;
  27. // 上一次的内容
  28. private String lastContents;
  29. private boolean nextIsString;
  30. private int sheetIndex = -1;
  31. private List<String> rowList = new ArrayList<String>();
  32. // 当前行
  33. private int curRow = 0;
  34. // 当前列
  35. private int curCol = 0;
  36. // 日期标志
  37. private boolean dateFlag;
  38. // 数字标志
  39. private boolean numberFlag;
  40. private boolean isTElement;
  41. /**
  42. * 遍历工作簿中所有的电子表格
  43. * @param filename
  44. * @throws Exception
  45. */
  46. public void process(String filename) throws Exception {
  47. OPCPackage pkg = OPCPackage.open(filename);
  48. XSSFReader r = new XSSFReader(pkg);
  49. SharedStringsTable sst = r.getSharedStringsTable();
  50. XMLReader parser = fetchSheetParser(sst);
  51. Iterator<InputStream> sheets = r.getSheetsData();
  52. while (sheets.hasNext()) {
  53. curRow = 0;
  54. sheetIndex++;
  55. InputStream sheet = sheets.next();
  56. InputSource sheetSource = new InputSource(sheet);
  57. parser.parse(sheetSource);
  58. sheet.close();
  59. }
  60. }
  61. /**
  62. * 只遍历一个电子表格,其中sheetId为要遍历的sheet索引,从1开始,1-3
  63. * @param filename
  64. * @param sheetId
  65. * @throws Exception
  66. */
  67. public void process(String filename, int sheetId) throws Exception {
  68. OPCPackage pkg = OPCPackage.open(filename);
  69. XSSFReader r = new XSSFReader(pkg);
  70. SharedStringsTable sst = r.getSharedStringsTable();
  71. XMLReader parser = fetchSheetParser(sst);
  72. // 根据 rId# 或 rSheet# 查找sheet
  73. InputStream sheet2 = r.getSheet("rId" + sheetId);
  74. sheetIndex++;
  75. InputSource sheetSource = new InputSource(sheet2);
  76. parser.parse(sheetSource);
  77. sheet2.close();
  78. }
  79. public XMLReader fetchSheetParser(SharedStringsTable sst)
  80. throws SAXException {
  81. XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
  82. this.sst = sst;
  83. parser.setContentHandler(this);
  84. return parser;
  85. }
  86. public void startElement(String uri, String localName, String name,
  87. Attributes attributes) throws SAXException {
  88. //      System.out.println("startElement: " + localName + ", " + name + ", " + attributes);
  89. // c => 单元格
  90. if ("c".equals(name)) {
  91. // 如果下一个元素是 SST 的索引,则将nextIsString标记为true
  92. String cellType = attributes.getValue("t");
  93. if ("s".equals(cellType)) {
  94. nextIsString = true;
  95. } else {
  96. nextIsString = false;
  97. }
  98. // 日期格式
  99. String cellDateType = attributes.getValue("s");
  100. if ("1".equals(cellDateType)) {
  101. dateFlag = true;
  102. } else {
  103. dateFlag = false;
  104. }
  105. String cellNumberType = attributes.getValue("s");
  106. if ("2".equals(cellNumberType)) {
  107. numberFlag = true;
  108. } else {
  109. numberFlag = false;
  110. }
  111. }
  112. // 当元素为t时
  113. if ("t".equals(name)) {
  114. isTElement = true;
  115. } else {
  116. isTElement = false;
  117. }
  118. // 置空
  119. lastContents = "";
  120. }
  121. public void endElement(String uri, String localName, String name)
  122. throws SAXException {
  123. //      System.out.println("endElement: " + localName + ", " + name);
  124. // 根据SST的索引值的到单元格的真正要存储的字符串
  125. // 这时characters()方法可能会被调用多次
  126. if (nextIsString) {
  127. try {
  128. int idx = Integer.parseInt(lastContents);
  129. lastContents = new XSSFRichTextString(sst.getEntryAt(idx))
  130. .toString();
  131. } catch (Exception e) {
  132. }
  133. }
  134. // t元素也包含字符串
  135. if (isTElement) {
  136. String value = lastContents.trim();
  137. rowList.add(curCol, value);
  138. curCol++;
  139. isTElement = false;
  140. // v => 单元格的值,如果单元格是字符串则v标签的值为该字符串在SST中的索引
  141. // 将单元格内容加入rowlist中,在这之前先去掉字符串前后的空白符
  142. } else if ("v".equals(name)) {
  143. String value = lastContents.trim();
  144. value = value.equals("") ? " " : value;
  145. try {
  146. // 日期格式处理
  147. if (dateFlag) {
  148. Date date = HSSFDateUtil.getJavaDate(Double.valueOf(value));
  149. SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
  150. value = dateFormat.format(date);
  151. }
  152. // 数字类型处理
  153. if (numberFlag) {
  154. BigDecimal bd = new BigDecimal(value);
  155. value = bd.setScale(3, BigDecimal.ROUND_UP).toString();
  156. }
  157. } catch (Exception e) {
  158. // 转换失败仍用读出来的值
  159. }
  160. rowList.add(curCol, value);
  161. curCol++;
  162. } else {
  163. // 如果标签名称为 row ,这说明已到行尾,调用 optRows() 方法
  164. if (name.equals("row")) {
  165. getRows(sheetIndex + 1, curRow, rowList);
  166. rowList.clear();
  167. curRow++;
  168. curCol = 0;
  169. }
  170. }
  171. }
  172. public void characters(char[] ch, int start, int length)
  173. throws SAXException {
  174. // 得到单元格内容的值
  175. lastContents += new String(ch, start, length);
  176. }
  177. /**
  178. * 获取行数据回调
  179. * @param sheetIndex
  180. * @param curRow
  181. * @param rowList
  182. */
  183. public abstract void getRows(int sheetIndex, int curRow, List<String> rowList);
  184. /**
  185. * 测试方法
  186. */
  187. public static void main(String[] args) throws Exception {
  188. String file = "E:/导入测试数据.xlsx";
  189. ExcelReader reader = new ExcelReader() {
  190. public void getRows(int sheetIndex, int curRow, List<String> rowList) {
  191. System.out.println("Sheet:" + sheetIndex + ", Row:" + curRow + ", Data:" +rowList);
  192. }
  193. };
  194. reader.process(file, 1);
  195. }
  196. }
Java代码  
  1. import java.io.File;
  2. import java.io.FileInputStream;
  3. import java.io.FileOutputStream;
  4. import java.io.FileWriter;
  5. import java.io.IOException;
  6. import java.io.InputStream;
  7. import java.io.OutputStream;
  8. import java.io.Writer;
  9. import java.util.Calendar;
  10. import java.util.Enumeration;
  11. import java.util.zip.ZipEntry;
  12. import java.util.zip.ZipFile;
  13. import java.util.zip.ZipOutputStream;
  14. import org.apache.poi.hssf.util.CellReference;
  15. import org.apache.poi.ss.usermodel.DateUtil;
  16. import org.apache.poi.xssf.usermodel.XSSFSheet;
  17. import org.apache.poi.xssf.usermodel.XSSFWorkbook;
  18. /**
  19. * Excel超大数据写入,抽象excel2007读入器,先构建.xlsx一张模板,改写模板中的sheet.xml,
  20. * 使用这种方法 写入.xlsx文件,不需要太大的内存
  21. * @version 2014-9-2
  22. */
  23. public abstract class ExcelWriter {
  24. private SpreadsheetWriter sw;
  25. /**
  26. * 写入电子表格的主要流程
  27. *
  28. * @param fileName
  29. * @throws Exception
  30. */
  31. public void process(String fileName) throws Exception {
  32. // 建立工作簿和电子表格对象
  33. XSSFWorkbook wb = new XSSFWorkbook();
  34. XSSFSheet sheet = wb.createSheet("sheet1");
  35. // 持有电子表格数据的xml文件名 例如 /xl/worksheets/sheet1.xml
  36. String sheetRef = sheet.getPackagePart().getPartName().getName();
  37. // 保存模板
  38. FileOutputStream os = new FileOutputStream("template.xlsx");
  39. wb.write(os);
  40. os.close();
  41. // 生成xml文件
  42. File tmp = File.createTempFile("sheet", ".xml");
  43. Writer fw = new FileWriter(tmp);
  44. sw = new SpreadsheetWriter(fw);
  45. generate();
  46. fw.close();
  47. // 使用产生的数据替换模板
  48. File templateFile = new File("template.xlsx");
  49. FileOutputStream out = new FileOutputStream(fileName);
  50. substitute(templateFile, tmp, sheetRef.substring(1), out);
  51. out.close();
  52. // 删除文件之前调用一下垃圾回收器,否则无法删除模板文件
  53. System.gc();
  54. // 删除临时模板文件
  55. if (templateFile.isFile() && templateFile.exists()) {
  56. templateFile.delete();
  57. }
  58. }
  59. /**
  60. * 类使用者应该使用此方法进行写操作
  61. *
  62. * @throws Exception
  63. */
  64. public abstract void generate() throws Exception;
  65. public void beginSheet() throws IOException {
  66. sw.beginSheet();
  67. }
  68. public void insertRow(int rowNum) throws IOException {
  69. sw.insertRow(rowNum);
  70. }
  71. public void createCell(int columnIndex, String value) throws IOException {
  72. sw.createCell(columnIndex, value, -1);
  73. }
  74. public void createCell(int columnIndex, double value) throws IOException {
  75. sw.createCell(columnIndex, value, -1);
  76. }
  77. public void endRow() throws IOException {
  78. sw.endRow();
  79. }
  80. public void endSheet() throws IOException {
  81. sw.endSheet();
  82. }
  83. /**
  84. *
  85. * @param zipfile
  86. *            the template file
  87. * @param tmpfile
  88. *            the XML file with the sheet data
  89. * @param entry
  90. *            the name of the sheet entry to substitute, e.g.
  91. *            xl/worksheets/sheet1.xml
  92. * @param out
  93. *            the stream to write the result to
  94. */
  95. private static void substitute(File zipfile, File tmpfile, String entry,
  96. OutputStream out) throws IOException {
  97. ZipFile zip = new ZipFile(zipfile);
  98. ZipOutputStream zos = new ZipOutputStream(out);
  99. @SuppressWarnings("unchecked")
  100. Enumeration<ZipEntry> en = (Enumeration<ZipEntry>) zip.entries();
  101. while (en.hasMoreElements()) {
  102. ZipEntry ze = en.nextElement();
  103. if (!ze.getName().equals(entry)) {
  104. zos.putNextEntry(new ZipEntry(ze.getName()));
  105. InputStream is = zip.getInputStream(ze);
  106. copyStream(is, zos);
  107. is.close();
  108. }
  109. }
  110. zos.putNextEntry(new ZipEntry(entry));
  111. InputStream is = new FileInputStream(tmpfile);
  112. copyStream(is, zos);
  113. is.close();
  114. zos.close();
  115. }
  116. private static void copyStream(InputStream in, OutputStream out)
  117. throws IOException {
  118. byte[] chunk = new byte[1024];
  119. int count;
  120. while ((count = in.read(chunk)) >= 0) {
  121. out.write(chunk, 0, count);
  122. }
  123. }
  124. /**
  125. * 在写入器中写入电子表格
  126. *
  127. */
  128. public static class SpreadsheetWriter {
  129. private final Writer _out;
  130. private int _rownum;
  131. private static String LINE_SEPARATOR = System
  132. .getProperty("line.separator");
  133. public SpreadsheetWriter(Writer out) {
  134. _out = out;
  135. }
  136. public void beginSheet() throws IOException {
  137. _out.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
  138. + "<worksheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">");
  139. _out.write("<sheetData>" + LINE_SEPARATOR);
  140. }
  141. public void endSheet() throws IOException {
  142. _out.write("</sheetData>");
  143. _out.write("</worksheet>");
  144. }
  145. /**
  146. * 插入新行
  147. *
  148. * @param rownum
  149. *            以0开始
  150. */
  151. public void insertRow(int rownum) throws IOException {
  152. _out.write("<row r=\"" + (rownum + 1) + "\">" + LINE_SEPARATOR);
  153. this._rownum = rownum;
  154. }
  155. /**
  156. * 插入行结束标志
  157. */
  158. public void endRow() throws IOException {
  159. _out.write("</row>" + LINE_SEPARATOR);
  160. }
  161. /**
  162. * 插入新列
  163. *
  164. * @param columnIndex
  165. * @param value
  166. * @param styleIndex
  167. * @throws IOException
  168. */
  169. public void createCell(int columnIndex, String value, int styleIndex)
  170. throws IOException {
  171. String ref = new CellReference(_rownum, columnIndex)
  172. .formatAsString();
  173. _out.write("<c r=\"" + ref + "\" t=\"inlineStr\"");
  174. if (styleIndex != -1)
  175. _out.write(" s=\"" + styleIndex + "\"");
  176. _out.write(">");
  177. _out.write("<is><t>" + encoderXML(value) + "</t></is>");
  178. _out.write("</c>");
  179. }
  180. public void createCell(int columnIndex, String value)
  181. throws IOException {
  182. createCell(columnIndex, value, -1);
  183. }
  184. public void createCell(int columnIndex, double value, int styleIndex)
  185. throws IOException {
  186. String ref = new CellReference(_rownum, columnIndex)
  187. .formatAsString();
  188. _out.write("<c r=\"" + ref + "\" t=\"n\"");
  189. if (styleIndex != -1)
  190. _out.write(" s=\"" + styleIndex + "\"");
  191. _out.write(">");
  192. _out.write("<v>" + value + "</v>");
  193. _out.write("</c>");
  194. }
  195. public void createCell(int columnIndex, double value)
  196. throws IOException {
  197. createCell(columnIndex, value, -1);
  198. }
  199. public void createCell(int columnIndex, Calendar value, int styleIndex)
  200. throws IOException {
  201. createCell(columnIndex, DateUtil.getExcelDate(value, false),
  202. styleIndex);
  203. }
  204. }
  205. // XML Encode
  206. private static final String[] xmlCode = new String[256];
  207. static {
  208. // Special characters
  209. xmlCode['\''] = "'";
  210. xmlCode['\"'] = "\""; // double quote
  211. xmlCode['&'] = "&"; // ampersand
  212. xmlCode['<'] = "<"; // lower than
  213. xmlCode['>'] = ">"; // greater than
  214. }
  215. /**
  216. * <p>
  217. * Encode the given text into xml.
  218. * </p>
  219. *
  220. * @param string
  221. *            the text to encode
  222. * @return the encoded string
  223. */
  224. public static String encoderXML(String string) {
  225. if (string == null)
  226. return "";
  227. int n = string.length();
  228. char character;
  229. String xmlchar;
  230. StringBuffer buffer = new StringBuffer();
  231. // loop over all the characters of the String.
  232. for (int i = 0; i < n; i++) {
  233. character = string.charAt(i);
  234. // the xmlcode of these characters are added to a StringBuffer
  235. // one by one
  236. try {
  237. xmlchar = xmlCode[character];
  238. if (xmlchar == null) {
  239. buffer.append(character);
  240. } else {
  241. buffer.append(xmlCode[character]);
  242. }
  243. } catch (ArrayIndexOutOfBoundsException aioobe) {
  244. buffer.append(character);
  245. }
  246. }
  247. return buffer.toString();
  248. }
  249. /**
  250. * 测试方法
  251. */
  252. public static void main(String[] args) throws Exception {
  253. String file = "E:/导出测试数据.xlsx";
  254. ExcelWriter writer = new ExcelWriter() {
  255. public void generate() throws Exception {
  256. // 电子表格开始
  257. this.beginSheet();
  258. for (int rownum = 0; rownum < 100; rownum++) {
  259. // 插入新行
  260. this.insertRow(rownum);
  261. // 建立新单元格,索引值从0开始,表示第一列
  262. this.createCell(0, "第 " + rownum + " 行");
  263. this.createCell(1, 34343.123456789);
  264. this.createCell(2, "23.67%");
  265. this.createCell(3, "12:12:23");
  266. this.createCell(4, "2014-10-11 12:12:23");
  267. this.createCell(5, "true");
  268. this.createCell(6, "false");
  269. // 结束行
  270. this.endRow();
  271. }
  272. // 电子表格结束
  273. this.endSheet();
  274. }
  275. };
  276. writer.process(file);
  277. }
  278. }

POI实现超大数据的Excel的读写操作,支持Excel最大行数。相关推荐

  1. 使用Easyexcel对Excel进行读写操作

    1.概述 EasyExcel是一个基于Java的简单.省内存的读写Excel的开源项目.在尽可能节约内存的情况下支持读写百M的Excel. github地址:GitHub - alibaba/easy ...

  2. python写表格_使用Python对Excel进行读写操作

    学习Python的过程中,我们会遇到Excel的读写问题.这时,我们可以使用xlwt模块将数据写入Excel表格中,使用xlrd模块从Excel中读取数据.下面我们介绍如何实现使用Python对Exc ...

  3. excel调用python编程-使用Python对Excel进行读写操作

    学习Python的过程中,我们会遇到Excel的读写问题.这时,我们可以使用xlwt模块将数据写入Excel表格中,使用xlrd模块从Excel中读取数据.下面我们介绍如何实现使用Python对Exc ...

  4. python对excel进行读写操作

    python对excel进行读写操作 欢迎使用Markdown编辑器 一.安装库 1.安装xlrd模块 2.安装xlwt模块 2.安装openpyxl模块 补充(多个python版本) 二.使用介绍 ...

  5. 利用python对Excel进行读写操作

    最近在写论文做实验的过程中,利用python自带的matplotlib库进行绘画,但是尝尝会出现在程序跑完后发现图片里面有一些小细节没有注意到,导致整个代码重新跑.代码重新跑短则几小时,长则就不好说了 ...

  6. excel怎么一次填充到选择行数

    转载自:excel怎么一次填充到选择行数 软件版本:Office2007 方法如下: 1.将B1中的公式往下填充到第20行: 2.在名称框中输入B1:B20,然后按下回车,选中B1:B20区域: 3. ...

  7. java-使用POI对Excel进行读写操作

    HSSFWorkBook:操作2003版本以前的(包括2003版本),扩展名.xls,该类在org.apache.poi:poi中 XSSFWorkBook:操作2007版本以后的(包括2007版本) ...

  8. EasyExcel java实现excel简单读写操作(快速上手,复制粘贴即可)

    EasyExcel是一个基于Java的简单.省内存的读写Excel的开源项目. https://github.com/alibaba/easyexcel 一.导入maven坐标 Lombok可选,不导 ...

  9. pythonexcel操作总结_python中常见关于Excel表格读写操作

    最近在写项目,刚好要运用到excel表格的一些读写,顺便总结一下我以前学过的几个关于表格的操作.在写项目中,经常会见到页面中数据导出到表格中,同时,也会有经常在表格中填写测试用例,然后获取数据来做自动 ...

  10. Excel简单读写操作

    Excel的程序集来自与Excel,所以如果在C#中遇到自己不懂的东西,不妨打开Excel的VBA工具,录制宏然后查看其中的操作代码.虽然好多的功能都通过import特征引入到C#中,但是深层的属性和 ...

最新文章

  1. springmvc处理流程
  2. Uuntu16.04重装后u盘不识别问题解决
  3. 红帽子linux安装ftp,Linux 安装 vsftpd ftp软件包
  4. 搜索引擎的十大秘密(收藏)
  5. Cordova Android 禁用长按选中功能
  6. java 接收前台富文本_前后端分离ueditor富文本编辑器的使用-Java版本
  7. Qt 萤石云 /萤石云官方Demo二次开发 Qt 5.12.3 / H5/萤石云官方Demo
  8. linux安装酷q机器人,docker一键安装酷Q搭建个人QQ机器人
  9. jdk下载/Linux64位 jdk1.8 jdk-8u161下载
  10. 使用winserver2003配置DNS服务器
  11. 开路电压法与电流积分法
  12. 关于阿里云的一键部署工具【飞流】的使用(详细)
  13. 关于高性能的MIMO技术的实现方法介绍
  14. PS零基础学习教程(一)
  15. 华为机试真题 C++ 实现【最短木板长度】【100%通过率】【2022.11 Q4 新题】
  16. “搜索大战”正式打响,微软发布ChatGPT版搜索引擎和浏览器
  17. Android布局原理与优化
  18. 【sql注入】二次注入
  19. “拼多多”惊爆重大 Bug!程序员的眼泪,羊毛党的狂欢
  20. 如何注销计算机管理员用户,电脑管理员注销了开电脑要密码 电脑被注销了要密码怎么办...

热门文章

  1. 这几个专业考上研再考公务员更有优势!
  2. 输入成绩等级c语言,C语言实现学生成绩等级划分的方法实例
  3. Android Studio实现百度地图定位(显示经纬度和地址)
  4. mysql常用知识点
  5. 源码角度了解Skywalking之Skywalking是如何进行JVM监控的
  6. 如何去掉快捷方式上的小箭头
  7. 镜头光晕是如何形成的?
  8. 【LensFlare镜头光晕】Unity3D奇葩实现
  9. 多飞行机器人吊运系统研究进展及挑战
  10. Python Flask 学习笔记 —— 二(路由,视图函数,jinjia2语法)