Hibernate3 深度探索

2024-05-13 13:58
文章标签 深度 探索 hibernate3

本文主要是介绍Hibernate3 深度探索,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Hibernate 作为 Java ORM 模式的优秀开源实现, 当下已经成为一种标准,为饱受 JDBC 折磨的 Java 开发者带来了“福音。快速的版本更新,想开发者所想,Hibernate 的开发团队俨然就是我们开发者的知音呀。随着 Hibernate 的用户群的越来越壮大,Hibernate 的功能也变得更加强大和健硕起来,下面就对于我们比较常用和关心的一些内容做一些比较深入的探讨,我们的宗旨就是不但会灵活应用,还能知其所以然,不要求大家一定要把 Hibernate 的原理弄的很清楚,但其中一些好的模式,好的设计方法及其中重要功能的实现原理和方法还是对我们很有帮助的。我们将会从 Hibernate 的对象关系映射,Hibernate 的事务处理,Hibernate 对大数量的处理方法以及 Hibernate 根据不同情况怎样来调整性能等方面来和大家共同探讨。下面我们开始讲解 Hibernate 的对象关系映射。

深度探索一(Hibernate 的对象关系映射)

首先我们新建一个 Java project,命名为 hibernate-deep-research。我们知道现在通过数据表反生成的 Java 对象的工具很多,比如 Jboss Tools 的 HibernateTools 等,选择你熟悉的就好了,都大同小异的,为了更好的掌握基本知识,本文没使用这些工具, 第一步我们首先完成 Hibernate 的配置文件 - hibernate.cfg.xml,, 该文件内容见清单 1 所示:

清单 1.hibernate.cfg.xml 内容
 <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE hibernate-configuration PUBLIC 
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory name="sessionFactory"> <property name="hibernate.bytecode.use_reflection_optimizer">false</property><property name="hibernate.connection.driver_class">org.h2.Driver</property><property name="hibernate.connection.url">jdbc:h2:~/test</property> <property name="hibernate.connection.username">sa</property> <property name="hibernate.dialect">org.hibernate.dialect.H2Dialect</property> <mapping class="org.ibm.hibernate.deep.research.Test" /> </session-factory> </hibernate-configuration>

从这个 Hibernate 的配置文件中我们可以看出本文使用的数据库为 H2,所以我们要将 H2 的 jar 包引到本工程中。关于 H2 的使用方法,这里不做详细介绍了,它是 HSQLDB 的重写,速度更快些。我们还需要一个 POJO 对象类 Test,值得注意的是我们这里 mapping 属性设置的是 class 而不是 resource,如果是 resource 我们就需要指定 hbm 的 xml 文件,下面我们看下 Test 这个类,POJO Test 类的详细代码见清单 2 所示:

清单 2.Test 对象类内容
@Entity
@Table(name = "TEST", schema = "PUBLIC", catalog = "TEST")
public class Test implements java.io.Serializable {private int id;private String name;public Test() {}public Test(int id) {this.id = id;}public Test(int id, String name) {this.id = id;this.name = name;}@Id@Column(name = "ID", unique = true, nullable = false)public int getId() {return this.id;}public void setId(int id) {this.id = id;}@Column(name = "NAME", length = 10)public String getName() {return this.name;}public void setName(String name) {this.name = name;}}

Test 这个 POJO 类我们使用 annotation 来完成与数据表的映射关系,@Entity 表示这是一个实体类,@Table 指定与那个数据表产生映射关系,@Id 指明主键,@Column 来指明 POJO 对象中的变量与数据表中字段的对应关系,当然 annotation 还有很多,这里就不一一介绍了。从中我们可以知道 Test 这个 POJO 类与数据库中的 test 表产生映射关系,那么这样的映射关系 Hibernate 是怎样来维护的呢?还有上面的 Hibernate 配置文件 hibernate.cfg.xml 又是什么时候,怎样加载的呢?配置文件中的 mapping 属性有的是 resource,有的是 class,Hibernate 又是怎么来区分它们的呢? resource 与 class 加载的不同点又在什么地方呢 ? 如果能回答这些问题,你就基本了解了 Hibernate 的对象映射关系,你可以跳过这部分了,呵呵,如果你还没有个明确答案,那么请继续,下面我们就通过一个测试来一一解答上面的这些疑问。新建一个单元测试类命名为 HibernateTestCase, 继承自 TestCase,下面我们来看下 HibernateTestCase 的 setUp 方法,代码见清单 3 所示:

