总结map和set增删查改的使用

本文介绍了STL中map和set的基本用法,包括插入、删除、查找等操作,并通过实例展示了如何使用这些容器。map和set都是关联式容器,其中set不允许重复元素并自动排序,而map则用于存储键值对。

在STL中map和set都是关联式容器,vector和list是序列式容器,在今天的这篇文章中主要介绍的是map和set的基本用法。
一、set
set是一种key类型的集合结构,所以set是不允许有重复元素的一种结构,set中所有元素的key值都会被自动排序为升序。set和multiset都包含在头文件#include<set>中,set和multiset的底层都是用红黑树实现的,但是set的底层插入机制时insert_unique,multiset的底层插入机制时insert_equal

template < class Key, class Compare = less<Key>,
           class Allocator = allocator<Key> > class set; 
  • 1
  • 2

由上述定义可知,set也是一种模板,它的第一个参数Key代表的是键值的类型;第二个参数是一个仿函数,它传入的是键值的比较方式,决定其是升序还是降序;第三个参数是空间配置器。我们主要关心的是set的第一个参数。
set的常用操作:
set的常用操作

在介绍set和map的常用操作之前,先来介绍一种模板类型pair,每个pair可以存储两个值,这两个值可以是任意类型的,pair的两个成员分别是first和second,且pair包含在头文件#include<utility>中。

pair

1、insert,set的插入有三种类型
1.1、pair<iterator,bool> insert(const value_type& x)

其中的value_type是键值的类型,这个版本的插入会返回一个pair类型的对象,其中pair对second成员为false说明插入失败,该元素已经存在,反之则说明插入成功。pair的first成员指向的是要插入位置的迭代器,如果second是false,则first就是这个已经存在元素的迭代器;如果second成员是true,则first就会指向插入位置的迭代器。

1.2、iterator insert(iterator position,const valut_type& val)

postion的类型是一个迭代器的位置,vla表示要插入的数据。如果插入成功,则会返回一个新插入位置的迭代器,否则的话返回这个传入的迭代器。

1.3、插入一段迭代器区间

template<class InputIterator>
void insert(InputIterator first,InputIterator last)
  • 1
  • 2

2、erase,删除
2.1、void erase(iterator position)
删除一个迭代器位置
2.2、size_type erase(const value_type& val)
删除成功返回1,删除失败返回0
2.3、void erase(iterator first,iterator last)
删除从first到last的一段迭代器空间
3、find,查找

   iterator find(const value_type& val) const;
  • 1

如果找到的话就返回这个位置的迭代器,否则就返回end
4、count,统计键值出现的次数

  size_type count(const value_type& val) const;
  • 1

由于set里面不允许有重复出现的键值,所以count的返回结果就只有0和1两个结果,0就表示不存在,1表示存在。这个功能不就和empty功能重复了吗?当然不会啦!count这个接口一般在multiset中使用的最多,这个主要是为了统一接口罢了。
5、set的迭代器set::iterator是一种const_iterator,所以不允许通过迭代器修改set中的值。
当然了,set中的接口还有很多,在这里只介绍这几个。


multiset的常用操作:
multiset的特性和用法和set的用法完全相同,唯一的差别就是multiset是允许键值重复出现的。
1、insert,插入

iterator insert(const value_type& val)
  • 1

如果直接插入一个键值的话,在multiset中返回的是插入元素位置的迭代器。
2、count
multiset中可以出现重复的键值。count统计的是相同键值出现的次数。如果count的结果为0则表示没有出现,如果不为0则表示出现的次数 。
下面是我写的一个关于set操作的简单例子:

void TestSet()
{
    //set是K类型的
    set<string> Myset;
    Myset.insert("left");
    Myset.insert("right");
    Myset.insert("high");
    Myset.insert("low");
    Myset.insert("right");
    Myset.insert("right");
    cout<<"size?"<<Myset.size()<<endl;  //4
    cout<<"max_size?"<<Myset.max_size()<<endl;
    set<string>::iterator it=Myset.begin();
    while(it != Myset.end())
    {
        cout<<*it<<" ";
        ++it;
    }
    cout<<endl;
    cout<<Myset.count("right")<<endl;   //1 统计right出现的次数,set是去重的
    it=Myset.find("left");
    if(it != Myset.end())    //找到了
    {
        cout<<*it<<endl;     //left
    }
    //Myset.erase(it);      //删除一个迭代器
    Myset.erase(it,Myset.end());   //删除两个迭代器之间的范围
}

