转载自:

什么是JAVA内容仓库(Java Content Repository)(1)

什么是JAVA内容仓库(Java Content Repository)(2)

什么是JAVA内容仓库(Java Content Repository)(3)

什么是JAVA内容仓库(Java Content Repository)(4完)

(1)什么是JCR ? 我们为什么需要它 ?
JSR-170把自己定义为一个能与内容仓库互相访问的,独立的,标准的方式。同时它也对内容仓库做出了自己的定义,它认为内容仓库是一个高级的信息管理系统,该系统是是传统的数据仓库的扩展,它提供了诸如版本控制、全文检索,访问控制,内容分类、访问控制、内容事件监视等内容服务。Java Content Repository  API(JSR-170)试图建立一套标准的API去访问内容仓库。如果你对内容管理系统(CMS)不熟悉的话,你一定会对内容仓库是什么感到疑惑。你可以这样去理解,把内容仓库理解为一个用来存储文本和二进制数据(图片,word文档,PDF等等)的数据存储应用程序。一个显著的特点是你不用关心你真正的数据到底存储在什么地方,是关系数据库?是文件系统?还是XML?不仅仅是数据的存储和读取,大多数的内容仓库还提供了更加高级的功能,例如访问控制,查找,版本控制,锁定内容等等。一段时间以来市场上出现了各个厂家开发的不同的CMS系统,这些系统都建立在他们各自的内容仓库之上。
问题出现了,每个CMS开发商都提供了他们自己的API来访问内容仓库。这对应用程序的开发者带来了困扰,因为他们要学习不同的开发商提供的API,同时,他们的代码也与这些特定的API产生了绑定。JSR-170正是为解决这一问题而出现的,它提供了一套标准的API来访问任何数据仓库。通过JSR-170,你开发代码只需要引用 javax.jcr.* 这些类和接口。它适用于任何兼容JSR-170规范的内容仓库。我们将通过一个例子来逐步了解JSR-170。为什么需要 Java Content Repository API随着各个厂家各自的内容仓库实现数量的增长,人们越来越需要一组通用的编程接口来使用这些内容仓库,这就是JSR-170所要做的东西。它提供一组通用的编程接口来连接内容仓库。你可以把JSR-170理解为和JDBC类似的API,这样你可以不依赖任何具体的内容仓库实现来开发你的程序。你可以直接使用支持JSR-170的内容仓库;或者如果一些厂家的内容仓库不支持JSR-170则可以通过这些厂家提供的JSR-170驱动来完成从JSR-170与厂家特定的内容仓库的转换。下面这张图描述了使用JSR-170开发的应用系统的结构。在该系统运行的时候,它可以操作内容仓库1,2,3中的任意一个。在这些内容仓库当中,只有2是直接支持JSR-170的,剩下的两个都需要JSR-170驱动来和应用系统交互。注意:你的应用系统完全不用关心你的数据是如何存储的。1可能使用了关系数据库来存储,而2使用了文件系统,至于上,它甚至更前卫的使用了XML。JSR-170 API对不同的人员提供了不同的好处。●对于开发者无需了解厂家的仓库特定的API,只要兼容JSR-170就可以通过JSR-170访问其仓库。
●对于使用CMS的公司则无需花费资金用于在不同种类CMS的内容仓库之间进行转换。
●对于CMS厂家,无需自己开发内容仓库,而专注于开发CMS应用。

(2)内容仓库模型 + Apache JackRabbit

JSR-170 是这样定义内容仓库的,内容仓库由一组 workspace(工作空间)组成,这些workspace通常应该包含相似的内容。一个内容仓库有一个到多个 workspace。每个workspace都是一个树状结构,都有一个唯一的树根节点(root node)。树上的item(元素)或者是个node(节点)或者是个property(属性)。每个node都可以有零个到多个子节点和零个到多个子属性。只有根节点没有父节点,其余所有的节点都有一个父节点。property 也必须有一个父节点,但它没有子节点或是子属性,property 是叶子元素。property是真正存储数据的元素。下图描述了一个blog应用程序的内容仓库模型。每个root node(根节点)的子节点都代表了一个blog实体。与这个blog实体有关的数据都存储在 bolgEntry 节点的属性里,其中一个 blogAttachment property 存储了一个二进制图片文件。

