系统中有一个读取PDF表单的数据,然后将数据按字段解析出来,存储到数据库的功能。实现思路,大致是先获取PDF的流,把数据导入到xml中,然后逐行读取xml的数据。具体的实现代码有点挫...
package com.rb.common.pdf; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactoryConfigurationError; import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.xml.sax.SAXException; import com.itextpdf.text.DocumentException; import com.rb.owk.commons.lang.base.orm.BusinessException; /** * 解析PDF报表T-Q-1,提取报表数据 * * @author HO274509 * */ public class PdfTQ1 { private PdfTQ1() { } private final static Log log = LogFactory.getLog(PdfTQ1.class); // 要解析的PDF public static final String RESOURCE = "/pdf/T-Q-1.pdf";// classpath相对路径 // 要填充PDF的XML数据来源 public static final String XMLDATA = "T-Q-1.xml"; // 填充之前的PDF public static final String SOURCE = "T-Q-1.pdf"; // 填充之后的PDF public static final String RESULT = "T-Q-1_fill.pdf"; /** * 打印pdf数据 * * @throws TransformerException * @throws TransformerFactoryConfigurationError * @throws SAXException * @throws ParserConfigurationException * @throws IOException */ public static void printPdfData() throws IOException, ParserConfigurationException, SAXException, TransformerFactoryConfigurationError, TransformerException { /** * 获取xml格式数据 */ InputStream in = PdfTQ4.class.getResourceAsStream(RESOURCE); // 另一种获取方法 PdfUtil.getXFAData(RESOURCE, file); ByteArrayOutputStream os = PdfUtil.getXFAData(in); ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); /** * 使用dom4j解析xml,获取数据 */ SAXReader xmlReader = new SAXReader(); org.dom4j.Document document = null; try { // document = xmlReader.read(xml); document = xmlReader.read(is); } catch (org.dom4j.DocumentException e) { // TODO Auto-generated catch block e.printStackTrace(); log.error("log4j解析出错"); } // 根节点 Element root = document.getRootElement(); // 表单根节点 Element element = root.element("T_Q_1"); // 填报人相关信息 log.info("-----填报人信息-----"); log.info(String.format("填报部门:%s", element.element("FILLIN_DEPT") .getText())); log.info(String.format("填报人:%s", element.element("FILLIN_PERSON") .getText())); log.info(String.format("联系电话:%s", element.element("TELEPHONE") .getText())); log.info(String.format("责任人:%s", element.element("RES_PERSON") .getText())); log.info("-----重要信息系统的停止服务情况-----"); // 表单TQ1001数据根节点:停止服务性质为非预期停止服务的系统停止服务情况 Iterator<Element> it_TQ1001 = element.elementIterator("TQ1001"); int index = 1; while (it_TQ1001.hasNext()) {// 打印非预期停止服务的系统停止服务情况 Element TQ1001 = it_TQ1001.next(); log.info("停止服务性质:非预期停止服务"); log.info(String.format("序号:%s", index)); log .info(String.format("信息系统:%s", TQ1001.element("COL1") .getText())); if (StringUtils.isNotEmpty(TQ1001.elementText("COL2"))) { log.info(String.format("备注:%s", TQ1001.elementText("COL2"))); } log.info(String.format("停止服务原因:%s", TQ1001.elementText("COL3"))); if (StringUtils.isNotEmpty(TQ1001.elementText("COL4"))) { log.info(String.format("备注:%s", TQ1001.elementText("COL4"))); } log.info(String.format("事件等级:%s", TQ1001.elementText("COL5"))); log.info(String.format("起始时间:%s", TQ1001.elementText("COL6"))); log.info(String.format("结束时间:%s", TQ1001.elementText("COL7"))); log.info(String.format("影响范围:%s", TQ1001.elementText("COL8"))); if (StringUtils.isNotEmpty(TQ1001.elementText("COL9"))) { log.info(String.format("个数:%s", TQ1001.elementText("COL9"))); } log.info(String.format("描述:%s", TQ1001.elementText("COL10"))); index++; } // 表单TQ1003数据根节点:停止服务性质为预期停止服务的系统停止服务情况 Iterator<Element> it_TQ1003 = element.elementIterator("TQ1003"); index = 1; while (it_TQ1003.hasNext()) {// 打印停止服务的系统停止服务情况 Element TQ1002 = it_TQ1003.next(); log.info("停止服务性质:预期停止服务"); log.info(String.format("序号:%s", index)); log .info(String.format("信息系统:%s", TQ1002.element("COL1") .getText())); if (StringUtils.isNotEmpty(TQ1002.elementText("COL7"))) { log.info(String.format("备注:%s", TQ1002.elementText("COL7"))); } log.info(String.format("停止服务原因:%s", TQ1002.elementText("COL2"))); if (StringUtils.isNotEmpty(TQ1002.elementText("COL8"))) { log.info(String.format("备注:%s", TQ1002.elementText("COL8"))); } log.info(String.format("起始时间:%s", TQ1002.elementText("COL3"))); log.info(String.format("结束时间:%s", TQ1002.elementText("COL4"))); log.info(String.format("影响范围:%s", TQ1002.elementText("COL10"))); if (StringUtils.isNotEmpty(TQ1002.elementText("COL9"))) { log.info(String.format("个数:%s", TQ1002.elementText("COL9"))); } log.info(String.format("描述:%s", TQ1002.elementText("COL6"))); index++; } // 表单TQ1006数据根节点:核心业务系统重要性能指标 Element TQ1006 = element.element("TQ1006"); log.info("-----核心系统重要性能指标-----"); log.info(String.format("系统可用率-->数量:%s,备注:%s", TQ1006.element("COL1") .getText(), TQ1006.element("COL19").getText())); log.info(String.format("批处理的平均批处理用时-->数量:%s,备注:%s", TQ1006.element( "COL2").getText(), TQ1006.element("COL20").getText())); log.info(String.format("CPU平均使用率-->数量:%s,备注:%s", TQ1006.element("COL3") .getText(), TQ1006.element("COL21").getText())); log.info(String.format("CPU高峰使用率-->数量:%s,备注:%s", TQ1006.element("COL4") .getText(), TQ1006.element("COL22").getText())); log.info(String.format("内存平均使用率-->数量:%s,备注:%s", TQ1006.element("COL5") .getText(), TQ1006.element("COL23").getText())); log.info(String.format("磁盘空间占有率峰值-->数量:%s,备注:%s", TQ1006 .element("COL8").getText(), TQ1006.element("COL26").getText())); log.info(String.format("处理能力---")); log.info(String.format("日均交易笔数-->数量:%s,备注:%s", TQ1006.element("COL9") .getText(), TQ1006.element("COL27").getText())); log.info(String.format("日交易笔数峰值-->数量:%s,备注:%s", TQ1006.element("COL10") .getText(), TQ1006.element("COL28").getText())); log.info(String.format("系统可承载的最大交易并发数-->数量:%s,备注:%s", TQ1006.element( "COL11").getText(), TQ1006.element("COL29").getText())); log.info(String.format("交易成功率-->数量:%s,备注:%s", TQ1006.element("COL12") .getText(), TQ1006.element("COL30").getText())); log.info(String.format("账户数及变动---")); log.info(String.format("公司账户及增减---")); log .info(String.format("公司账户:%s,同比:%s,环比:%s,备注:%s", TQ1006 .element("COL13").getText(), TQ1006.element("COL14") .getText(), TQ1006.element("COL15").getText(), TQ1006 .element("COL31").getText())); log.info(String.format("个人账户及增减---")); log .info(String.format("个人账户:%s,同比:%s,环比:%s,备注:%s", TQ1006 .element("COL16").getText(), TQ1006.element("COL17") .getText(), TQ1006.element("COL18").getText(), TQ1006 .element("COL32").getText())); // 表单TQ1007数据根节点: Element TQ1007 = element.element("TQ1007"); log.info("-----核心网络系统运行情况-----"); log.info(String.format("业务时段平均带宽占用情况(生产中心或中心机房到一级分支机构):%s,备注:%s", TQ1007.elementText("COL1"), TQ1007.elementText("COL15"))); log.info(String.format("业务时段平均带宽占用情况(互联网出口):%s,备注:%s", TQ1007 .elementText("COL2"), TQ1007.elementText("COL16"))); log.info("------网上银行系统运行情况-----"); log.info(String.format("日均交易笔数 数量:%s,备注:%s", TQ1007.elementText("COL3"), TQ1007.elementText("COL17"))); log.info(String.format("日交易笔数峰 数量:%s,备注:%s", TQ1007.elementText("COL4"), TQ1007.elementText("COL18"))); log.info(String.format("系统可承载的最大交易并发 数量:%s,备注:%s", TQ1007 .elementText("COL5"), TQ1007.elementText("COL19"))); log.info(String.format("平均在线并发用户数 数量:%s,备注:%s", TQ1007 .elementText("COL6"), TQ1007.elementText("COL20"))); log.info(String.format("最大在线并发用户数 数量:%s,备注:%s", TQ1007 .elementText("COL7"), TQ1007.elementText("COL21"))); log.info(String.format("系统可承载的最大在线并发数 数量:%s,备注:%s", TQ1007 .elementText("COL8"), TQ1007.elementText("COL22"))); log.info("-----银行卡系统运行情况-----"); log.info(String.format("日均交易笔数 数量:%s,备注:%s", TQ1007.elementText("COL9"), TQ1007.elementText("COL23"))); log.info(String.format("日交易笔数峰数 数量:%s,备注:%s", TQ1007 .elementText("COL10"), TQ1007.elementText("COL24"))); log.info(String.format("系统可承载的最大交易并发数 数量:%s,备注:%s", TQ1007 .elementText("COL11"), TQ1007.elementText("COL25"))); log.info("-----第三方存管系统运行情况-----"); log.info(String.format("日均交易笔数 数量:%s,备注:%s", TQ1007 .elementText("COL12"), TQ1007.elementText("COL26"))); log.info(String.format("日交易笔数峰数 数量:%s,备注:%s", TQ1007 .elementText("COL13"), TQ1007.elementText("COL27"))); log.info(String.format("系统可承载的最大交易并发数 数量:%s,备注:%s", TQ1007 .elementText("COL14"), TQ1007.elementText("COL28"))); log.info("-----数据中心(中心机房)外部异常情况-----"); log.info(String.format("数据中心市电中断次数 数量:%s,备注:%s", TQ1007 .elementText("COL29"), TQ1007.elementText("COL30"))); log.info(String.format("数据中心由于外部原因导致网络通讯中断次数 数量:%s,备注:%s", TQ1007 .elementText("COL31"), TQ1007.elementText("COL32"))); } public static Map<String, Object> getTq1Data() throws IOException, ParserConfigurationException, SAXException, TransformerFactoryConfigurationError, TransformerException { Map<String, Object> tq1Map = new HashMap<String, Object>(); /** * 获取xml格式数据 */ InputStream in = PdfTQ4.class.getResourceAsStream(RESOURCE); ByteArrayOutputStream os = PdfUtil.getXFAData(in); ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); /** * 使用dom4j解析xml,获取数据 */ SAXReader xmlReader = new SAXReader(); org.dom4j.Document document = null; try { // document = xmlReader.read(xml); document = xmlReader.read(is); } catch (org.dom4j.DocumentException e) { // TODO Auto-generated catch block e.printStackTrace(); log.error("log4j解析出错"); } // PDF报表在adobe life cycle表单设计里的结构是:pdf报表标识符-》多个表单-》表单内嵌套有数据节点或者子表单 // PDF报表的XML数据结构(基本一致,就这四层):ROOT-》报表标识节点-》各个表单根节点=》表单内部数据节点 // 根节点 Element root = document.getRootElement(); // 报表根节点 Element element = root.element("T_Q_1"); // 填报人相关信息 tq1Map.put("ISEMPTY", element.elementText("isempty")); tq1Map.put("FILLINDEPT", element.elementText("FILLIN_DEPT")); tq1Map.put("FILLINPERSON", element.elementText("FILLIN_PERSON")); tq1Map.put("TELEPHONE", element.elementText("TELEPHONE")); tq1Map.put("RESPERSON", element.elementText("RES_PERSON")); List<Map<String, Object>> tq1001List = new ArrayList<Map<String,Object>>(); Iterator<Element> it_TQ1001 = element.elementIterator("TQ1001"); int i=1; while (it_TQ1001.hasNext()) { Element tq1001 = it_TQ1001.next(); Iterator<Element> it = tq1001.elementIterator(); Map<String , Object> tq1001Map = new HashMap<String, Object>(); tq1001Map.put("INDEX", i++); while (it.hasNext()) { Element tmp = it.next(); log.info(tmp.getName() + ":::::::" + tmp.getTextTrim()); tq1001Map.put(tmp.getName(), tmp.getTextTrim()); } if(StringUtils.isEmpty((String)tq1001Map.get("COL9"))){ tq1001Map.put("COL9", null); } tq1001List.add(tq1001Map); } tq1Map.put("tq1001List", tq1001List); List<Map<String, Object>> tq1003List = new ArrayList<Map<String,Object>>(); Iterator<Element> it_TQ1003 = element.elementIterator("TQ1003"); i=1; while (it_TQ1003.hasNext()) { Element tq1003 = it_TQ1003.next(); Iterator<Element> it = tq1003.elementIterator(); Map<String , Object> tq1003Map = new HashMap<String, Object>(); tq1003Map.put("INDEX", i++); while (it.hasNext()) { Element tmp = it.next(); log.info(tmp.getName() + ":::::::" + tmp.getTextTrim()); tq1003Map.put(tmp.getName(), tmp.getTextTrim()); } if(StringUtils.isEmpty((String)tq1003Map.get("COL9"))){ tq1003Map.put("COL9", null); } tq1003List.add(tq1003Map); } tq1Map.put("tq1003List", tq1003List); Element TQ1006 = element.element("TQ1006"); Iterator<Element> it_TQ1006 = TQ1006.elementIterator(); while (it_TQ1006.hasNext()) { Element tmp = it_TQ1006.next(); log.info(tmp.getName() + ":::::::" + tmp.getTextTrim()); tq1Map.put(tmp.getName(), tmp.getTextTrim()); } Element TQ1007 = element.element("TQ1007"); Iterator<Element> it_TQ1007 = TQ1007.elementIterator(); while (it_TQ1007.hasNext()) { Element tmp = it_TQ1007.next(); log.info(tmp.getName()+"TQ1007" + ":::::::" + tmp.getTextTrim()); tq1Map.put(tmp.getName()+"TQ1007", tmp.getTextTrim()); } return tq1Map; } /** * 填充成新的pdf之后,会提示pdf文档自创建后被修改,无法再使用扩展功能,这个不知道为什么 * * @throws IOException * @throws DocumentException */ public static void fillPdf() throws IOException, DocumentException { PdfUtil.manipulatePdf(RESOURCE, XMLDATA, RESULT); } public static void main(String[] args) throws IOException, ParserConfigurationException, SAXException, TransformerFactoryConfigurationError, TransformerException, DocumentException, BusinessException { // 提取pdf数据,打印 printPdfData(); // // 以XML形式导出pdf数据 File file = new File(XMLDATA); PdfUtil.getXFAData(RESOURCE, file); // 根据模板和数据生成PDF fillPdf(); } }
调用的pdfutil代码如下
package com.rb.common.pdf; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.PrintStream; import java.util.Set; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import com.itextpdf.text.DocumentException; import com.itextpdf.text.pdf.AcroFields; import com.itextpdf.text.pdf.PdfReader; import com.itextpdf.text.pdf.PdfStamper; import com.itextpdf.text.pdf.XfaForm; /** * pdf工具类 * 依赖ITEXT * @author HO274509 * */ public class PdfUtil { /** * Reads the data from a PDF containing an XFA form. * 解析基于XML Forms Architecture的pdf,导出xml形式的表单数据写入到文件中 * @param src * the original PDF * @param dest * the data in XML format * @throws IOException * @throws ParserConfigurationException * @throws SAXException * @throws TransformerFactoryConfigurationError * @throws TransformerException */ public static void getXFAData(String src, File file) throws IOException, ParserConfigurationException, SAXException, TransformerFactoryConfigurationError, TransformerException { FileOutputStream os = new FileOutputStream(file); PdfReader reader = new PdfReader(src); XfaForm xfa = new XfaForm(reader); Node node = xfa.getDatasetsNode(); NodeList list = node.getChildNodes(); for (int i = 0; i < list.getLength(); i++) { if ("data".equals(list.item(i).getLocalName())) { node = list.item(i); break; } } Transformer tf = TransformerFactory.newInstance().newTransformer(); tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); tf.setOutputProperty(OutputKeys.INDENT, "yes"); tf.transform(new DOMSource(node), new StreamResult(os)); reader.close(); } /** * Reads the data from a PDF containing an XFA form. * 解析基于XML Forms Architecture的pdf,导出xml形式的表单数据写入到文件中 * @param src * the original PDF * @throws IOException * @throws ParserConfigurationException * @throws SAXException * @throws TransformerFactoryConfigurationError * @throws TransformerException */ public static ByteArrayOutputStream getXFAData(InputStream is) throws IOException, ParserConfigurationException, SAXException, TransformerFactoryConfigurationError, TransformerException { ByteArrayOutputStream os = new ByteArrayOutputStream(); PdfReader reader = new PdfReader(is); XfaForm xfa = new XfaForm(reader); Node node = xfa.getDatasetsNode(); NodeList list = node.getChildNodes(); for (int i = 0; i < list.getLength(); i++) { if ("data".equals(list.item(i).getLocalName())) { node = list.item(i); break; } } Transformer tf = TransformerFactory.newInstance().newTransformer(); tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); tf.setOutputProperty(OutputKeys.INDENT, "yes"); tf.transform(new DOMSource(node), new StreamResult(os)); reader.close(); return os; } /** * Reads the data from a PDF containing an XFA form. * 解析基于XML Forms Architecture的pdf,导出xml形式的表单数据写入到文件中 * @param src * the original PDF * @param dest * the data in XML format * @throws IOException * @throws ParserConfigurationException * @throws SAXException * @throws TransformerFactoryConfigurationError * @throws TransformerException */ public static void getXFAData(InputStream is, File file) throws IOException, ParserConfigurationException, SAXException, TransformerFactoryConfigurationError, TransformerException { FileOutputStream os = new FileOutputStream(file); PdfReader reader = new PdfReader(is); XfaForm xfa = new XfaForm(reader); Node node = xfa.getDatasetsNode(); NodeList list = node.getChildNodes(); for (int i = 0; i < list.getLength(); i++) { if ("data".equals(list.item(i).getLocalName())) { node = list.item(i); break; } } Transformer tf = TransformerFactory.newInstance().newTransformer(); tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); tf.setOutputProperty(OutputKeys.INDENT, "yes"); tf.transform(new DOMSource(node), new StreamResult(os)); reader.close(); } /** * Checks if a PDF containing an interactive form uses AcroForm technology, * XFA technology, or both. Also lists the field names. * * 不支持adobe designer设计的xfa格式的pdf * @param src * the original PDF * @param dest * a text file containing form info. * @throws IOException */ public void getFieldnames(String src, String dest) throws IOException { PrintStream out = new PrintStream(new FileOutputStream(dest)); PdfReader reader = new PdfReader(src); AcroFields form = reader.getAcroFields(); XfaForm xfa = form.getXfa(); out.println(xfa.isXfaPresent() ? "XFA form" : "AcroForm"); Set<String> fields = form.getFields().keySet(); for (String key : fields) { out.println(key); } out.flush(); out.close(); } /** * Reads the XML that makes up an XFA form. * * @param src * the original PDF file * @param dest * the resulting XML file * @throws IOException * @throws ParserConfigurationException * @throws SAXException * @throws TransformerFactoryConfigurationError * @throws TransformerException */ public void readXfa(String src, String dest) throws IOException, ParserConfigurationException, SAXException, TransformerFactoryConfigurationError, TransformerException { FileOutputStream os = new FileOutputStream(dest); PdfReader reader = new PdfReader(src); XfaForm xfa = new XfaForm(reader); Document doc = xfa.getDomDocument(); Transformer tf = TransformerFactory.newInstance().newTransformer(); tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); tf.setOutputProperty(OutputKeys.INDENT, "yes"); tf.transform(new DOMSource(doc), new StreamResult(os)); reader.close(); } /** * Manipulates a PDF file src with the file dest as result * @param src the original PDF * @param xml the XML data that needs to be added to the XFA form * @param dest the resulting PDF * @throws IOException * @throws DocumentException */ public static void manipulatePdf(String src, String xml, String dest) throws IOException, DocumentException { PdfReader reader = new PdfReader(src); PdfStamper stamper = new PdfStamper(reader, new FileOutputStream(dest), '\0', true); AcroFields form = stamper.getAcroFields(); XfaForm xfa = form.getXfa(); xfa.fillXfaForm(new FileInputStream(xml)); stamper.close(); } }
相关推荐
李春葆编著数据结构(C语言篇)习题与解析(修订版).pdf李春葆编著数据结构(C语言篇)习题与解析(修订版).pdf李春葆编著数据结构(C语言篇)习题与解析(修订版).pdf李春葆编著数据结构(C语言篇)习题与解析(修订版).pdf...
非常详细的AIS信号解析过程,介绍了AIS报文的各个部分的含义,以及报文种类,以及报文参数的解码方法和过程,并添加了举例说明。
美国HIPAA隐私规则对于个人健康医疗数据合规解析.pdf美国HIPAA隐私规则对于个人健康医疗数据合规解析.pdf美国HIPAA隐私规则对于个人健康医疗数据合规解析.pdf美国HIPAA隐私规则对于个人健康医疗数据合规解析.pdf美国...
最近有个项目是要求将PDF版的电子发票,解析成文本格式,要求各个名称都对应,刚开始用的是PDFTextStripper.getText(),发现不能准确的抓到自己想要的数据,后来想了个办法,使用Rectangle,画多个矩形,精准定位,...
数据结构习题与解析.pdf
12.可直接解析指定页的PDF数据,与页面顺序无关。 13.支持日文的处理。 14.支持超级链接的提取。 15.支持直接将PDF文档变为HTM文档。 联系方式: 主页:http://www.pdfimage.com E-mail: pdfimage@pdfimage...
PDF 文档结构 二进制 节点 解析器,可以打开pdf,查看pdf各个节点的情况
金融行业研究方法-美国共同基金FOF数据解析:公募FOF正蓄势,解析美国看未来.pdf
医学数据挖掘解析.pdf
PDF矢量数据解析引擎介绍
读取PDF文档的结构,包括源码,是C++的,通个这个小程序可以更加理解PDF的文件格式和结构
数据结构习题和答案及解析.pdf
Python数据可视化实战全书教案1-8章全.pdfPython数据可视化实战全书教案1-8章全.pdfPython数据可视化实战全书教案1-8章全.pdfPython数据可视化实战全书教案1-8章全.pdfPython数据可视化实战全书教案1-8章全.pdf...
数据结构 习题解析 邓俊辉 第三版 pdf 电子版 非扫描版
大数据采集数据解析业务流程.pdf
2019云栖大会-数智化运营中的数据中台构建解析,2019年IT界最火的词可能是中台,本篇教你如何打造数据中台。
基于Python的Asterix Cat 021数据格式解析分析与实现.pdf
NULL 博文链接:https://snowdymy.iteye.com/blog/1114344
数据结构C++版 殷人昆编著的配套资源,清华大学出版社出版的数据结构习题解析第二版 2011年出版