hibernate 延迟加载

本贴最后更新于 2796 天前,其中的信息可能已经物是人非

Hibernae 的延迟加载是一个非常常用的技术,实体的集合属性默认会被延迟加载,实体所关联的实体默认也会被延迟加载。hibernate 通过这种延迟加载来降低系统的内存开销,从而保证 Hibernate 的运行性能。

下面先来剖析 Hibernate 延迟加载的“秘密”。

集合属性的延迟加载

当 Hibernate 从数据库中初始化某个持久化实体时,该实体的集合属性是否随持久化类一起初始化呢?如果集合属性里包含十万,甚至百万的记录,在初始化持久化实体的同时,完成所有集合属性的抓取,将导致性能急剧下降。完全有可能系统只需要使用持久化类集合属性中的部分记录,而完全不是集合属性的全部,这样,没有必要一次加载所有的集合属性。

对于集合属性,通常推荐使用延迟加载策略。所谓延迟加载就是等系统需要使用集合属性时才从数据库装载关联的数据。

例如下面 Person 类持有一个集合属性,该集合属性里的元素的类型为 Address,该 Person 类的代码片段如下:


清单 1. Person.java

 public class Person 
 { 
 // 标识属性
 private Integer id; 
 // Person 的 name 属性
 private String name; 
 // 保留 Person 的 age 属性
 private int age; 
 // 使用 Set 来保存集合属性
 private Set<Address> addresses = new HashSet<Address>(); 
 // 下面省略了各属性的 setter 和 getter 方法
 ... 
 } 

 

为了让 Hibernate 能管理该持久化类的集合属性,程序为该持久化类提供如下映射文件:


清单 2. Person.hbm.xml

 <?xml version="1.0" encoding="GBK"?> 
 <!DOCTYPE hibernate-mapping PUBLIC 
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> 
 <hibernate-mapping package="org.crazyit.app.domain"> 
 <!-- 映射 Person 持久化类 --> 
 <class name="Person" table="person_inf"> 
 <!-- 映射标识属性 id --> 
 <id name="id" column="person_id"> 
 <!-- 定义主键生成器策略 --> 
 <generator class="identity"/> 
 </id> 
 <!-- 用于映射普通属性 --> 
 <property name="name" type="string"/> 
 <property name="age" type="int"/> 
 <!-- 映射集合属性  --> 
 <set name="addresses" table="person_address" lazy="true"> 
 <!-- 指定关联的外键列 --> 
 <key column="person_id"/> 
 <composite-element class="Address"> 
 <!-- 映射普通属性 detail --> 
 <property name="detail"/> 
 <!-- 映射普通属性 zip --> 
 <property name="zip"/> 
 </composite-element> 
 </set> 
 </class> 
 </hibernate-mapping> 

 

从上面映射文件的代码可以看出,Person 的集合属性中的 Address 类只是一个普通的 POJO。该 Address 类里包含 detail、zip 两个属性。由于 Address 类代码非常简单,故此处不再给出该类的代码。

上面映射文件中 <set.../> 元素里的代码指定了 lazy="true"(对于 <set.../> 元素来说,lazy="true"是默认值),它指定 Hibernate 会延迟加载集合属性里 Address 对象。

例如通过如下代码来加载 ID 为 1 的 Person 实体:

 Session session = sf.getCurrentSession(); 
 Transaction tx = session.beginTransaction(); 
 Person p = (Person) session.get(Person.class, 1);  //<1> 
 System.out.println(p.getName()); 

 

上面代码只是需要访问 ID 为 1 的 Person 实体,并不想访问这个 Person 实体所关联的 Address 对象。此时有两种情况:

  • 如果不延迟加载,Hibernate 就会在加载 Person 实体对应的数据记录时立即抓取它关联的 Address 对象。
  • 如果采用延迟加载,Hibernate 就只加载 Person 实体对应的数据记录。

很明显,第二种做法既能减少与数据库的交互,而且避免了装载 Address 实体带来的内存开销——这也是 Hibernate 默认启用延迟加载的原因。