根据内容仓库实现的功能,JSR-170定义了三种级别:
Level 1:定义了一个只读的内容仓库。功能包括读取内容,将内容导出为XML和查找内容。
Level 2:定义了可写的内容仓库。Level 2是Level 1的扩展,新增的功能包括往内容仓库里写入内容,和从XML导入数据到仓库。
Advanced options:定义实现五种附加功能,版本控制、JTA、SQL查询、清晰的内容锁定和监视。什么是Apache JackRabbit?
Apache JackRabbit是一个开放源码的JSR-170 实现,实现了Level 2,但它还有许多扩展的功能。详细可以去它的官方网站。下面我们决定用Apache JackRabbit来作为我们示例程序的内容仓库。如何配置Apache JackRabbit
JackRabbit需要两个参数来配置一个内容仓库实例。
1.内容仓库主目录:这个文件目录下通常包含了所有的内容,搜索索引,内部配置文件和其他持久化信息。它的结构看起来会像下面这个样子:c:/temp||--Blogging||-repository|       ||       |-index|       |-meta|       |-namespaces|       |-nodetypes             ||-version||-workspace||--default在上面的情况下,内容仓库主目录是c:/temp/Blogging.2.内容仓库配置文件:一个典型的配置文件如下:<Repository><FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem"><param name="path" value="${rep.home}/repository"/></FileSystem><Security appName="Jackrabbit"><AccessManager class="org.apache.jackrabbit.core.security.SimpleAccessManager"/><LoginModule class="org.apache.jackrabbit.core.security.SimpleLoginModule"><param name="anonymousId" value="anonymous"/></LoginModule></Security><Workspaces rootPath="${rep.home}/workspaces" defaultWorkspace="default"/><Workspace name="${wsp.name}"><FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem"><param name="path" value="${wsp.home}"/></FileSystem><PersistenceManager class="org.apache.jackrabbit.core.state.db.DerbyPersistenceManager"><param name="url" value="jdbc:derby:${wsp.home}/db;create=true"/><param name="schemaObjectPrefix" value="${wsp.name}_"/></PersistenceManager><SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex"><param name="path" value="${wsp.home}/index"/></SearchIndex></Workspace><Versioning rootPath="${rep.home}/version"><FileSystem class="org.apache.jackrabbit.core.fs.local.LocalFileSystem"><param name="path" value="${rep.home}/version" /></FileSystem><PersistenceManager class="org.apache.jackrabbit.core.state.db.DerbyPersistenceManager"><param name="url" value="jdbc:derby:${rep.home}/version/db;create=true"/><param name="schemaObjectPrefix" value="version_"/></PersistenceManager></Versioning><SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex"><param name="path" value="${rep.home}/repository/index"/></SearchIndex>
</Repository>在这个配置文件里,<Repository>元素是根元素,它包含了下面这些元素:a,<FileSystem>: 该元素配置了内容仓库的全局数据存储位置,这些全局数据包括已注册的命名空间,定制的节点类型等等。        JackRabbit 提供了几种选择,一种是像上面例子里配置的存储在本地文件里,LocalFileSystem. 如果你想把它们存储在数据库里,你可以使用 DbFileSystem.b,<Security>:内容仓库的安全配置,它有两个子元素:<AccessManager>和<LoginModule>。<AccessManager>配置的类用来判断用户有没有权限来对特定数据执行特定的操作。c,<Workspaces>:这个元素的配置对所有的workspace都通用。它的rootPath 属性是所有workspace文件夹的根目录,在我们的例子里它是c:/temp/Blogging/Workspace;defaultWorkspace 属性则包含了workspace的默认名。d,<Workspace>:这个元素是所有workspace的默认配置模板。去每个workspace文件夹下你都会发现一个workspace.xml文件,这个文件和这个元素的配置一模一样。三个子元素:<FileSystem>,和这个workspace相关数据的存储位置;<PersistenceManager> ,这个workspace内容节点存储策略;<SearchIndex>,可选,全文检索。e,<Versioning>:配置一个版本相关的对象。其实JackRabbit也是把它作为节点来处理的。这两个参数可以通过两种方式设置,一种是在仓库实例创建时直接传到Jackrabbit里去,一种是间接的通过设置JNDI object factory。
你可以设置org.apache.jackrabbit.repository.home 这个系统属性的值来指定你的内容仓库主目录;也可以设置
org.apache.jackrabbit.repository.conf 这个系统属性的值来指定你的内容仓库配置文件repository.xml。如果你不设定这两个
参数,Jackrabbit会把当前目录作为内容仓库主目录,同时,它有一个默认的内容仓库配置文件。