二、map
map是一种Key(键)、value(值)形式的结构,用于保存键和值组成的条目集合。它要求键值必须是唯一的,值可以不唯一,同一个值可以映射到多个键上(所以map在vlaue层面上是允许重复的,这一点不同于set)。所有的元素都会根据键值自动排序。map中的所有元素都是pair,同时拥有键值(Key)和实值(value),pair的first被视为键值,second被当做实值。我们是不可以修改map的key值的,但是我们可以更改map的vlaue值。map的底层是用红黑树实现的,它的insert机制是一种insert_unique()

template<class Key,class T,class Compare=less<Key>,class Alloc=allocator<pair<const Key,T> > >
  • 1

map也是一个模板,他有四个模板参数类型,第一个是键值的类型(key);第二个是实值的类型(value);第三个是一个仿函数,它传入的是键值的比较方式,默认是升序排列;第四个是空间配置器。
map的常用操作:

map的常用操作

1、insert,插入
1.1、pair<iterator,bool> insert(const value_type& val);
插入一个value_type类型,返回值是一个pair类型。 pair

template<class InputIterator>
    void insert(InputIterator first,InputIterator last);
  • 1
  • 2

2、erase
2.1、void erase(iterator position)
删除的是position迭代器位置的元素。
2.2、size_type erase(const key_type& k)
删除这个键值所在的位置,成功返回1,失败返回0。
2.3、void erase(iterator first,iterator last);
删除一段从first到last的迭代器区间
3、find

  iterator find(const key_type& k)
  • 1

如果查找成功的话则返回这个元素的迭代器,否则返回end,所以在使用find返回的迭代器之前要先判断一下。
4、count

size_type count(const key_type& k)const
  • 1

map中的count与set的count功能一样。
5、operator[]

mapped_type& operator[](const key_type& k)
  • 1

map中的operator[]是最常用的,map中operator[]的底层实现利用的是insert来实现的。
operator


multimap:
multimap与map的特性及用法完全相同,但是multimap是允许键值重复的。multimap和 map都包含在头文件#include<map>中,它们的底层也是用红黑树实现的,它的insert机制是insert_equal。它的count与multiset的count的用法及作用完全一样。下面看一下他们的不同:
1、insert

iterator insert(const value_type& val)
  • 1

如果插入一个元素的话,multimap会返回一个这个插入位置的迭代器。
2、count
count与multiset的count完全相同,multimap中的count统计的是键值出现的次数。
3、multimap中没有重载operator[]。

简单的例子测试map的基本操作:利用map实现一个字典树,利用map统计出现次数最多的前k个水果。

//template<class K,class V>    //自己编写的一个简单的pair
//struct pair
//{
//  K first;
//  V second;
//  pair(const K& key,const V& value)
//      :first(key)
//      ,second(value)
//  {}
//};

//构造一个pair的对象返回
template<class K,class V>
pair<string,int> make_pair(const K& key,const V& value)
{
    return pair<string,int>(key,value);
}