现在的问题是,延迟加载到底是如何实现的呢? Hibernate 在加载 Person 实体时,Person 实体的 addresses 属性值是什么呢?

为了解决这个问题,我们在 <1>号代码处设置一个断点,在 Eclipse 中进行 Debug,此时可以看到 Eclipse 的 Console 窗口有如图 1 所示的输出:


图 1. 延迟加载集合属性的 Console 输出
 

正如图 1 输出所看到的,此时 Hibernate 只从 Person 实体对应的数据表中抓取数据,并未从 Address 对象对应的数据表中抓取数据,这就是延迟加载。

那么 Person 实体的 addresses 属性是什么呢?此时可以从 Eclipse 的 Variables 窗口看到如图 2 所示的结果:


图 2. 延迟加载的集合属性值
 

从图 2 的方框里的内容可以看出,这个 addresses 属性并不是我们熟悉的 HashSet、TreeSet 等实现类,而是一个 PersistentSet 实现类,这是 Hibernate 为 Set 接口提供的一个实现类。

PersistentSet 集合对象并未真正抓取底层数据表的数据,因此自然也无法真正去初始化集合里的 Address 对象。不过 PersistentSet 集合里持有一个 session 属性,这个 session 属性就是 Hibernate Session,当程序需要访问 PersistentSet 集合元素时,PersistentSet 就会利用这个 session 属性去抓取实际的 Address 对象对应的数据记录。

那么到底抓取那些 Address 实体对应的数据记录呢?这也难不倒 PersistentSet,因为 PersistentSet 集合里还有一个 owner 属性,该属性就说明了 Address 对象所属的 Person 实体,Hibernate 就会去查找 Address 对应数据表中外键值参照到该 Person 实体的数据。

例如我们单击图 2 所示窗口中 addresses 行,也就是告诉 Eclipse 要调试、输出 addresses 属性,这就是要访问 addresses 属性了,此时就可以在 Eclipse 的 Console 窗口看到输出如下 SQL 语句:

    select 
        addresses0_.person_id as person1_0_0_, 
        addresses0_.detail as detail0_, 
        addresses0_.zip as zip0_ 
    from 
        person_address addresses0_ 
    where 
        addresses0_.person_id=? 

 

这就是 PersistentSet 集合跟据 owner 属性去抓取特定 Address 记录的 SQL 语句。此时可以从 Eclipse 的 Variables 窗口看到图 3 所示的输出:


图 3. 已加载的集合属性值
 

从图 3 可以看出,此时的 addresses 属性已经被初始化了,集合里包含了 2 个 Address 对象,这正是 Person 实体所关联的两个 Address 对象。

通过上面介绍可以看出,Hibernate 对于 Set 属性延迟加载关键就在于 PersistentSet 实现类。在延迟加载时,开始 PersistentSet 集合里并不持有任何元素。但 PersistentSet 会持有一个 Hibernate Session,它可以保证当程序需要访问该集合时“立即”去加载数据记录,并装入集合元素。

与 PersistentSet 实现类类似的是,Hibernate 还提供了 PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 等实现类,它们的功能与 PersistentSet 的功能大致类似。

熟悉 Hibernate 集合属性读者应该记得:Hibernate 要求声明集合属性只能用 Set、List、Map、SortedSet、SortedMap 等接口,而不能用 HashSet、ArrayList、HashMap、TreeSet、TreeMap 等实现类,其原因就是因为 Hibernate 需要对集合属性进行延迟加载,而 Hibernate 的延迟加载是依靠 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 来完成的——也就是说,Hibernate 底层需要使用自己的集合实现类来完成延迟加载,因此它要求开发者必须用集合接口、而不是集合实现类来声明集合属性。

Hibernate 对集合属性默认采用延迟加载,在某些特殊的情况下,为 <set.../>、<list.../>、<map.../> 等元素设置 lazy="false"属性来取消延迟加载。

 