(3)开发我们的例子程序 + 连接jackrabbit + 增加内容

开发我们的例子程序
jackrabbit已经配置好了,现在让我们来创建我们的示例程序。这个例子程序将调用JCR-170 API。很显然,我们需要做两件事情:一个是作为后台的对数据进行增删改查(持久层),另一个是开发相对应的UI界面(WEB 层)。首先,让我们定义一个DAO接口。这个接口BlogEntryDAO.java 如下:public interface BlogEntryDAO {public void insertBlogEntry(BlogEntryDTO blogEntryDTO)throws BlogApplicationException;public void updateBlogEntry(BlogEntryDTO blogEntryDTO)throws BlogApplicationException;public ArrayList getBlogList()throws BlogApplicationException;public BlogEntryDTO getBlogEntry(String blogTitle)throws BlogApplicationException;public void removeBlogEntry(String blogTitle)throws BlogApplicationException;public ArrayList searchBlogList(String userName)throws BlogApplicationException;public void attachFileToBlogEntry(String blogTitle, InputStream uploadInputStream)throws BlogApplicationException;public InputStream getAttachedFile(String blogTitle)throws BlogApplicationException;
}正如你看到的,这个接口提供了增删改查的方法,同时还提供了两个方法来处理附件。接下来,我们需要一个DTO对象用来在两个层之间传递数据。public class BlogEntryDTO {private String userName;private String title;private String blogContent;private Calendar creationTime;//Getter and setter methods for each of these properties
}这里我们将仅仅讨论持久层。连接jackrabbit
现在,第一件事情是开发一个组件,获得一个到jackrabbit内容仓库的连接。为了简单,我们将在程序启动的时候获得这个连接,然后在程序停止的时候释放这个连接。这里我们使用了Struts ,所以我们需要开发一个PlugIn 类。如下:public class JackrabbitPlugin implements PlugIn{public static Session session;public void destroy() {session.logout();}public void init(ActionServlet actionServlet, ModuleConfig moduleConfig) throws ServletException {try {System.setProperty("org.apache.jackrabbit.repository.home","c:/temp/Blogging");Repository repository = new TransientRepository();session = repository.login(new SimpleCredentials("username","password".toCharArray()));} catch (LoginException e) {throw new ServletException(e);} catch (IOException e) {throw new ServletException(e);} catch (RepositoryException e) {throw new ServletException(e);            }}public static Session getSession() {return session;}
}init()方法将会在程序启动的时候调用,destroy()将会在程序停止的时候调用。我们在init()方法里获得了到jackrabbit内容仓库的连接。看看代码,我们做的第一件事是设定了org.apache.jackrabbit.repository.home这个系统属性,在上篇文章里提到,这个属性是用来指向我们的内容仓库主目录。这里我们设定它为c:/temp/blogging。接下来,我们创建了TransientRepository的一个实例。这是jackrabbit提供的类,它提供了一个到内容仓库的代理。它在第一个session 打开的时候自动启动内容仓库,在最后一个session 关闭的时候自动关闭内容仓库。
一旦我们获得了一个内容仓库对象,我们就可以调用它的login() 方法来打开一个连接。login() 方法需要一个Credential 对象作为参数。如果Credential 对象是NULL,jackrabbit会认为其他的机制做了这个验证(比如JAAS)。login() 方法还可以传入一个workspace名字作为参数,如果不传入这个参数,jackrabbit会返回一个session对象指向默认的workspace。注意workspace和session是一对一的,即一个session仅对应一个workspace。(注:如果不传入Credential对象,返回的session对workspace是只读的)增加内容
连接已经建立起来了,下面让我们实现BlogEntryDAO这个接口。第一个我们想实现的方法是插入数据 insertBlogEntry()public void insertBlogEntry(BlogEntryDTO blogEntryDTO)throws BlogApplicationException {Session session = JackrabbitPlugin.getSession();Node rootNode = session.getRootNode();Node blogEntry = rootNode.addNode("blogEntry");blogEntry.setProperty("title", blogEntryDTO.getTitle());blogEntry.setProperty("blogContent", blogEntryDTO.getBlogContent());blogEntry.setProperty("creationTime", blogEntryDTO.getCreationTime());blogEntry.setProperty("userName", blogEntryDTO.getUserName());            session.save();
}首先获得session 对象,即到内容仓库特定workspace的连接。然后,我们在这个session 对象上调用getRootNode() 方法,获得这个workspace的根节点,这个根节点的路径是("/").一旦我们获得这个根节点,我们就可以通过addNode()方法在这个根节点下增加新的子节点。新节点的名字是blogEntry. 通过setProperty() 方法我们把数据存储到节点的property里。正如我们先前说明的,真实的数据是存储在property元素里,property元素是叶子。
注意session.save() 这行代码。这个方法是必须调用的,这个方法调用之前,任何 Node,Property的改变都被保存在这个session的一个临时区域里,其他的和该session连接到相同workspace的session都看不到这些改变。当这个方法被调用并被成功执行后,这些Node,Property的改变才会被持久化到这个session关联的workspace里,同时所有与这个workspace关联的session才可见这些变化。相对应的,Session.refresh(false)将会丢弃所有这些改变。item.save()和Item.refresh(false)作用相似,只是影响范围限定在单个Item上(注意,包括它的子节点)

