• Hibernate数据保存操作方法的原理对比


    Interface Session 
    All Superinterfaces: 
    Serializable 
    All Known Subinterfaces: 
    EventSource, Session 
    All Known Implementing Classes: 
    SessionImpl 

    public interface Session  extends Serializable 

    Java应用程序与Hibernate之间的主要运行时接口。它是抽象了持久化服务概念的核心抽象API类。 

    Session的生命周期绑定在一个物理的事务(tansaction)上面。(长的事务可能跨越多个数据库事物。) 

    引用

    Session的主要功能是提供对映射的实体类实例的创建,读取和删除操作。 
    实例可能以下面三种状态存在: 
    瞬态或者自由态(transient): 不曾进行持久化,未与任何Session相关联 
    持久化状态(persistent): 仅与一个Session相关联 
    脱管状态或者游离态(detached): 已经进行过持久化,但当前未与任何Session相关联 

    游离状态的实例可以通过调用save()、persist()或者saveOrUpdate()方法进行持久化。 
    持久化实例可以通过调用 delete()变成游离状态。 
    通过get()或load()方法得到的实例都是持久化状态的。 
    游离状态的实例可以通过调用 update()、0saveOrUpdate()、lock()或者replicate()进行持久化。 
    游离或者自由状态下的实例可以通过调用merge()方法成为一个新的持久化实例。 

    save()和persist()将会引发SQL的INSERT,delete()会引发SQLDELETE,而update()或merge()会引发SQLUPDATE。 
    对持久化(persistent)实例的修改在刷新提交的时候会被检测到,它也会引起SQLUPDATE。 
    saveOrUpdate()或者replicate()会引发SQLINSERT或者UPDATE。 



    其具体实现并不一定是线程安全的。每个线程/事务应该从一个SessionFactory获取自己的session实例。 

    如果其持久化对象类是可序列化的,则Session实例也是可序列化的。 

    一个典型的事务应该使用下面的形式: 

    Java代码  收藏代码
    1. Session sess = factory.openSession();  
    2. Transaction tx;  
    3. try {  
    4.     tx = sess.beginTransaction();  
    5.     //do some work  
    6.     ...  
    7.     tx.commit();  
    8. }  
    9. catch (Exception e) {  
    10.     if (tx!=null) tx.rollback();  
    11.     throw e;  
    12. }  
    13. finally {  
    14.     sess.close();  
    15. }  




    如果Session抛出了异常, 事务必须回滚而session会被废弃。在异常发生后Session的内部状态可能会与数据库失去同步。 

    引用

    save方法 
    public Serializable save(Object object) 
                      throws HibernateException 

    Persist the given transient instance, first assigning a generated identifier. 
    (Or using the current value of the identifier property if the assigned generator is used.) 
    This operation cascades to associated instances if the association is mapped with cascade="save-update". 
    持久化指定的游离态对象,首先要分配一个自动生成的主键标示符。 
    (或者如果主键生成器正在使用,使用当前主键标示符属性值) 
    如果关联关系映射配置了cascade="save-update"属性,save操作对关联对象级联操作。 
    Parameters: 
    object - a transient instance of a persistent class 
    一个要持久化类的自由状态的实例对象 
    Returns: 
    the generated identifier 
    返回生成的主键标示符 
    Throws: 
    HibernateException 



    实例: 
    Session.save()方法用于实体对象的持久化保存,也就是说当执行session.save()方法时会生成对应的insert SQL语句,完成数据的保存。 
    如下面的代码: 

    Java代码  收藏代码
    1. public static void insertUser()  
    2. {  
    3.     Session sess = HibernateSessionFactory.getSession();  
    4.     User user = new User();  
    5.     Transaction tx = null;  
    6.     try  
    7.     {  
    8.         tx = sess.beginTransaction();  
    9.         // do some work  
    10.         user.setLogonName("关羽");  
    11.         user.setNickName("关云长");  
    12.         user.setPassword("1234");  
    13.         sess.save(user);  
    14.         tx.commit();  
    15.     }  
    16.     catch (Exception e)  
    17.     {  
    18.         if (tx != null)  
    19.             tx.rollback();  
    20.     }  
    21.     finally  
    22.     {  
    23.         sess.close();  
    24.     }  
    25. }  



    当执行到session.save()方法时,Hibernate并不会马上生成insert SQL语句来进行数据的保存,而是当稍后清理session的缓存时才有可能执行insert SQL语句,那么session.save()方法到底会执行哪些步骤呢? 

    Hibernate3.2中save()方法实现的源代码: 

    引用

    Java代码  收藏代码
    1. /** 
    2.  * Concrete implementation of a Session, and also the central, organizing component 
    3.  * of Hibernate's internal implementation. As such, this class exposes two interfaces; 
    4.  * Session itself, to the application, and SessionImplementor, to other components 
    5.  * of Hibernate. This class is not threadsafe. 
    6.  * 
    7.  * @author Gavin King 
    8.  */  
    9. public final class SessionImpl extends AbstractSessionImpl   
    10.         implements EventSource, org.hibernate.classic.Session, JDBCContext.Context {  
    11.           //省略部分代码  
    12.     // save() operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
    13.   
    14.     public void save(Object obj, Serializable id) throws HibernateException {  
    15.         save(null, obj, id);  
    16.     }  
    17.        
    18.     public Serializable save(Object obj) throws HibernateException {  
    19.         return save(null, obj);  
    20.     }  
    21.   
    22.     public Serializable save(String entityName, Object object) throws HibernateException {  
    23.         return fireSave( new SaveOrUpdateEvent(entityName, object, this) );  
    24.     }  
    25.   
    26.     public void save(String entityName, Object object, Serializable id) throws HibernateException {  
    27.         fireSave( new SaveOrUpdateEvent(entityName, object, id, this) );  
    28.     }  
    29.   
    30.     private Serializable fireSave(SaveOrUpdateEvent event) {  
    31.         errorIfClosed();  
    32.         checkTransactionSynchStatus();  
    33.         SaveOrUpdateEventListener[] saveEventListener = listeners.getSaveEventListeners();  
    34.         for ( int i = 0; i < saveEventListener.length; i++ ) {  
    35.             saveEventListener[i].onSaveOrUpdate(event);  
    36.         }  
    37.         return event.getResultId();  
    38.     }  
    39.           //省略部分代码  
    40. }  



    持久化指定的游离态对象,首先要分配一个自动生成的主键标示符。(或者如果主键生成器正在使用,使用当前主键标示符属性值) 

    引用
    请看进行了如下总结: 
    一、在session的内部缓存中寻找保存对象,如果找到了,则认为此数据已经保存(曾经执行过insert操作),实体对象已经处于persistent状态,直接返回。此时即使数据相比之前的状态发生了变化,也将在事务提交时由脏数据检查来判定是否需要执行update操作。 

    二、如果实体对象实现了lifecycle接口,那么将执行待保存对象的onSave()方法。 

    三、如果实体对象实现了Validatable接口,那么将会执行相应的validate()方法。 

    四、如果存在拦截器对象,那么将会执行Interceptor.onSave()方法。 

    五、构造insert SQL语句完成数据保存。 

    六、数据保存成功后,设定实体对象的id为插入记录的id。 

    七、将保存后的实体对象纳入Hibernate的内部缓存(一级缓存)。注意Hibernate不会把保存后的实体对象纳入二级缓存,因为刚刚保存过的实体对象很可能在之后被修改,缓存的频繁更新以及带来的同步问题代价,超出了缓存该对象所带来的收益。 

    八、如果关联关系映射配置了cascade="save-update"属性,save操作对关联对象级联操作。



    引用

    update方法 
    public void update(Object object) 
                throws HibernateException 

    Update the persistent instance with the identifier of the given detached instance. 
    If there is a persistent instance with the same identifier, an exception is thrown. 
    This operation cascades to associated instances if the association is mapped with cascade="save-update". 
    使用给定的游离状态对象的主键标示符来更新持久化状态实例对象。 
    如果存在相同主键标示符的持久化状态对象,抛出异常。 
    如果关联关系映射配置了cascade="save-update"属性,update操作对关联对象级联操作。 

    Parameters: 
    object - a detached instance containing updated state 
    包含了要更新状态的游离状态对象 
    Throws: 
    HibernateException 



    Hibernate3.2中update()方法实现的源代码: 

    引用

    Java代码  收藏代码
    1. /** 
    2.  * Concrete implementation of a Session, and also the central, organizing component 
    3.  * of Hibernate's internal implementation. As such, this class exposes two interfaces; 
    4.  * Session itself, to the application, and SessionImplementor, to other components 
    5.  * of Hibernate. This class is not threadsafe. 
    6.  * 
    7.  * @author Gavin King 
    8.  */  
    9. public final class SessionImpl extends AbstractSessionImpl   
    10.         implements EventSource, org.hibernate.classic.Session, JDBCContext.Context {  
    11.           //省略部分代码  
    12.     // update() operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
    13.   
    14.     public void update(Object obj) throws HibernateException {  
    15.         update(null, obj);  
    16.     }  
    17.   
    18.     public void update(Object obj, Serializable id) throws HibernateException {  
    19.         update(null, obj, id);  
    20.     }  
    21.   
    22.     public void update(String entityName, Object object) throws HibernateException {  
    23.         fireUpdate( new SaveOrUpdateEvent(entityName, object, this) );  
    24.     }  
    25.   
    26.     public void update(String entityName, Object object, Serializable id) throws HibernateException {  
    27.         fireUpdate(new SaveOrUpdateEvent(entityName, object, id, this));  
    28.     }  
    29.   
    30.     private void fireUpdate(SaveOrUpdateEvent event) {  
    31.         errorIfClosed();  
    32.         checkTransactionSynchStatus();  
    33.         SaveOrUpdateEventListener[] updateEventListener = listeners.getUpdateEventListeners();  
    34.         for ( int i = 0; i < updateEventListener.length; i++ ) {  
    35.             updateEventListener[i].onSaveOrUpdate(event);  
    36.         }  
    37.     }  
    38.           //省略部分代码  
    39. }  



    实例: 
    前面我在实体对象状态转化部分曾经讲过,session.update()方法能够将一个处于游离状态的对象,重新纳入Hibernate的内部缓存,变成持久化对象。 
    如下面的代码: 

    Java代码  收藏代码
    1. Configuration cfg = new Configuration();  
    2. SessionFactory sf=cfg. configure().buildSessionFactory();  
    3. Customer customer=new Customer("关羽",27);//customer对象处于自由状态  
    4. Session session=sf.openSession();  
    5. // 开启事务   
    6. Transaction tx=session.beginTransaction();  
    7. //保存后customer对象处于持久化状态  
    8. session.save(customer);  
    9. //清空缓存后customer对象处于游离状态  
    10. session.flush();  
    11. // 提交事务  
    12. tx.commit();  
    13. //session关闭  
    14. session.close();  
    15.   
    16. // 又开启一个新的session   
    17. Session session2=sf.openSession();  
    18. Transaction tx2=session2.beginTransaction();  
    19. //通过调用update()方法将游离状态的customer对象,再次转化成持久化状态  
    20. session2.update(customer);  
    21. //调用delete()方法后,当清空缓存时,会将customer对象移出缓存,同时会在数据库中生成delete事务,来删除customer对象对应的数据记录  
    22. session2.delete(customer);  
    23. tx.commit();  
    24. session.close();  



    那么update方法到底执行了哪些步骤呢?它会按照下面的步骤进行操作: 

    引用

    控制台查看Hibernate生成的sql语句如下: 
    Hibernate: select user0_.id as id0_0_, user0_.logon_name as logon2_0_0_, user0_.nick_name as nick3_0_0_, user0_.password as password0_0_ from logon_user user0_ where user0_.id=? 
    Hibernate: update logon_user set logon_name=?, nick_name=?, password=? where id=? 


    一、首先会在缓存中寻找需要更新的实体对象,如果找到就立刻返回,从这里我们可以看出如果对一个已经处于persistent的实体对象执行update()方法,将不会产生任何作用。 
    二、然后当提交事务进行缓存清理时,将会通过脏数据检查,确定变化的属性,然后生成update SQL语句完成数据的更新。 
    这里有一个问题我们要强调一下,那就是只要通过update()方法将一个游离对象与session相关联,那么不论这个游离的实体对象的属性是否发生改变,都会执行update SQL语句。 
    如下面的代码: 

    Java代码  收藏代码
    1. Transaction tx=session.beginTransaction();  
    2. session.update(customer);  
    3. tx.commit();  
    4. session.close();  




    在这段代码中并没有修改customer对象的任何属性值,但是也会执行一个update SQL语句,如果你希望在没有改变实体对象属性值的情况下不去执行update SQL语句,那么你要开启实体对象元素的”select-before-update”属性,将其设置为”true”,这个属性默认为”false”。 
    如下进行配置: 
    如果启用了这个属性配置,那么在清理session缓存之前,会首先执行类似如下的一条SQL语句: 
    Select * from logon_user where id='1'; 
    查询处所有的customer实体在数据库中对应的属性值,然后逐条与缓存中属性值进行比较,如果发生了改变,那么将会生成update操作进行数据更新,如果没有发生改变那么将不会进行update操作。 
    要跟据实际需求情况来决定是否开启这个选项,如果实体对象的属性不会经常发生改变,那么就应该开启这个选项,以免执行多余的update操作。如果实体对象的属性会经常发生改变,那么就没必要开启这个选项,以免在执行update操作前再执行多余的select语句。 

    注意事项: 
    1.当执行对一个游离实体对象执行session.update()操作时,如果在数据库中不存在这个实体对应的纪录,那么这个操作将会抛出异常。 
    2.当执行session.update()方法将一个游离对象与session关联时,如果此时在缓存中已经存在了与该实体对象具有相同OID的持久化对象,那么这个方法会抛出异常。 
    如下面代码: 

    Java代码  收藏代码
    1. Customer customer1=new Customer("刘备",25);  
    2. Session session1=sf.openSession();  
    3. Transaction tx=session1.beginTransaction();  
    4. //保存后customer对象处于持久化状态  
    5. session.save(customer1);  
    6. //清空缓存后customer对象处于游离状态  
    7. session.flush();  
    8. tx.commit();  
    9. session1.close();  
    10.    
    11. Session session2=sf.openSession();  
    12. Transaction tx2=session2.beginTransaction();  
    13. Customer othercustomer=(Customer)session2.load(Customer.class,"1");  
    14. //通过调用update()方法将游离状态的customer1对象,再次转化成持久化状态  
    15. session2.update(customer1)  
    16. tx2.commit();  
    17. session2.close();  


    当再次将游离对象customer1与session2关联时,此时因为load()操作,在缓存已经加载了一个和customer1具有相同OID的othercustomer对象,此时由于Hibernate缓存的对象缓存机制不允许把OID相同的对象缓存,所以会抛出异常。 

    引用

    load方法 
    public void load(Object object, 
                     Serializable id) 
              throws HibernateException 

    Read the persistent state associated with the given identifier into the given transient instance. 
    读取和给定的主键标示符关联的持久化状态到给定的自由状态的实例对象。 
    Parameters: 
    object - an "empty" instance of the persistent class 
    id - a valid identifier of an existing persistent instance of the class 
    object - 一个持久化类的空对象 
    id - 一个已经存在的持久化实例对象的有效的主键标示符 
    Throws: 
    HibernateException 



    引用

    get和load方法的区别? 
    get和load方式是根据id取得一个记录 
    下边详细说一下get和load的不同,因为有些时候为了对比也会把find加进来。 

    1.从返回结果上对比: 
    load方式检索不到的话会抛出org.hibernate.ObjectNotFoundException异常 
    get方法检索不到的话会返回null 

    2.从检索执行机制上对比: 
    get方法和find方法都是直接从数据库中检索 
    而load方法的执行则比较复杂 
    ■ 首先查找session的persistent Context中是否有缓存,如果有则直接返回 
    ■ 如果没有则判断是否是lazy,如果不是直接访问数据库检索,查到记录返回,查不到抛出异常 
    ■ 如果是lazy则需要建立代理对象,对象的initialized属性为false,target属性为null 
    ■ 在访问获得的代理对象的属性时,检索数据库,如果找到记录则把该记录的对象复制到代理对象的target上,并将initialized=true,如果找不到就抛出异常 。 

    3.根本区别说明 
    ■ 如果你使用load方法,hibernate认为该id对应的对象(数据库记录)在数据库中是一定存在的,所以它可以放心的使用,它可以放心的使用代理来延迟加载该对象。在用到对象中的其他属性数据时才查询数据库,但是万一数据库中不存在该记录,那没办法,只能抛异常。所说的load方法抛异常是指在使用该对象的数据时,数据库中不存在该数据时抛异常,而不是在创建这个对象时(注意:这就是由于“延迟加载”在作怪)。 

    由于session中的缓存对于hibernate来说是个相当廉价的资源,所以在load时会先查一下session缓存看看该id对应的对象是否存在,不存在则创建代理。所以如果你知道该id在数据库中一定有对应记录存在就可以使用load方法来实现延迟加载。 

    ■ 对于get方法,hibernate会确认一下该id对应的数据是否存在,首先在session缓存中查找,然后在二级缓存中查找,还没有就查数据库,数据库中没有就返回null。 

    对于load和get方法返回类型:虽然好多书中都这么说:“get()永远只返回实体类”,但实际上这是不正确的,get方法如果在session缓存中找到了该id对应的对象,如果刚好该对象前面是被代理过的,如被load方法使用过,或者被其他关联对象延迟加载过,那么返回的还是原先的代理对象,而不是实体类对象,如果该代理对象还没有加载实体数据(就是id以外的其他属性数据),那么它会查询二级缓存或者数据库来加载数据,但是返回的还是代理对象,只不过已经加载了实体数据。 

    get方法首先查询session缓存,没有的话查询二级缓存,最后查询数据库;反而load方法创建时首先查询session缓存,没有就创建代理,实际使用数据时才查询二级缓存和数据库。 

    4.简单总结 

    总之对于get和load的根本区别,一句话,hibernate对于load方法认为该数据在数据库中一定存在,可以放心的使用代理来延迟加载,如果在使用过程中发现了问题,只能抛异常;而对于get方法,hibernate一定要获取到真实的数据,否则返回null。 



    引用

    saveOrUpdate方法 

    public void saveOrUpdate(Object object) 
                      throws HibernateException 
    Either save(Object) or update(Object) the given instance, depending upon resolution of the unsaved-value checks (see the manual for discussion of unsaved-value checking). 
    This operation cascades to associated instances if the association is mapped with cascade="save-update". 
    可能对给定的实例对象执行save或者update方法,如果关联关系映射配置了cascade="save-update"属性,update操作对关联对象级联操作。 

    Parameters: 
    object - a transient or detached instance containing new or updated state 
    object - 包含新的或者已更新状态的自由态或者是游离态的 
    对象实例 
    Throws: 
    HibernateException 
    See Also: 
    save(Object), update(Object) 



    Hibernate3.2中saveOrUpdate()方法实现的源代码: 

    引用

    Java代码  收藏代码
    1. /** 
    2.  * Concrete implementation of a Session, and also the central, organizing component 
    3.  * of Hibernate's internal implementation. As such, this class exposes two interfaces; 
    4.  * Session itself, to the application, and SessionImplementor, to other components 
    5.  * of Hibernate. This class is not threadsafe. 
    6.  * 
    7.  * @author Gavin King 
    8.  */  
    9. public final class SessionImpl extends AbstractSessionImpl   
    10.         implements EventSource, org.hibernate.classic.Session, JDBCContext.Context {  
    11.           //省略部分代码  
    12.     // saveOrUpdate() operations ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~  
    13.   
    14.     public void saveOrUpdate(Object object) throws HibernateException {  
    15.         saveOrUpdate(null, object);  
    16.     }  
    17.   
    18.     public void saveOrUpdate(String entityName, Object obj) throws HibernateException {  
    19.         fireSaveOrUpdate( new SaveOrUpdateEvent(entityName, obj, this) );  
    20.     }  
    21.   
    22.     private void fireSaveOrUpdate(SaveOrUpdateEvent event) {  
    23.         errorIfClosed();  
    24.         checkTransactionSynchStatus();  
    25.         SaveOrUpdateEventListener[] saveOrUpdateEventListener = listeners.getSaveOrUpdateEventListeners();  
    26.         for ( int i = 0; i < saveOrUpdateEventListener.length; i++ ) {  
    27.             saveOrUpdateEventListener[i].onSaveOrUpdate(event);  
    28.         }  
    29.     }  
    30.           //省略部分代码  
    31. }  

    查看onSaveOrUpdate(event)方法的定义: 
    Java代码  收藏代码
    1. public class DefaultSaveOrUpdateEventListener extends AbstractSaveEventListener implements SaveOrUpdateEventListener {  
    2.   
    3.     private static final Log log = LogFactory.getLog( DefaultSaveOrUpdateEventListener.class );  
    4.   
    5.     /** 
    6.      * Handle the given update event. 
    7.      * 
    8.      * @param event The update event to be handled. 
    9.      */  
    10.     public void onSaveOrUpdate(SaveOrUpdateEvent event) {  
    11.         final SessionImplementor source = event.getSession();  
    12.         final Object object = event.getObject();  
    13.         final Serializable requestedId = event.getRequestedId();  
    14.   
    15.         if ( requestedId != null ) {  
    16.             //assign the requested id to the proxy, *before*   
    17.             //reassociating the proxy  
    18.             if ( object instanceof HibernateProxy ) {  
    19.                 ( ( HibernateProxy ) object ).getHibernateLazyInitializer().setIdentifier( requestedId );  
    20.             }  
    21.         }  
    22.   
    23.         if ( reassociateIfUninitializedProxy( object, source ) ) {  
    24.             log.trace( "reassociated uninitialized proxy" );  
    25.             // an uninitialized proxy, noop, don't even need to   
    26.             // return an id, since it is never a save()  
    27.         }  
    28.         else {  
    29.             //initialize properties of the event:  
    30.             final Object entity = source.getPersistenceContext().unproxyAndReassociate( object );  
    31.             event.setEntity( entity );  
    32.             event.setEntry( source.getPersistenceContext().getEntry( entity ) );  
    33.             //return the id in the event object  
    34.             event.setResultId( performSaveOrUpdate( event ) );  
    35.         }  
    36.   
    37.     }  
    38.     // 省略部分代码... ...  
    39. }  



    这个方法包含了save()方法和update()方法的特点,如果传入该方法的是一个游离对象,那么这个方法就会执行update操作,如果传入该方法的是一个自由对象,那么这个方法就会执行insert操作。 
    这个方法幕后的工作原理如下: 
    1.首先在缓存寻找,如果找到待保存的操作就直接返回。 
    2.如果实体实现了拦截方法,那么就执行isUnsaved()方法,判断实体对象状态。 
    3.如果实体处于自由状态就执行save(),如果实体处于游离状态那么就执行update()。 

    这里存在一个问题,那就是Hibernate是怎样判断一个实体是处于游离态还是临时状态的? 
    如果实体满足下面的一个条件,就认为这个实体处于临时状态。 
    Java对象的OID值为null。 
    1.如果Java对象具有version属性(可以了解并发加锁部分)且为null。 
    2.如果实体的设置了属性unsaved-value,而且OID值与unsaved-value值相等。 
    3.如果实体的version属性设置了unsaved-value,并且version属性的值与unsaved-value值相等。 
    4.如果实体实现了Interceptor,而且Interceptor.isUnsaved()方法返回true。 
    满足这些条件中的一个,这个实体就被认为是临时对象。 

    Session.delete()方法: 
    delete()方法用于从数据库中删除一个或一批实体所对应的数据,如果传入的对象是持久化对象,那么当清理缓存时,就会执行delete操作。 
    如果传入的是游离对象,那么首先会使该对象与session相关联,然后当清理缓存时,再执行delete操作。 

    看如下代码: 

    Java代码  收藏代码
    1.    
    2. Session session=sessionFactory().openSession();  
    3. Transaction tx=session.beginTransaction();  
    4. //加载持久态对象  
    5. Customer customer=(Customer)session.load(Customer.class,”1”);  
    6. //计划执行一条delete语句  
    7. session.delete(customer);  
    8. //清理缓存,执行一条delete语句  
    9. tx.commit();  
    10. //关闭session,这时将会把customer对象从缓存中删除  
    11. session.close();  



    如果上面的代码中的customer对象是一个游离对象,那么当执行session.delete()方法时,会首先将游离的customer对象与session相关联(转换为持久态),然后再清理缓存时,再执行delete操作。 
    如果你想一次删除多条数据,那么可以采用一个重载的delete()方法:delete("from Customer c where c.id > '8'");这个方法可以删除符合条件的所有数据。 

  • 相关阅读:
    Maria 与Ann的故事
    引语
    Preface
    Chapter 1 Foundation
    Roman to Integer
    Integer to Roman
    Container with most water
    palindrome number
    String to Integer (atoi)
    Reverse Integer
  • 原文地址:https://www.cnblogs.com/writeLessDoMore/p/6725790.html
Copyright © 2020-2023  润新知