void TestDict()
{
    //map是K,V类型的
    map<string,string> dict;
    dict.insert(pair<string,string>("value","值"));
    dict.insert(pair<string,string>("left","左边"));
    dict.insert(pair<string,string>("right","右边"));
    dict.insert(pair<string,string>("up","上"));
    dict.insert(pair<string,string>("down","下"));
    dict["left"]="剩余";    //也可以使用operator[]来插入或者修改数据的value
    map<string,string>::iterator it=dict.begin();
    while(it != dict.end())
    {
        cout<<(*it).first<<":"<<(*it).second<<endl;
        ++it;
    }
    cout<<endl;

}
vector<int> GetTopKFruits(const vector<string>& fruits)
{
    typedef map<string,int> Map;
    typedef map<string,int>::iterator MapIt;
    vector<int> v;
    //map<string,int> countMap;
    Map countMap;
    for(size_t i=0;i<fruits.size();i++)
    {
        //方法一,find和insert都遍历了这个map,效率不行,但是方便理解,简洁直观
        //map<string,int>::iterator it=countMap.find(fruits[i]);
        //if(it != countMap.end())    //找到了
        //{
        //  it->second++;
        //}
        //else    //没有找到则插入到map中
        //{
        //  //countMap.insert(pair<string,int>(fruits[i],1));
        //  countMap.insert(make_pair(fruits[i],1));
        //}

        //方法二,只查找一次,利用返回pair类型的insert函数
        //pair<MapIt,bool> res=countMap.insert(make_pair(fruits[i],1));
        //if(res.second == false)    //找到了,插入失败
        //{
        //  res.first->second++;
        //}

        //方法三,利用operator[]来实现,operator[]的底层是利用insert来实现的,具体可参考前面提到的operator[]的实现原理
        countMap[fruits[i]]++;
    }
    map<string,int>::iterator it=countMap.begin();
    while(it != countMap.end())
    {
        v.push_back(it->second);
        ++it;
    }
    return v;
}
void TestFruits()
{
    vector<string> fruits;
    fruits.push_back("菠萝");
    fruits.push_back("梨");
    fruits.push_back("水蜜桃");
    fruits.push_back("梨");
    fruits.push_back("菠萝");
    fruits.push_back("水蜜桃");
    fruits.push_back("香蕉");
    fruits.push_back("菠萝");
    fruits.push_back("梨");
    fruits.push_back("水蜜桃");
    fruits.push_back("梨");
    fruits.push_back("菠萝");
    fruits.push_back("水蜜桃");
    fruits.push_back("香蕉");

    vector<int> res=GetTopKFruits(fruits);
    for(size_t i=0;i<res.size();i++)
    {
        cout<<res[i]<<" ";
    }
    cout<<endl;   // 4 4 4 2
}