(4)获得列表 + 查找内容(用XPath的方式)+ 处理二进制内容

获得列表
在上一步中我们已经把数据保存到了内容仓库中,那我们如何确定数据确实保存进去了呢?getBlogList() 这个方法将返回根节点下所有名为blogEntry.的子节点。public ArrayList getBlogList() throws BlogApplicationException {Session session = JackrabbitPlugin.getSession();ArrayList blogEntryList = new ArrayList();Node rootNode = session.getRootNode();NodeIterator blogEntryNodeIterator = rootNode.getNodes();while (blogEntryNodeIterator.hasNext()) {Node blogEntry = blogEntryNodeIterator.nextNode();if (blogEntry.getName().equals("blogEntry") == false)continue;String title = blogEntry.getProperty("title").getString();String blogContent = blogEntry.getProperty("blogContent").getString();Value creationTimeValue = (Value) blogEntry.getProperty("creationTime").getValue();String userName = blogEntry.getProperty("userName").getString();BlogEntryDTO blogEntryDTO = new BlogEntryDTO(userName, title,blogContent, creationTimeValue.getDate());blogEntryList.add(blogEntryDTO);}return blogEntryList;
}一旦你获得了根节点这个对象,你就可以通过调用getNodes()这个方法来获取它所有的子节点。如果这个节点没有子节点,将返回一个空的NodeIterator 对象。我们可以遍历这个NodeIterator 对象来获得名为blogEntry 的节点集合,然后通过getProperty()方法来获得节点上的属性,即我们保存的真实数据。getProperty()方法返回Value对象的一个实例。因为存储数据类型的不同,所以返回的Value对象实例是不同的。根据不同的数据类型,你应该调用特定的方法来获取数据,比如getString()来获取字符串,而getDate()获得一个日期。查找内容(用XPath的方式)
JSR-170定义了两种方式来查找内容(也可以理解为查找节点)。一种使用XPath语法,另一种使用SQL语法。JSR-170要求Level 1必须实现XPath的方式,而SQL的方式则作为一个可选的功能。XPath原本是一种设计用来查找XML元素的语言。因为我们的workspace是树状的结构,很像XML。所以XPath语法非常适合于在这里查找内容。下面的代码演示了通过作者名来查找节点。
Session session = JackrabbitPlugin.getSession();Workspace workSpace = session.getWorkspace();QueryManager queryManager = workSpace.getQueryManager();StringBuffer queryStr = new StringBuffer("//blogEntry[@"+PROP_BLOGAUTHOR +"= '");queryStr.append(userName);queryStr.append("']");Query query = queryManager.createQuery(queryStr.toString(),Query.XPATH);QueryResult queryResult = query.execute();NodeIterator queryResultNodeIterator = queryResult.getNodes();while (queryResultNodeIterator.hasNext()) {Node blogEntry = queryResultNodeIterator.nextNode();String title = blogEntry.getProperty(PROP_TITLE).getString();String blogContent = blogEntry.getProperty(PROP_BLOGCONTENT).getString();Value creationTimeValue = (Value) blogEntry.getProperty(PROP_CREATIONTIME).getValue();BlogEntryDTO blogEntryDTO = new BlogEntryDTO(userName, title,blogContent, creationTimeValue.getDate());blogEntryList.add(blogEntryDTO);}首先获得session 对象,通过它获得它连接的workspace,然后就可以通过workspace获得这个workspace的QueryManager 。QueryManager 接口定义了很多用来查询的方法。接下来我们要做的是创建一条查询语句。我们这里这样写"//blogEntry[@blogAuthor='<bloggerName>'"。这句话的意思是查找所有名为blogEntry ,含有blogAuthor 属性且属性值为<bloggerName>的节点。具体可以看JSR-170规范。通过queryManager's createQuery()方法创建一个查询对象,这个方法需要两个参数,一个是我们的查询语句,另一个是查询的方式,这里使用XPath。获得这个Query 查询对象后,调用它的execute() 方法开始执行查询,返回一个QueryResult 对象。注意,查询的结果受到当前session的限制,换句话说,就是如果这个session没有权限查看一个特定的节点,哪怕这个节点满足我们查询的条件,在我们的查询结果里也是看不到这个节点的。所有的查询数据来自于该workspace已经持久化的数据,哪些已经改变但还没有通过session.save()(item.save())持久化到workspace的数据不在查询之列。获得QueryResult 对象后,我们就可以通过调用getNodes()方法来获得符合查询条件的节点的一个遍历。

