http://thinkgem.iteye.com/blog/2150940

前端时间写了注解方式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的读写操作相关推荐

  1. 使用poi导出大量数据到excel遇到的问题

    最近在工作遇到利用poi导出大量数据到excel并提供下载的运用场景,并遇到了一个问题,当数据量过大时(几十万),后台在进行数据写入excel中的过程会非常耗时,导致迟迟没有响应前台,结果数据还没导完 ...

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

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

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

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

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

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

  5. python对excel进行读写操作

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

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

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

  7. POI读写超大数据量Excel,解决超过几万行而导致内存溢出的问题(附源码)

    来源:cnblogs.com/swordfall/p/8298386.html 1. Excel2003与Excel2007 两个版本的最大行数和列数不同,2003版最大行数是65536行,最大列数是 ...

  8. poi方式写入数据到Excel

    在java数据库编程中,常常会用到向excel中读写数据,一方面可以将数据从数据库导出到Excel,进行数据展示,另一方面可以批量的向数据库插入多条数据,这对于软件开发是必不可少的,今天先介绍如何使用 ...

  9. java POI 写入百万数据到 excel

    .xls文件只支持6w+的数据写入 .xlsx文件只支持104w+数据的写入 在java中jxl工具类只能操作.xls的文件,不能操作.xlsx的文件 POI工具类能够支持.xlsx的文件操作. ex ...

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

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

最新文章

  1. 基于C#局域网语音聊天
  2. CV之face_recognition:Py之face_recognition库安装、介绍、使用方法详细攻略
  3. Android系统进程Zygote启动过程的源代码分析
  4. 【连载】如何掌握openGauss数据库核心技术?秘诀五:拿捏数据库安全(2)
  5. windows域策略——配置组策略刷新间隔
  6. 求数组中k个数的所有组合
  7. 彻底放弃没落的MFC,对新人的忠告! by FreeWick
  8. MySQL 中的数据类型介绍
  9. 如果面试官问您还有什么问题要问的,应该如何巧妙的回答才算合适呢?
  10. word参考文献乱码问题
  11. Ubuntu 重新安装声卡驱动
  12. 6月中国最美的地方!对的时间就要去对的地方!
  13. 前端工程师项目能力精选文章50篇
  14. 看懂Azure DevOps燃尽图(Burndown Chart)
  15. python 语音识别培训使用Python和Keras创建简单语音识别引擎
  16. 2022P气瓶充装判断题及在线模拟考试
  17. JS 数组动态添加键值对
  18. HTML第一周学习笔记(标题重置版)
  19. 深度学习 一 :使用BERT做 NLP分类任务
  20. 埃森哲杯第十六届上海大学程序设计联赛春季赛暨上海高校金马五校赛H题小Y与多米诺骨牌(线段树优化dp)

热门文章

  1. 解决Mac电脑在启动时出现空白屏幕情况的解决方法
  2. 使用Palette来对图片进行颜色提取
  3. 用Open Images Dataset V6制作yolo训练数据集(darknet版本)
  4. Ubuntu18.04 上 phpvirtualbox 折腾记
  5. 完美解决PHP、AJAX跨域问题
  6. octave c++函数中调用fortran77子程序
  7. NO5 grep-head-tail命令
  8. Groovy模板引擎
  9. 记一次失败的电话面试
  10. ReactiveCocoa MVVM 学习总结二