关联实体的延迟加载

默认情况下,Hibernate 也会采用延迟加载来加载关联实体,不管是一对多关联、还是一对一关联、多对多关联,Hibernate 默认都会采用延迟加载。

对于关联实体,可以将其分为两种情况:

  • 关联实体是多个实体时(包括一对多、多对多):此时关联实体将以集合的形式存在,Hibernate 将使用 PersistentSet、PersistentList、PersistentMap、PersistentSortedMap、PersistentSortedSet 等集合来管理延迟加载的实体。这就是前面所介绍的情形。
  • 关联实体是单个实体时(包括一对一、多对一):当 Hibernate 加载某个实体时,延迟的关联实体将是一个动态生成代理对象。

当关联实体是单个实体时,也就是使用 <many-to-one.../> 或 <one-to-one.../> 映射关联实体的情形,这两个元素也可通过 lazy 属性来指定延迟加载。

下面例子把 Address 类也映射成持久化类,此时 Address 类也变成实体类,Person 实体与 Address 实体形成一对多的双向关联。此时的映射文件代码如下:


清单 3. Person.hbm.xml

				 
 <?xml version="1.0" encoding="GBK"?> 
 <!-- 指定 Hibernate 的 DTD 信息 --> 
 <!DOCTYPE hibernate-mapping PUBLIC 
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> 
 <hibernate-mapping package="org.crazyit.app.domain"> 
 <!-- 映射 Person 持久化类 --> 
 <class name="Person" table="person_inf"> 
 <!-- 映射标识属性 id --> 
 <id name="id" column="person_id"> 
 <!-- 定义主键生成器策略 --> 
 <generator class="identity"/> 
 </id> 
 <!-- 用于映射普通属性 --> 
 <property name="name" type="string"/> 
 <property name="age" type="int"/> 
 <!-- 映射集合属性,集合元素是其他持久化实体
没有指定 cascade 属性,指定不控制关联关系 --> 
 <set name="addresses" inverse="true"> 
 <!-- 指定关联的外键列 --> 
 <key column="person_id"/> 
 <!-- 用以映射到关联类属性 --> 
 <one-to-many class="Address"/> 
 </set> 
 </class>

<!-- 映射 Address 持久化类 -->
<class name="Address" table="address_inf">
<!-- 映射标识属性 addressId -->
<id name="addressId" column="address_id">
<!-- 指定主键生成器策略 -->
<generator class="identity"/>
</id>
<!-- 映射普通属性 detail -->
<property name="detail"/>
<!-- 映射普通属性 zip -->
<property name="zip"/>
<!-- 必须指定列名为 person_id,
与关联实体中 key 元素的 column 属性值相同 -->
<many-to-one name="person" class="Person"
column="person_id" not-null="true"/>
</class>
</hibernate-mapping>

 

接下来程序通过如下代码片段来加载 ID 为 1 的 Person 实体:

 // 打开上下文相关的 Session 
 Session session = sf.getCurrentSession(); 
 Transaction tx = session.beginTransaction(); 
 Address address = (Address) session.get(Address.class , 1); //<1> 
 System.out.println(address.getDetail()); 

 

为了看到 Hibernate 加载 Address 实体时对其关联实体的处理,我们在 <1>号代码处设置一个断点,在 Eclipse 中进行 Debug,此时可以看到 Eclipse 的 Console 窗口输出如下 SQL 语句:

    select 
        address0_.address_id as address1_1_0_, 
        address0_.detail as detail1_0_, 
        address0_.zip as zip1_0_, 
        address0_.person_id as person4_1_0_ 
    from 
        address_inf address0_ 
    where 
        address0_.address_id=? 

 

从这条 SQL 语句不难看出,Hibernate 加载 Address 实体对应的数据表抓取记录,并未从 Person 实体对应的数据表中抓取记录,这是延迟加载发挥了作用。

从 Eclipse 的 Variables 窗口看到如图 4 所示的输出:


图 4. 延迟加载的实体
 