剩下的两个未实现的方法是updateBlogEntry() 和 removeBlogEntry(),它们实现起来都很简单。我们把BOLG 标题作为主键,通过标题来获得相关的节点。在updateBlogEntry()方法里,我们直接设定需要改变的属性;在 removeBlogEntry()方法里,我们获得目标节点后直接在节点上调用remove()方法。最后别忘了一定要调用session.save()方法把我们改变的数据持久化。处理二进制内容
对内容仓库来说,处理二进制内容是个很基本的要求,比如说图片。现在我们的示例程序容许给每个BLOG附加一张图片。下面分别是附加图片和获取图片的方法。public void attachFileToBlogEntry(String blogTitle,InputStream uploadInputStream) throws BlogApplicationException {Session session = JackrabbitPlugin.getSession();Node blogEntryNode = getBlogEntryNode(blogTitle, session);blogEntryNode.setProperty(PROP_ATTACHMENT, uploadInputStream);session.save();}
public InputStream getAttachedFile(String blogTitle) throws BlogApplicationException {InputStream attachFileIS = null;Node blogEntryNode = getBlogEntryNode(blogTitle);Value attachFileValue = (Value) blogEntryNode.getProperty(PROP_ATTACHMENT).getValue();attachFileIS = attachFileValue.getStream();return attachFileIS;
}正如你看到的那样,我们的代码在处理二进制内容和一般内容间并没有什么太大的区别。仅仅一点不同的是你要通过InputStream 对象来保存和获取二进制数据。在我们的配置文件里关于persistent manager会有一个externalBLOBs 属性。把这个属性设为true, 图片将会保存在文件里,相反则会保存在数据库的blob字段里。总结
到这里,我们对 JSR-170, Jackrabbit以及如何使用 JSR-170 API开发一个简单的应用程序都有了大概的了解。我们的讨论更多的在于基础。相信大家一定会对内容仓库有个初步的认识。