清单 3.HibernateTestCase 代码
 Transaction tx; protected void setUp() throws Exception { Configuration cfg = new Configuration(); cfg.configure("/org/hibernate.cfg.xml"); SessionFactory sf  = cfg.buildSessionFactory(); session = sf.openSession(); tx = session.beginTransaction(); }

然后我们执行这个测试用例,跟踪下执行过程,我们会发现虽然在创建 configration 实例时我们没有指定任何资源文件,但 Hibernate 会自动设置 hibernate.properties 为默认加载文件,Hibernate 是在哪里又是怎样来设置的呢?我们来看下 Environment 这个类,在这个类的第 608 行有这么段代码 ConfigHelper.getResourceAsStream("/hibernate.properties"),这段代码是被 Configration 类的 reset 方法调用的,我们回过头来再看下 Environment 中的这段代码,如清单 4 所示:

清单 4.Environment 中的代码
 ISOLATION_LEVELS.put( new Integer(Connection.TRANSACTION_NONE), "NONE" ); ISOLATION_LEVELS.put( new Integer(Connection.TRANSACTION_READ_UNCOMMITTED), "READ_UNCOMMITTED" ); ISOLATION_LEVELS.put( new Integer(Connection.TRANSACTION_READ_COMMITTED), "READ_COMMITTED" ); ISOLATION_LEVELS.put( new Integer(Connection.TRANSACTION_REPEATABLE_READ), "REPEATABLE_READ" ); ISOLATION_LEVELS.put( new Integer(Connection.TRANSACTION_SERIALIZABLE), "SERIALIZABLE" );

这段代码是初始化事务隔离级别的,我们在讲到事务那一节会用到,在这里先简单提一下,有个印象。

我们接下来再看下 Hibernate 是怎样加载我们指定的配置文件的,来看下 ConfigHelper 这个类的 getResourceAsStream 方法,见清单 5 所示:

清单 5.getResourceAsStream 方法
 public static InputStream getResourceAsStream(String resource) { String stripped = resource.startsWith("/") ? resource.substring(1) : resource; InputStream stream = null; ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if (classLoader!=null) { stream = classLoader.getResourceAsStream( stripped );} if ( stream == null ) {stream = Environment.class.getResourceAsStream( resource );} if ( stream == null ) {stream = Environment.class.getClassLoader().getResourceAsStream( stripped );} if ( stream == null ) {throw new HibernateException( resource + " not found" );} return stream;}

首先我们看下这个方法的第一行,它的作用是去掉传进来参数的第一个”/”, 为什么要去掉呢,大家发现没有 Hibernate 在指定默认文件时,用的方式是 ConfigHelper.getResourceAsStream("/hibernate.properties"),默认也要加上”/”, 怎么总感觉 Hibernate 有点自虐倾向,呵呵,那么 Hibernate 为什么要去掉这个”/”, 我们在看下清单 5,代码中是通过 ClassLoader 来处理资源文件的,对于 Class 和 ClassLoader 加载资源文件的区别,我想各位一定清楚吧,Class 处理资源文件前面是可以带”/”的,而通过 ClassLoader 处理资源文件前面是不能有”/”, 否则将永远找不到文件。接下来我们来看下 configration 这个类中的 parseMappingElement 方法,代码见清单 6 所示:

清单 6.parseMappingElement
private void parseMappingElement(Element mappingElement, String name) {
final Attribute resourceAttribute=mappingElement.attribute("resource" );
final Attribute fileAttribute = mappingElement.attribute( "file" );
final Attribute jarAttribute = mappingElement.attribute( "jar" );
final Attribute packageAttribute = mappingElement.attribute( "package" );
final Attribute classAttribute = mappingElement.attribute( "class" );
if ( resourceAttribute != null ) {final String resourceName = resourceAttribute.getValue();log.debug( "session-factory config [{}] named resource [{}] for mapping", name, resourceName );addResource( resourceName );}
else if ( fileAttribute != null ) {final String fileName = fileAttribute.getValue();log.debug( "session-factory config [{}] named file [{}] for mapping", name, fileName );addFile( fileName );}
else if ( jarAttribute != null ) {final String jarFileName = jarAttribute.getValue();log.debug( "session-factory config [{}] named jar file [{}] for mapping", name, jarFileName );addJar( new File( jarFileName ) );
}else if ( packageAttribute != null ) {final String packageName = packageAttribute.getValue();log.debug( "session-factory config [{}] named package [{}] for mapping", name, packageName );addPackage( packageName );}
else if ( classAttribute != null ) {final String className = classAttribute.getValue();log.debug( "session-factory config [{}] named class [{}] for mapping", name, className );try {addAnnotatedClass( ReflectHelper.classForName( className ) );}catch ( Exception e ) {throw new MappingException("Unable to load class [ "+ className +"] declared in Hibernate configuration <mapping/> entry",

这个方法是被 parseSessionFactory 方法调用,调用语句见清单 7 所示:

清单 7. 调用 parseMappingElement 代码块
 if ( "mapping".equals( subelementName ) ) { parseMappingElement( subelement, name ); }

我们再来看下清单 6 中的代码,通过代码我们知道 mapping 支持的属性类型有五种,分别是 resource,file,jar,package 和 class,当属性是 class 时候会调用 addAnnotatedClass 方法将该 Class 加载,并放入 List 中。当然其它的三种方式也会执行不同的操作的,这里建议应该把判断 class 这个条件提到 resource 的后边,性能会更好一些。至此 Hibernate 加载资源文件及 POJO 与数据表的映射关系我们就讲完了,我们知道映射关系中有好多属性,只要你根据以上这个思路去跟踪 Hibernate 的执行过程,一切都会明白的,我们在这里就不一一介绍了。下面我们开始讲解关于 Hibernate 事务的一些知识。

深度探索二(Hibernate 的事务管理)

我们知道 Hiberante 是对 JDBC 的轻量级封装,Hiberante 本身并不具备事务管理能力,

它是将事务的管理委托给底层的 JDBC 或 JTA 来实现事物的管理和调度的。也就是说 Hibernate 支持的事务类型有这么两种 JDBC 和 JTA, 那么这两种事务之间又有什么区别呢? Hibernate 的 JDBC 事务是基于 JDBC Connection 实现的,它的生命周期是在 Connection 之内的,也就是说 Hibernate 的 JDBC 事务是不能跨 Connection 的。而 Hibernate 的 JTA 事务类型是由 JTA 容器来管理的,JTA 容器对当前加入事务的众多 Connection 进行调度,实现其事务性要求。JTA 的事务周期可横跨多个 JDBC Connection 生命周期。了解了 Hibernate 的事务类型后,我们再来跟踪下 Hibernate 是怎样来实现这两种事务类型的,下面我们就来讲解下 Hibernate 是怎样来实现 JDBC 事务管理的。看下清单 3 中的最后第二行代码 session = sf.openSession(),在 open Session 时候 Hibernate 都做了些什么呢?它首先调用 SessionFactoryImpl 的 openSession 方法,在 openSession 方法中会获得 connection 连接,相当于 JDBC 中的 getConnection(), 然后设置了 AutoCommit 为 false,设置代码见清单 8 所示:

清单 8.SessionImpl 部分代码
 SessionImpl(final Connection connection,final SessionFactoryImpl factory,final boolean autoclose,final long timestamp,final Interceptor interceptor,final EntityMode entityMode,final boolean flushBeforeCompletionEnabled,final boolean autoCloseSessionEnabled,final ConnectionReleaseMode connectionReleaseMode) {super( factory );this.rootSession = null;this.timestamp = timestamp;this.entityMode = entityMode;this.interceptor = interceptor;this.listeners = factory.getEventListeners();this.actionQueue = new ActionQueue( this );this.persistenceContext = new StatefulPersistenceContext( this );this.flushBeforeCompletionEnabled = flushBeforeCompletionEnabled;this.autoCloseSessionEnabled = autoCloseSessionEnabled;this.connectionReleaseMode = connectionReleaseMode;this.jdbcContext = new JDBCContext( this, connection, interceptor );loadQueryInfluencers = new LoadQueryInfluencers( factory );if ( factory.getStatistics().isStatisticsEnabled() ) {factory.getStatisticsImplementor().openSession();}if ( log.isDebugEnabled() ) {log.debug( "opened session at timestamp: " + timestamp );}}

autoclose 传过来的值是 false,其实这个值是在 Settings 这个类中设置的。然后我们再来看下清单 3 中的最后一行代码 tx = session.beginTransaction() 这行代码,首先调用的是 SessionImpl 这个类的 beginTransaction 方法,代码见清单 9 所示:

清单 9.beginTransaction 代码段
 errorIfClosed(); if ( rootSession != null ) { // todo : should seriously consider not allowing a txn to begin from a child session //      can always route the request to the root session... log.warn( "Transaction started on non-root session" ); } Transaction result = getTransaction(); result.begin(); return result;

我们主要看下最后面的第三行和第四行代码,Transaction result = getTransaction() 我们可以看到,result 为 org.hibernate.transaction.JDBCTransaction,那么 result.begin() 调用的就是 JDBCTransaction 中的 begin 方法,这里就不列出方法代码了,这个方法的主要功能就是再一次检查一下 AutoCommit 是否为 false,如果不是就设置成 false,可以看到 Hibernate 在 openSession 和 begin transaction 中都需要检查事务是否是 auto commit 的。然后会调用 DriverManagerConnectionProvider 的 getConnection 方法,方法代码见清单 10 所示:

清单 10.getConnection 方法体
 public Connection getConnection() throws SQLException { if ( log.isTraceEnabled() ) log.trace( "total checked-out connections: " + checkedOut ); synchronized (pool) { if ( !pool.isEmpty() ) { int last = pool.size() - 1; if ( log.isTraceEnabled() ) { log.trace("using pooled JDBC connection, pool size: " + last); checkedOut++;} Connection pooled = (Connection) pool.remove(last); if (isolation!=null) pooled.setTransactionIsolation(isolation.intValue() );if ( pooled.getAutoCommit()!=autocommit ) pooled.setAutoCommit(autocommit);return pooled;}} log.debug("opening new JDBC connection"); Connection conn = DriverManager.getConnection(url, connectionProps); if (isolation!=null) conn.setTransactionIsolation( isolation.intValue() ); if ( conn.getAutoCommit()!=autocommit ) conn.setAutoCommit(autocommit); if ( log.isDebugEnabled() ) { log.debug( "created connection to: " + url + ", Isolation Level: " + conn.getTransactionIsolation() );} if ( log.isTraceEnabled() ) checkedOut++;return conn;}

从这段代码中更能证明我们上面说的,Hibernate 只是对 JDBC 的封装,它本身不做任何额外的事情的,通过 DriverManager 来获得 connection 连接和 JDBC 一样,我们再来看下这段代码中的一个变量 Isolation,这个变量我们在清单 4 中提起过,是关于事务隔离级别的,还记得事务隔离级别都有那些吗?我们该如何设置事务隔离级别呢?目前这个例子没有设置事务隔离级别,所以 Isolation 的值为 null,我们再来看下清单 4 中的不同事务隔离级别都表示什么意思?

TRANSACTION_READ_UNCOMMITTED 表示可以读到未提交事物的数据,也就是常说的脏读;TRANSACTION_READ_COMMITTED 表示未提交事务之前数据库中读不到该记录;TRANSACTION_REPEATABLE_READ 表示此记录不能被修改,但可以读到已经添加的记录;TRANSACTION_SERIALIZABLE 表示所有情况都加锁,事务是一个接一个的串行执行的,保证不出现脏读,不可重复读,幻影等情况,但要付出很大代价,性能不好。那么 Hibernate 是怎样来设置隔离级别的呢?我们只需在 Hibernate 配置文件中添加 hibernate.connection.isolation 这个属性就可以,它的值是数值型的,可设置成 1,2,4,8 对应上面的介绍顺序。不同的数据库默认的隔离级别是不一样的,所以要根据不同的数据库来设置它所支持的数据库隔离级别。对于 JTA 事务类型大家根据讲解 JDBC 的方法可以自己去看看 Hibernate 是怎样实现的。下面我们我开始讲解下 Hibernate 对于大数据量是采用什么方式来处理的。

深度探索三(Hibernate 对大数据量的处理)

我们在项目中经常会发现一旦要访问的数据量达到一定数量级后,系统会变得异常的慢,这样的情况多数产生在批量操作中的,如果只是单纯地从用户操作界面发送的请求的话,这种情况基本不会发生的,除非你没有应用分页机制,下面我们就具体讲解下,对于这种批量的,大数据量该用什么方式来处理。现在有一个需求就是要求将年龄大于 40 岁的所有记录删除,这样的数据将会有 5 万条,通常的做法,应该像清单 11 那样:

清单 11. 菜鸟级的大数据量删除方式
 Iterator it = session.createQuery("from Employee where age >40").iterate(); while (it.hasNext()) { Employee employee = (Employee) it.next(); session.delete(employee); } tx.commit(); session.close();

我们会发现以上的操作 Hibernate 执行了大量的 SQL 语句,并将这些对象做了持久化,除了消耗大量内存外,执行起来还相当的慢,有的人说,我们可以用 evict 方法呀,每执行完一次删除后将内存中的持久化对象清理掉,不错,可以这么做,这样解决了消耗大量内存的问题,但是如果跟踪 Hibernate 执行过程的话,你会发现大量的 SQL 语句依然存在,那么我们有没有一种方法只执行一条语句就删除掉那 5 万多条记录呢?当然有,我们来看下 Hibernate3 给我提供的批量操作方法,修改后的代码见清单 12 所示:

清单 12.Hibernate3 提供的批量操作方法
 int it = session.createQuery("delete Employee where age >40").executeUpdate();

这个方法,避免了上面提出的两个缺点,我们来看下 Hibernate 是怎样来实现这个功能的,看下 Hibernate 自带的 BasicExecutor 类的 execute 方法,方法体见清单 13 所示:

清单 13.BasicExecutor 的 execute 方法片段
 st = session.getBatcher().prepareStatement( sql ); Iterator parameterSpecifications = this.parameterSpecifications.iterator(); int pos = 1; while ( parameterSpecifications.hasNext() ) { final ParameterSpecification paramSpec = ( ParameterSpecification ) parameterSpecifications.next(); pos += paramSpec.bind( st, parameters, session, pos );} if ( selection != null ) { if ( selection.getTimeout() != null ) {st.setQueryTimeout( selection.getTimeout().intValue() );}} return st.executeUpdate(); }

这段代码是不是有种似曾相识的感觉,呵呵,那说明你 JAVA 基础还可以,的确上面代码和我们最早用 JDBC API 来操作批量数据方式是一样的。

深度探索四(Hibernate 性能调优)

我们知道 Hibernate 3 去掉了 find 方法,之所以去掉也是考虑到性能问题。因为 find 方法对于缓存只写不读,性能低下,如果您有用到 Hibernate 3 以前的版本,要注意一下。

Hibernate 3 以前版本在普通情况下持久化对象时会把该对象的属性全都加载进来,即使有些大对象我们用不到。因为延迟加载属性只能作用在类级别,Hibernate 3 允许我们在属性级别上应用延迟加载功能,这样就可以有选择的加载对象的属性了。设置也很简单,只需在属性的 get 方法上加入 @Basic(fetch = FetchType.LAZY) 这行代码就可以了,然后我们需要增强该类,通过 Ant 脚本来加强就可,很简单,就不给出代码了,要不然加入的注释将被忽略。如果你使用 hbm 格式来映射文件的话,那么在属性行加入 lazy=true 即可。以上讲的是属性级别的延迟加载,类级别的延迟加载大家也要灵活应用,毕竟它对提升性能还是很有帮助的。

我们上面也提到了关于大数据量缓存的问题,提到对于那些不常用的数据要及时清理缓存,我们可以用 session 的 clear 或 evict 方法,养成好习惯,省着一旦出现性能瓶颈时盲目。对于监控性能的一些工具大家可以自己研究一下,比如 loadrunner 等都是很不错的。

总结

本文章通过新建一个 Java 工程 hibernate-deep-research,然后通过一例子带领大家逐步讲解 Hibernate 3 关于对象映射、事务管理、大数据量等方面的原理和执行过程,每一次的揭秘都是通过一个简单的测试单元来进行的。通过对 Hibernate 执行过程的跟踪来和您共同分享其中的奥秘,通过实例的方式能使大家更清楚,更容易的理解。我们知道仅仅通过一篇文章来很详细的将 Hibernate 的方方面面都阐述的很清楚,那是不可能的。本文章提供了最基本,最基础的讲解开发过程,任何复杂的事务归根结底还是源于基础,有句话是这样说的, “授之以鱼,不如授之以渔”,我想只要方向对了,知道如何下手了,就不会有大的失误,最后祝大家工作顺利。

这篇关于Hibernate3 深度探索的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/985895

相关文章

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

基于UE5和ROS2的激光雷达+深度RGBD相机小车的仿真指南(五):Blender锥桶建模

前言 本系列教程旨在使用UE5配置一个具备激光雷达+深度摄像机的仿真小车,并使用通过跨平台的方式进行ROS2和UE5仿真的通讯,达到小车自主导航的目的。本教程默认有ROS2导航及其gazebo仿真相关方面基础,Nav2相关的学习教程可以参考本人的其他博客Nav2代价地图实现和原理–Nav2源码解读之CostMap2D(上)-CSDN博客往期教程: 第一期:基于UE5和ROS2的激光雷达+深度RG

韦季李输入法_输入法和鼠标的深度融合

在数字化输入的新纪元,传统键盘输入方式正悄然进化。以往,面对实体键盘,我们常需目光游离于屏幕与键盘之间,以确认指尖下的精准位置。而屏幕键盘虽直观可见,却常因占据屏幕空间,迫使我们在操作与视野间做出妥协,频繁调整布局以兼顾输入与界面浏览。 幸而,韦季李输入法的横空出世,彻底颠覆了这一现状。它不仅对输入界面进行了革命性的重构,更巧妙地将鼠标这一传统外设融入其中,开创了一种前所未有的交互体验。 想象

AI(文生语音)-TTS 技术线路探索学习:从拼接式参数化方法到Tacotron端到端输出

AI(文生语音)-TTS 技术线路探索学习:从拼接式参数化方法到Tacotron端到端输出 在数字化时代,文本到语音(Text-to-Speech, TTS)技术已成为人机交互的关键桥梁,无论是为视障人士提供辅助阅读,还是为智能助手注入声音的灵魂,TTS 技术都扮演着至关重要的角色。从最初的拼接式方法到参数化技术,再到现今的深度学习解决方案,TTS 技术经历了一段长足的进步。这篇文章将带您穿越时

免费也能高质量!2024年免费录屏软件深度对比评测

我公司因为客户覆盖面广的原因经常会开远程会议,有时候说的内容比较广需要引用多份的数据,我记录起来有一定难度,所以一般都用录屏工具来记录会议内容。这次我们来一起探索有什么免费录屏工具可以提高我们的工作效率吧。 1.福晰录屏大师 链接直达:https://www.foxitsoftware.cn/REC/  录屏软件录屏功能就是本职,这款录屏工具在录屏模式上提供了多种选项,可以选择屏幕录制、窗口

动手学深度学习【数据操作+数据预处理】

import osos.makedirs(os.path.join('.', 'data'), exist_ok=True)data_file = os.path.join('.', 'data', 'house_tiny.csv')with open(data_file, 'w') as f:f.write('NumRooms,Alley,Price\n') # 列名f.write('NA

深度优先(DFS)和广度优先(BFS)——算法

深度优先 深度优先搜索算法(英语:Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支,当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访

轻松录制每一刻:探索2024年免费高清录屏应用

你不会还在用一些社交工具来录屏吧?现在的市面上有不少免费录屏的软件了。别看如软件是免费的,它的功能比起社交工具的录屏功能来说全面的多。这次我就分享几款我用过的录屏工具。 1.福晰录屏大师 链接直达:https://www.foxitsoftware.cn/REC/  这个软件的操作方式非常简单,打开软件之后从界面设计就能看出来这个软件操作的便捷性。界面的设计简单明了基本一打眼你就会轻松驾驭啦

图解TCP三次握手|深度解析|为什么是三次

写在前面 这篇文章我们来讲解析 TCP三次握手。 TCP 报文段 传输控制块TCB:存储了每一个连接中的一些重要信息。比如TCP连接表,指向发送和接收缓冲的指针,指向重传队列的指针,当前的发送和接收序列等等。 我们再来看一下TCP报文段的组成结构 TCP 三次握手 过程 假设有一台客户端,B有一台服务器。最初两端的TCP进程都是处于CLOSED关闭状态,客户端A打开链接,服务器端

java线程深度解析(六)——线程池技术

http://blog.csdn.net/Daybreak1209/article/details/51382604 一种最为简单的线程创建和回收的方法: [html]  view plain copy new Thread(new Runnable(){                @Override               public voi