<think>嗯,用户之前问过如何使用JdbcTemplate进行查询,特别是将结果映射到User POJO。现在他们接着要求“写一下增删查改”,也就是CRUD操作。我需要确保覆盖所有四个操作:增(Create)、删(Delete)、查(Read)、改(Update)。之前已经详细讲了查询,所以这次重点在其他三个操作。 首先,用户可能已经掌握了查询部分,现在需要完整的CRUD示例,以便全面了解JdbcTemplate的使用。他们的身份可能是一个刚学习Spring JDBC的开发者,或者是在项目中需要实现数据库操作的初级程序员。需要确保示例清晰,步骤详细,特别是参数设置SQL语句的正确性。 接下来要考虑的是,用户可能有POJO类,但需要知道如何将对象属性映射到SQL参数。比如插入操作,可能需要使用PreparedStatement的Setter方法,或者使用JdbcTemplate的update方法传入参数数组。这里需要给出两种常见的方法:使用对象数组传递参数,或者使用PreparedStatementSetter来设置参数。 对于更新删除操作,用户需要注意SQL语句中的占位符(?)参数的顺序。此外,应该提醒异常处理,比如删除时可能出现的空结果,或者更新时的影响行数判断。同时,事务管理也是关键点,需要提到@Transactional注解的使用,以确保操作的原子性。 另外,用户可能对批量操作感兴趣,特别是插入多条数据时的效率问题。这时候需要给出批量更新的示例,比如使用batchUpdate方法,并传递参数列表。这对于处理大量数据时非常有用,能减少数据库访问次数,提升性能。 还需要注意代码示例的正确性,比如确保User类有无参构造函数,Getter/Setter方法是否齐全,以及数据库字段名与POJO属性名的匹配。在自定义RowMapper时,如果字段名不匹配,需要用别名或者显式映射。同时,异常情况的处理,比如queryForObject在结果为空时会抛出异常,需要捕获或者使用Optional处理。 最后,总结部分要强调最佳实践,比如使用BeanPropertyRowMapper的注意事项,性能优化,事务控制等。可能用户在实际应用中会遇到这些问题,提前给出提示可以帮助他们避免常见错误。 综上所述,回答需要结构清晰,分步骤介绍每个CRUD操作,提供代码示例,并补充注意事项最佳实践,确保用户能够全面理解并正确应用JdbcTemplate进行数据库操作。</think>以下是使用`org.springframework.jdbc.core.JdbcTemplate`实现 **增删查改(CRUD)** 的完整指南,以`User` POJO为例: --- ### 一、POJO定义(基础) ```java public class User { private Long id; private String username; private String email; // 必须有无参构造函数 public User() {} // Getter/Setter省略(实际需完整实现) } ``` --- ### 二、完整CRUD实现 #### 1. **新增(Create)** ```java public int addUser(User user) { String sql = "INSERT INTO users (username, email) VALUES (?, ?)"; return jdbcTemplate.update( sql, user.getUsername(), // 参数顺序与SQL中的?对应 user.getEmail() ); } // 或使用PreparedStatementSetter public int addUser(User user) { return jdbcTemplate.update( "INSERT INTO users (username, email) VALUES (?, ?)", (PreparedStatement ps) -> { ps.setString(1, user.getUsername()); ps.setString(2, user.getEmail()); } ); } ``` #### 2. **查询(Read)** ```java // 查询单个(需处理空结果异常) public User getUserById(Long id) { try { return jdbcTemplate.queryForObject( "SELECT * FROM users WHERE id = ?", BeanPropertyRowMapper.newInstance(User.class), id ); } catch (EmptyResultDataAccessException e) { return null; // 或抛出自定义异常 } } // 查询列表 public List<User> getAllUsers() { return jdbcTemplate.query( "SELECT id, username, email FROM users", BeanPropertyRowMapper.newInstance(User.class) ); } ``` #### 3. **更新(Update)** ```java public int updateEmail(Long userId, String newEmail) { return jdbcTemplate.update( "UPDATE users SET email = ? WHERE id = ?", newEmail, // 对应第一个? userId // 对应第二个? ); } // 更新整个对象 public int updateUser(User user) { return jdbcTemplate.update( "UPDATE users SET username=?, email=? WHERE id=?", user.getUsername(), user.getEmail(), user.getId() ); } ``` #### 4. **删除(Delete)** ```java public int deleteUser(Long userId) { return jdbcTemplate.update( "DELETE FROM users WHERE id = ?", userId ); } ``` --- ### 三、高级操作 #### 1. **批量插入** ```java public int[] batchInsert(List<User> users) { return jdbcTemplate.batchUpdate( "INSERT INTO users (username, email) VALUES (?, ?)", users.stream().map(user -> new Object[]{user.getUsername(), user.getEmail()}) .collect(Collectors.toList()) ); } ``` #### 2. **查询返回主键** ```java public Long insertAndReturnId(User user) { KeyHolder keyHolder = new GeneratedKeyHolder(); jdbcTemplate.update(connection -> { PreparedStatement ps = connection.prepareStatement( "INSERT INTO users (username, email) VALUES (?, ?)", Statement.RETURN_GENERATED_KEYS ); ps.setString(1, user.getUsername()); ps.setString(2, user.getEmail()); return ps; }, keyHolder); return keyHolder.getKey().longValue(); } ``` --- ### 四、关键注意事项 1. **SQL注入防护**:始终使用`?`占位符,**不要拼接SQL字符串** 2. **事务控制**:在Service层添加`@Transactional`注解保证原子性 ```java @Transactional public void transferEmail(Long fromId, Long toId) { // 多个update操作 } ``` 3. **异常处理**: - `EmptyResultDataAccessException`:查询无结果时抛出 - `DataIntegrityViolationException`:违反约束(如唯一索引) 4. **性能优化**:高频操作建议复用`BeanPropertyRowMapper`实例 ```java private static final RowMapper<User> USER_MAPPER = BeanPropertyRowMapper.newInstance(User.class); ``` --- ### 五、完整工具类示例 ```java @Repository public class UserRepository { private final JdbcTemplate jdbcTemplate; private static final RowMapper<User> USER_MAPPER = BeanPropertyRowMapper.newInstance(User.class); public UserRepository(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } // 实现上述CRUD方法... } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值