[saiku] JCR在saiku中的运用原理相关推荐

  1. java final 实例_Java中final实现原理的深入分析(附示例)

    本篇文章给大家带来的内容是关于Java中final实现原理的深入分析(附示例),有一定的参考价值,有需要的朋友可以参考一下,希望对你有所帮助. final在Java中是一个保留的关键字,可以声明成员变 ...

  2. 线程池中阻塞队列的作用?为什么是先添加列队而不是先创建最大线程?线程池中线程复用原理

    1.一般的队列只能保证作为一个有限长度的缓冲区,如果超出了缓冲长度,就无法保留当前的任务了,阻塞队列通过阻塞可以保留住当前想要继续入队的任务.阻塞队列可以保证任务队列中没有任务时阻塞获取任务的线程,使 ...

  3. FreeRtos学习笔记(11)查找就绪任务中优先级最高任务原理刨析

    FreeRtos学习笔记(11)查找就绪任务中优先级最高任务原理刨析 怎么查找就绪任务中优先级最高的? tasks.c中声明了一个全局变量 uxTopReadyPriority,任务从其他状态进入就绪 ...

  4. 【直播预告】计算机视觉中数据增强原理和实践

    应广大粉丝要求,以后有三AI会多组织直播,分享更多实践的干货知识,下面预告一下下周六的知乎Live直播-计算机视觉中数据增强原理和实践. 1.直播基本信息 时间:2019年4月20日20:00开始. ...

  5. vue指令写在html中的原理,详解Vue中的MVVM原理和实现方法

    对Vue中的MVVM原理解析和实现首先你对Vue需要有一定的了解,知道MVVM.这样才能更有助于你顺利的完成下面原理的阅读学习和编写下面由我阿巴阿巴的详细走一遍Vue中MVVM原理的实现,这篇文章大家 ...

  6. 产品工作中的金字塔原理

    本文有PMCAFF 原创作者 咸鱼 原创发布于pmcaff.com 金字塔原理是国外一个慈祥的老太太(巴巴拉·明托)提出的一个方法论,主要是帮助我们清晰的思考.表达和写作的思维方式. 一. 什么是金字 ...

  7. 【转载】word2vec 中的数学原理详解

    文章目录 一.前言 二.预备知识 三.背景知识 四.基于 Hierarchical Softmax 的模型 五.基于 Negative Sampling 的模型 六.若干源码细节 原文传送门: wor ...

  8. 中间件是什么?在.NET Core中的工作原理又是怎样的呢?

    本文出自<从零开始学ASP.NET CORE MVC> 推荐文章:ASP.NET Core appsettings.json文件 ASP.NET Core 中的中间件(Middleware ...

  9. Java中随机数的原理,以及使用时的注意点

    转载自   Java中随机数的原理,以及使用时的注意点 1 前言 一提到 Java 中的随机数,很多人就会想到 Random,当出现生成随机数这样需求时,大多数人都会选择使用 Random 来生成随机 ...

  10. golang 结构体断言_Golang中的reflect原理

    反射(reflect)是在计算机程序运行时,访问,检查,修改它自身的一种能力,是元编程的一种形式.在Java等语言中都很好地支持了反射.Golang也实现了反射,主要核心位于reflect包,官方文档 ...

最新文章

  1. ChartType属性
  2. 超详细的CMD文件讲解
  3. Hadoop和Spark生态圈了解
  4. cp linux 显示进度条_Unix/Linux/Mac os下 文件互传
  5. mybatis查询mysql数据库很慢_mybatis查询大量数据库
  6. NPU 2015年陕西省程序设计竞赛网络预赛(正式赛)F题 和谐的比赛(递推 ||卡特兰数(转化成01字符串))...
  7. phpstorm内网远程debug
  8. lopatkin俄大神精简中文系统Windows 8.1 Pro 18655 x86-x64 ZH-CN PIP
  9. Ubuntu18.04 替换vscode字体
  10. 联通4g满格但是网速慢_手机网速太慢怎么办 教你一招(4g信号满格网速很慢)...
  11. xp系统打印机服务器报错,win10系统连接xp系统共享打印机报错0x000004的具体方法...
  12. Power bi 3.18 仪表盘
  13. Android studio报错:Gradle‘s dependency cache may be corrupt (this sometimes occurs after a network
  14. 转录组分析之 Trimming对reads进行处理
  15. 局域网共享打印机能连接上但不能打印
  16. 计算机系统怎么装到u盘启动不了,电脑开机就进入BIOS界面,设置了u盘启动准备从装系统但是进不了U盘,这些是为什么啊?...
  17. 以逗号为分隔符对字符串进行分隔
  18. python绘制单线图_如何绘制管道单线图
  19. JS中的event 对象详解
  20. 山楂(牛客月赛45 )

热门文章

  1. Django2 SQLite3迁移到MySQL数据库
  2. fastjson取某个key_JAVA学习:怎么给函数取一个“合理”的名字
  3. 常用的C++ STL
  4. git如何查看某个人提交的日志。
  5. C语言程序设计第四次作业
  6. iOS (导航条)navBar 透明
  7. Xcode之断点调试
  8. Swift - 通过url地址打开web页面
  9. 最大子序列的和算法分析一
  10. HDOJ 1465 不容易系列之一