从图 4 可以清楚地看到,此时 Address 实体所关联的 Person 实体并不是 Person 对象,而是一个 Person_$$_javassist_0 类的实例,这个类是 Hibernate 使用 Javassist 项目动态生成的代理类——当 Hibernate 延迟加载关联实体时,将会采用 Javassist 生成一个动态代理对象,这个代理对象将负责代理“暂未加载”的关联实体。

只要应用程序需要使用“暂未加载”的关联实体,Person_$$_javassist_0 代理对象会负责去加载真正的关联实体,并返回实际的关联实体——这就是最典型的代理模式。

单击图 4 所示 Variables 窗口中的 person 属性(也就是在调试模式下强行使用 person 属性),此时看到 Eclipse 的 Console 窗口输出如下的 SQL 语句:

    select 
        person0_.person_id as person1_0_0_, 
        person0_.name as name0_0_, 
        person0_.age as age0_0_ 
    from 
        person_inf person0_ 
    where 
        person0_.person_id=? 

 

上面 SQL 语句就是去抓取“延迟加载”的关联实体的语句。此时可以看到 Variables 窗口输出图 5 所示的结果:


图 5. 已加载的实体
 

Hibernate 采用“延迟加载”管理关联实体的模式,其实就在加载主实体时,并未真正去抓取关联实体对应数据,而只是动态地生成一个对象作为关联实体的代理。当应用程序真正需要使用关联实体时,代理对象会负责从底层数据库抓取记录,并初始化真正的关联实体。

在 Hibernate 的延迟加载中,客户端程序开始获取的只是一个动态生成的代理对象,而真正的实体则委托给代理对象来管理——这就是典型的代理模式。

 

代理模式

代理模式是一种应用非常广泛的设计模式,当客户端代码需要调用某个对象时,客户端实际上也不关心是否准确得到该对象,它只要一个能提供该功能的对象即可,此时我们就可返回该对象的代理(Proxy)。

在这种设计方式下,系统会为某个对象提供一个代理对象,并由代理对象控制对源对象的引用。代理就是一个 Java 对象代表另一个 Java 对象来采取行动。在某些情况下,客户端代码不想或不能够直接调用被调用者,代理对象可以在客户和目标对象之间起到中介的作用。

对客户端而言,它不能分辨出代理对象与真实对象的区别,它也无须分辨代理对象和真实对象的区别。客户端代码并不知道真正的被代理对象,客户端代码面向接口编程,它仅仅持有一个被代理对象的接口。

总而言之,只要客户端代码不能或不想直接访问被调用对象——这种情况有很多原因,比如需要创建一个系统开销很大的对象,或者被调用对象在远程主机上,或者目标对象的功能还不足以满足需求……,而是额外创建一个代理对象返回给客户端使用,那么这种设计方式就是代理模式。

下面示范一个简单的代理模式,程序首先提供了一个 Image 接口,代表大图片对象所实现的接口,该接口代码如下:


清单 3. Image.java

				 
 public interface Image 
 { 
 void show(); 
 } 

 

该接口提供了一个实现类,该实现类模拟了一个大图片对象,该实现类的构造器使用 Thread.sleep() 方法来暂停 3s。下面是该 BigImage 的程序代码。


清单 4. BigImage.java

				 
 // 使用该 BigImage 模拟一个很大图片
 public class BigImage implements Image 
 { 
 public BigImage() 
 { 
 try 
 { 
 // 程序暂停 3s 模式模拟系统开销 
				 Thread.sleep(3000); 
 System.out.println("图片装载成功 ..."); 
 } 
 catch (InterruptedException ex) 
 { 
 ex.printStackTrace(); 
 } 
 } 
 // 实现 Image 里的 show() 方法
 public void show() 
 { 
 System.out.println("绘制实际的大图片"); 
 } 
 } 

 

上面的程序代码暂停了 3s,这表明创建一个 BigImage 对象需要 3s 的时间开销——程序使用这种延迟来模拟装载此图片所导致的系统开销。如果不采用代理模式,当程序中创建 BigImage 时,系统将会产生 3s 的延迟。为了避免这种延迟,程序为 BigImage 对象提供一个代理对象,BigImage 类的代理类如下所示。


清单 5. ImageProxy.java

				 
 public class ImageProxy implements Image 
 { 
 // 组合一个 image 实例,作为被代理的对象
 private Image image; 
 // 使用抽象实体来初始化代理对象
 public ImageProxy(Image image) 
 { 
 this.image = image; 
 } 
 /** 
 * 重写 Image 接口的 show() 方法
 * 该方法用于控制对被代理对象的访问,
 * 并根据需要负责创建和删除被代理对象
 */ 
 public void show() 
 { 
 // 只有当真正需要调用 image 的 show 方法时才创建被代理对象
 if (image == null) 
 { 
  image = new BigImage(); 
  } 
 image.show(); 
 } 
 } 

 

上面的 ImageProxy 代理类实现了与 BigImage 相同的 show() 方法,这使得客户端代码获取到该代理对象之后,可以将该代理对象当成 BigImage 来使用。

在 ImageProxy 类的 show() 方法中增加了控制逻辑,这段控制逻辑用于控制当系统真正调用 image 的 show() 时,才会真正创建被代理的 BigImage 对象。下面程序需要使用 BigImage 对象,但程序并不是直接返回 BigImage 实例,而是先返回 BigImage 的代理对象,如下面程序所示。


清单 6. BigImageTest.java

				 
 public class BigImageTest 
 { 
 public static void main(String[] args) 
 { 
 long start = System.currentTimeMillis(); 
 // 程序返回一个 Image 对象,该对象只是 BigImage 的代理对象
 Image image = new ImageProxy(null); 
 System.out.println("系统得到 Image 对象的时间开销 :" + 
 (System.currentTimeMillis() - start)); 
 // 只有当实际调用 image 代理的 show() 方法时,程序才会真正创建被代理对象。
 image.show(); 
 } 
 } 

 

上面程序初始化 image 非常快,因为程序并未真正创建 BigImage 对象,只是得到了 ImageProxy 代理对象——直到程序调用 image.show() 方法时,程序需要真正调用 BigImage 对象的 show() 方法,程序此时才真正创建 BigImage 对象。运行上面程序,看到如图 6 所示的结果。


图 6. 使用代理模式提高性能
 

看到如图 6 所示的运行结果,读者应该能认同:使用代理模式提高了获取 Image 对象的系统性能。但可能有读者会提出疑问:程序调用 ImageProxy 对象的 show() 方法时一样需要创建 BigImage 对象啊,系统开销并未真正减少啊?只是这种系统开销延迟了而已啊?

我们可以从如下两个角度来回答这个问题:

  • 把创建 BigImage 推迟到真正需要它时才创建,这样能保证前面程序运行的流畅性,而且能减少 BigImage 在内存中的存活时间,从宏观上节省了系统的内存开销。
  • 有些情况下,也许程序永远不会真正调用 ImageProxy 对象的 show() 方法——意味着系统根本无须创建 BigImage 对象。在这种情形下,使用代理模式可以显著地提高系统运行性能。

与此完全类似的是,Hibernate 也是通过代理模式来“推迟”加载关联实体的时间,如果程序并不需要访问关联实体,那程序就不会去抓取关联实体了,这样既可以节省系统的内存开销,也可以缩短 Hibernate 加载实体的时间。

 

Hibernate 的延迟加载(lazy load)本质上就是代理模式的应用,我们在过去的岁月里就经常通过代理模式来降低系统的内存开销、提升应用的运行性能。Hibernate 充分利用了代理模式的这种优势,并结合了 Javassist 或 CGLIB 来动态地生成代理对象,这更加增加了代理模式的灵活性,Hibernate 给这种用法一个新名称:延迟加载。无论怎样,充分分析、了解这些开源框架的实现可以更好的感受经典设计模式的优势所在。

  • Hibernate

    Hibernate 是一个开放源代码的对象关系映射框架,它对 JDBC 进行了非常轻量级的对象封装,使得 Java 程序员可以随心所欲的使用对象编程思维来操纵数据库。

    39 引用 • 103 回帖 • 702 关注

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • Q&A

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

    7015 引用 • 31704 回帖 • 220 关注
  • Netty

    Netty 是一个基于 NIO 的客户端-服务器编程框架,使用 Netty 可以让你快速、简单地开发出一个可维护、高性能的网络应用,例如实现了某种协议的客户、服务端应用。

    49 引用 • 33 回帖 • 20 关注
  • B3log

    B3log 是一个开源组织,名字来源于“Bulletin Board Blog”缩写,目标是将独立博客与论坛结合,形成一种新的网络社区体验,详细请看 B3log 构思。目前 B3log 已经开源了多款产品:SymSoloVditor思源笔记

    1083 引用 • 3461 回帖 • 257 关注
  • 负能量

    上帝为你关上了一扇门,然后就去睡觉了....努力不一定能成功,但不努力一定很轻松 (° ー °〃)

    88 引用 • 1234 回帖 • 442 关注
  • GraphQL

    GraphQL 是一个用于 API 的查询语言,是一个使用基于类型系统来执行查询的服务端运行时(类型系统由你的数据定义)。GraphQL 并没有和任何特定数据库或者存储引擎绑定,而是依靠你现有的代码和数据支撑。

    4 引用 • 3 回帖 • 16 关注
  • PWL

    组织简介

    用爱发电 (Programming With Love) 是一个以开源精神为核心的民间开源爱好者技术组织,“用爱发电”象征开源与贡献精神,加入组织,代表你将遵守组织的“个人开源爱好者”的各项条款。申请加入:用爱发电组织邀请帖
    用爱发电组织官网:https://programmingwithlove.stackoverflow.wiki/

    用爱发电组织的核心驱动力:

    • 遵守开源守则,体现开源&贡献精神:以分享为目的,拒绝非法牟利。
    • 自我保护:使用适当的 License 保护自己的原创作品。
    • 尊重他人:不以各种理由、各种漏洞进行未经允许的抄袭、散播、洩露;以礼相待,尊重所有对社区做出贡献的开发者;通过他人的分享习得知识,要留下足迹,表示感谢。
    • 热爱编程、热爱学习:加入组织,热爱编程是首当其要的。我们欢迎热爱讨论、分享、提问的朋友,也同样欢迎默默成就的朋友。
    • 倾听:正确并恳切对待、处理问题与建议,及时修复开源项目的 Bug ,及时与反馈者沟通。不抬杠、不无视、不辱骂。
    • 平视:不诋毁、轻视、嘲讽其他开发者,主动提出建议、施以帮助,以和谐为本。只要他人肯努力,你也可能会被昔日小看的人所超越,所以请保持谦虚。
    • 乐观且活跃:你的努力决定了你的高度。不要放弃,多年后回头俯瞰,才会发现自己已经成就往日所仰望的水平。积极地将项目开源,帮助他人学习、改进,自己也会获得相应的提升、成就与成就感。
    1 引用 • 487 回帖
  • 机器学习

    机器学习(Machine Learning)是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。

    76 引用 • 37 回帖 • 1 关注
  • Kafka

    Kafka 是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据。 这种动作(网页浏览,搜索和其他用户的行动)是现代系统中许多功能的基础。 这些数据通常是由于吞吐量的要求而通过处理日志和日志聚合来解决。

    35 引用 • 35 回帖
  • ZeroNet

    ZeroNet 是一个基于比特币加密技术和 BT 网络技术的去中心化的、开放开源的网络和交流系统。

    1 引用 • 21 回帖 • 609 关注
  • SQLServer

    SQL Server 是由 [微软] 开发和推广的关系数据库管理系统(DBMS),它最初是由 微软、Sybase 和 Ashton-Tate 三家公司共同开发的,并于 1988 年推出了第一个 OS/2 版本。

    19 引用 • 31 回帖 • 1 关注
  • Solidity

    Solidity 是一种智能合约高级语言,运行在 [以太坊] 虚拟机(EVM)之上。它的语法接近于 JavaScript,是一种面向对象的语言。

    3 引用 • 18 回帖 • 353 关注
  • Bug

    Bug 本意是指臭虫、缺陷、损坏、犯贫、窃听器、小虫等。现在人们把在程序中一些缺陷或问题统称为 bug(漏洞)。

    71 引用 • 1737 回帖 • 1 关注
  • CSDN

    CSDN (Chinese Software Developer Network) 创立于 1999 年,是中国的 IT 社区和服务平台,为中国的软件开发者和 IT 从业者提供知识传播、职业发展、软件开发等全生命周期服务,满足他们在职业发展中学习及共享知识和信息、建立职业发展社交圈、通过软件开发实现技术商业化等刚性需求。

    14 引用 • 155 回帖
  • GitLab

    GitLab 是利用 Ruby 一个开源的版本管理系统,实现一个自托管的 Git 项目仓库,可通过 Web 界面操作公开或私有项目。

    46 引用 • 72 回帖
  • 房星科技

    房星网,我们不和没有钱的程序员谈理想,我们要让程序员又有理想又有钱。我们有雄厚的房地产行业线下资源,遍布昆明全城的 100 家门店、四千地产经纪人是我们坚实的后盾。

    6 引用 • 141 回帖 • 566 关注
  • RIP

    愿逝者安息!

    8 引用 • 92 回帖 • 322 关注
  • OpenStack

    OpenStack 是一个云操作系统,通过数据中心可控制大型的计算、存储、网络等资源池。所有的管理通过前端界面管理员就可以完成,同样也可以通过 Web 接口让最终用户部署资源。

    10 引用 • 5 关注
  • NetBeans

    NetBeans 是一个始于 1997 年的 Xelfi 计划,本身是捷克布拉格查理大学的数学及物理学院的学生计划。此计划延伸而成立了一家公司进而发展这个商用版本的 NetBeans IDE,直到 1999 年 Sun 买下此公司。Sun 于次年(2000 年)六月将 NetBeans IDE 开源,直到现在 NetBeans 的社群依然持续增长。

    78 引用 • 102 回帖 • 646 关注
  • Electron

    Electron 基于 Chromium 和 Node.js,让你可以使用 HTML、CSS 和 JavaScript 构建应用。它是一个由 GitHub 及众多贡献者组成的活跃社区共同维护的开源项目,兼容 Mac、Windows 和 Linux,它构建的应用可在这三个操作系统上面运行。

    15 引用 • 136 回帖 • 6 关注
  • 音乐

    你听到信仰的声音了么?

    60 引用 • 510 回帖 • 1 关注
  • TensorFlow

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

    20 引用 • 19 回帖
  • flomo

    flomo 是新一代 「卡片笔记」 ,专注在碎片化时代,促进你的记录,帮你积累更多知识资产。

    4 引用 • 91 回帖
  • abitmean

    有点意思就行了

    38 关注
  • 书籍

    宋真宗赵恒曾经说过:“书中自有黄金屋,书中自有颜如玉。”

    76 引用 • 390 回帖
  • 电影

    这是一个不能说的秘密。

    120 引用 • 598 回帖
  • SMTP

    SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。SMTP 协议属于 TCP/IP 协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地。

    4 引用 • 18 回帖 • 608 关注
  • Oracle

    Oracle(甲骨文)公司,全称甲骨文股份有限公司(甲骨文软件系统有限公司),是全球最大的企业级软件公司,总部位于美国加利福尼亚州的红木滩。1989 年正式进入中国市场。2013 年,甲骨文已超越 IBM,成为继 Microsoft 后全球第二大软件公司。

    103 引用 • 126 回帖 • 443 关注