Java 编程问题:五、数组、集合和数据结构4

本文涉及的产品
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
简介: Java 编程问题:五、数组、集合和数据结构

示例 1(computeIfPresent()


假设我们有以下Map

Map<String, String> map = new HashMap<>();
map.put("postgresql", "127.0.0.1");
map.put("mysql", "192.168.0.50");

我们使用这个映射为不同的数据库类型构建 JDBC URL。


假设我们要为 MySQL 构建 JDBC URL。如果映射中存在mysql键,则应根据相应的值jdbc:mysql://192.168.0.50/customers_db计算 JDBC URL。但是如果不存在mysql键,那么 JDBC URL 应该是null。除此之外,如果我们的计算结果是null(无法计算 JDBC URL),那么我们希望从映射中删除这个条目。


这是V computeIfPresent(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)的工作。


在我们的例子中,用于计算新值的BiFunction如下所示(k是映射中的键,v是与键关联的值):

BiFunction<String, String, String> jdbcUrl 
  = (k, v) -> "jdbc:" + k + "://" + v + "/customers_db";


一旦我们有了这个函数,我们就可以计算出mysql键的新值,如下所示:

// jdbc:mysql://192.168.0.50/customers_db
String mySqlJdbcUrl = map.computeIfPresent("mysql", jdbcUrl);


由于映射中存在mysql键,结果将是jdbc:mysql://192.168.0.50/customers_db,新映射包含以下条目:

postgresql=127.0.0.1, mysql=jdbc:mysql://192.168.0.50/customers_db


再次调用computeIfPresent()将重新计算值,这意味着它将导致类似mysql= jdbc:mysql://jdbc:mysql://....的结果。显然,这是不可以的,所以请注意这方面。


另一方面,如果我们对一个不存在的条目进行相同的计算(例如,voltdb),那么返回的值将是null,映射保持不变:

// null
String voldDbJdbcUrl = map.computeIfPresent("voltdb", jdbcUrl);



示例 2(computeIfAbsent()

假设我们有以下Map

Map<String, String> map = new HashMap<>();
map.put("postgresql", "jdbc:postgresql://127.0.0.1/customers_db");
map.put("mysql", "jdbc:mysql://192.168.0.50/customers_db");


我们使用这个映射为不同的数据库构建 JDBC URL。


假设我们要为 MongoDB 构建 JDBC URL。这一次,如果映射中存在mongodb键,则应返回相应的值,而无需进一步计算。但是如果这个键不存在(或者与一个null值相关联),那么它应该基于这个键和当前 IP 进行计算并添加到映射中。如果计算值为null,则返回结果为null,映射保持不变。


嗯,这是V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)的工作。


在我们的例子中,用于计算值的Function将如下所示(第一个String是映射中的键(k),而第二个String是为该键计算的值):

String address = InetAddress.getLocalHost().getHostAddress();
Function<String, String> jdbcUrl 
  = k -> k + "://" + address + "/customers_db";

基于此函数,我们可以尝试通过mongodb键获取 MongoDB 的 JDBC URL,如下所示:

// mongodb://192.168.100.10/customers_db
String mongodbJdbcUrl = map.computeIfAbsent("mongodb", jdbcUrl);

因为我们的映射不包含mongodb键,它将被计算并添加到映射中。


如果我们的Function被求值为null,那么映射保持不变,返回值为null。


再次调用computeIfAbsent()不会重新计算值。这次,由于mongodb在映射中(在上一次调用中添加),所以返回的值将是mongodb://192.168.100.10/customers_db。这与尝试获取mysql的 JDBC URL 是一样的,它将返回jdbc:mysql://192.168.0.50/customers_db,而无需进一步计算。




示例 3(compute()


假设我们有以下Map

Map<String, String> map = new HashMap<>();
map.put("postgresql", "127.0.0.1");
map.put("mysql", "192.168.0.50");


我们使用这个映射为不同的数据库类型构建 JDBC URL。


假设我们要为 MySQL 和 Derby DB 构建 JDBC URL。在这种情况下,不管键(mysql还是derby存在于映射中,JDBC URL 都应该基于相应的键和值(可以是null)来计算。另外,如果键存在于映射中,并且我们的计算结果是null(无法计算 JDBC URL),那么我们希望从映射中删除这个条目。基本上,这是computeIfPresent()和computeIfAbsent()的组合。


这是V compute(K key, BiFunction<? super K,? super V,? extends V> remappingFunction)的工作。


此时,应写入BiFunction以覆盖搜索键的值为null时的情况:

String address = InetAddress.getLocalHost().getHostAddress();
BiFunction<String, String, String> jdbcUrl = (k, v) 
  -> "jdbc:" + k + "://" + ((v == null) ? address : v) 
    + "/customers_db";


现在,让我们计算 MySQL 的 JDBC URL。因为mysql键存在于映射中,所以计算将依赖于相应的值192.168.0.50。结果将更新映射中mysql键的值:

// jdbc:mysql://192.168.0.50/customers_db
String mysqlJdbcUrl = map.compute("mysql", jdbcUrl);


另外,让我们计算 Derby DB 的 JDBC URL。由于映射中不存在derby键,因此计算将依赖于当前 IP。结果将被添加到映射的derby键下:

// jdbc:derby://192.168.100.10/customers_db
String derbyJdbcUrl = map.compute("derby", jdbcUrl);


在这两次计算之后,映射将包含以下三个条目:


   postgresql=127.0.0.1

   derby=jdbc:derby://192.168.100.10/customers_db

   mysql=jdbc:mysql://192.168.0.50/customers_db


请注意,再次调用compute()将重新计算值。这可能导致不需要的结果,如jdbc:derby://jdbc:derby://...。


如果计算的结果是null(例如 JDBC URL 无法计算),并且映射中存在键(例如mysql),那么这个条目将从映射中删除,返回的结果是null。



示例 4(merge()


假设我们有以下Map

Map<String, String> map = new HashMap<>();
map.put("postgresql", "9.6.1 ");
map.put("mysql", "5.1 5.2 5.6 ");


我们使用这个映射来存储每个数据库类型的版本,这些版本之间用空格隔开。


现在,假设每次发布数据库类型的新版本时,我们都希望将其添加到对应键下的映射中。如果键(例如,mysql)存在于映射中,那么我们只需将新版本连接到当前值的末尾。如果键(例如,derby)不在映射中,那么我们现在只想添加它。


这是V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)的完美工作。


如果给定的键(K与某个值没有关联或与null关联,那么新的值将是V。如果给定键(K与非null值相关联,则基于给定的BiFunction计算新值。如果此BiFunction的结果是null,并且该键存在于映射中,则此条目将从映射中删除。


在我们的例子中,我们希望将当前值与新版本连接起来,因此我们的BiFunction可以写为:

BiFunction<String, String, String> jdbcUrl = String::concat;


我们在以下方面也有类似的情况:

BiFunction<String, String, String> jdbcUrl 
  = (vold, vnew) -> vold.concat(vnew);


例如,假设我们希望在 MySQL 的映射版本 8.0 中连接。这可以通过以下方式实现:

// 5.1 5.2 5.6 8.0
String mySqlVersion = map.merge("mysql", "8.0 ", jdbcUrl);


稍后,我们还将连接 9.0 版:

// 5.1 5.2 5.6 8.0 9.0
String mySqlVersion = map.merge("mysql", "9.0 ", jdbcUrl);


或者,我们添加 Derby DB 的版本10.11.1.1。这将导致映射中出现一个新条目,因为不存在derby键:

// 10.11.1.1
String derbyVersion = map.merge("derby", "10.11.1.1 ", jdbcUrl);


在这三个操作结束时,映射条目如下所示:

postgresql=9.6.1, derby=10.11.1.1, mysql=5.1 5.2 5.6 8.0 9.0




示例 5(putIfAbsent()


假设我们有以下Map

Map<Integer, String> map = new HashMap<>();
map.put(1, "postgresql");
map.put(2, "mysql");
map.put(3, null);


我们使用这个映射来存储一些数据库类型的名称。


现在,假设我们希望基于以下约束在该映射中包含更多数据库类型:


   如果给定的键存在于映射中,那么只需返回相应的值并保持映射不变。

   如果给定的键不在映射中(或者与一个null值相关联),则将给定的值放入映射并返回null。

嗯,这是putIfAbsent(K key, V value)的工作。


以下三种尝试不言自明:

String v1 = map.putIfAbsent(1, "derby");     // postgresql
String v2 = map.putIfAbsent(3, "derby");     // null
String v3 = map.putIfAbsent(4, "cassandra"); // null


映射内容如下:

1=postgresql, 2=mysql, 3=derby, 4=cassandra




112 从映射中删除


Map中删除可以通过一个键或者一个键和值来完成。

例如,假设我们有以下Map

Map<Integer, String> map = new HashMap<>();
map.put(1, "postgresql");
map.put(2, "mysql");
map.put(3, "derby");

通过键删除就像调用V Map.remove(Object key)方法一样简单。如果给定键对应的条目删除成功,则返回关联值,否则返回null

检查以下示例:

String r1 = map.remove(1); // postgresql
String r2 = map.remove(4); // null


现在,映射包含以下条目(已删除键 1 中的条目):

2=mysql, 3=derby


从 JDK8 开始,Map接口被一个新的remove()标志方法所丰富,该方法具有以下签名:boolean remove(Object key, Object value)。使用这种方法,只有在给定的键和值之间存在完美匹配时,才能从映射中删除条目。基本上,这种方法是以下复合条件的捷径:map.containsKey(key) && Objects.equals(map.get(key), value)。

让我们举两个简单的例子:


// true
boolean r1 = map.remove(2, "mysql");
// false (the key is present, but the values don't match)
boolean r2 = map.remove(3, "mysql");


结果映射包含一个剩余条目3=derby。

迭代和从Map中移除至少可以通过两种方式来完成:第一,通过Iterator(捆绑代码中存在的解决方案),第二,从 JDK8 开始,我们可以通过removeIf(Predicate<? super E> filter)来完成:

map.entrySet().removeIf(e -> e.getValue().equals("mysql"));


有关从集合中删除的更多详细信息,请参见“删除集合中与谓词匹配的所有元素”。



113 替换映射中的条目


Map替换条目是一个在很多情况下都会遇到的问题。要实现这一点并避免在辅助方法中编写一段意大利面条代码,方便的解决方案依赖于 JDK8replace()方法。

假设我们有下面的Melon类和Melon的映射:

public class Melon {
  private final String type;
  private final int weight;
  // constructor, getters, equals(), hashCode(),
  // toString() omitted for brevity
}
Map<Integer, Melon> mapOfMelon = new HashMap<>();
mapOfMelon.put(1, new Melon("Apollo", 3000));
mapOfMelon.put(2, new Melon("Jade Dew", 3500));
mapOfMelon.put(3, new Melon("Cantaloupe", 1500));


通过V replace(K key, V value)可以完成按键 2 对应的甜瓜的更换。如果替换成功,则此方法将返回初始的Melon:

// Jade Dew(3500g) was replaced
Melon melon = mapOfMelon.replace(2, new Melon("Gac", 1000));


现在,映射包含以下条目:

1=Apollo(3000g), 2=Gac(1000g), 3=Cantaloupe(1500g)


此外,假设我们想用键 1 和阿波罗甜瓜(3000g)替换条目。所以,甜瓜应该是同一个,才能获得成功的替代品。这可以通过布尔值replace(K key, V oldValue, V newValue)实现。此方法依赖于equals()合同来比较给定的值,因此Melon需要执行equals()方法,否则结果不可预知:


// true
boolean melon = mapOfMelon.replace(
  1, new Melon("Apollo", 3000), new Melon("Bitter", 4300));


现在,映射包含以下条目:

1=Bitter(4300g), 2=Gac(1000g), 3=Cantaloupe(1500g)



最后,假设我们要根据给定的函数替换Map中的所有条目。这可以通过void replaceAll(BiFunction<? super K,? super V,? extends V> function)完成。

例如,将所有重量超过 1000g 的瓜替换为重量等于 1000g 的瓜,下面的BiFunction形成了这个函数(k是键,v是Map中每个条目的值):


BiFunction<Integer, Melon, Melon> function = (k, v) 
  -> v.getWeight() > 1000 ? new Melon(v.getType(), 1000) : v;



接下来,replaceAll()出现在现场:

mapOfMelon.replaceAll(function);


现在,映射包含以下条目:

1=Bitter(1000g), 2=Gac(1000g), 3=Cantaloupe(1000g)



114 比较两个映射


只要我们依赖于Map.equals()方法,比较两个映射是很简单的。在比较两个映射时,该方法使用Object.equals()方法比较它们的键和值。

例如,让我们考虑两个具有相同条目的瓜映射(在Melon类中必须存在equals()hashCode()

public class Melon {
  private final String type;
  private final int weight;
  // constructor, getters, equals(), hashCode(),
  // toString() omitted for brevity
}
Map<Integer, Melon> melons1Map = new HashMap<>();
Map<Integer, Melon> melons2Map = new HashMap<>();
melons1Map.put(1, new Melon("Apollo", 3000));
melons1Map.put(2, new Melon("Jade Dew", 3500));
melons1Map.put(3, new Melon("Cantaloupe", 1500));
melons2Map.put(1, new Melon("Apollo", 3000));
melons2Map.put(2, new Melon("Jade Dew", 3500));
melons2Map.put(3, new Melon("Cantaloupe", 1500));


现在,如果我们测试melons1Mapmelons2Map是否相等,那么我们得到true

boolean equals12Map = melons1Map.equals(melons2Map); // true


但如果我们使用数组,这将不起作用。例如,考虑下面两个映射:

Melon[] melons1Array = {
  new Melon("Apollo", 3000),
  new Melon("Jade Dew", 3500), new Melon("Cantaloupe", 1500)
};
Melon[] melons2Array = {
  new Melon("Apollo", 3000),
  new Melon("Jade Dew", 3500), new Melon("Cantaloupe", 1500)
};
Map<Integer, Melon[]> melons1ArrayMap = new HashMap<>();
melons1ArrayMap.put(1, melons1Array);
Map<Integer, Melon[]> melons2ArrayMap = new HashMap<>();
melons2ArrayMap.put(1, melons2Array);


即使melons1ArrayMapmelons2ArrayMap相等,Map.equals()也会返回false

boolean equals12ArrayMap = melons1ArrayMap.equals(melons2ArrayMap);


这个问题源于这样一个事实:数组的equals()方法比较的是标识,而不是数组的内容。为了解决这个问题,我们可以编写一个辅助方法如下(这次依赖于Arrays.equals(),它比较数组的内容):

public static <A, B> boolean equalsWithArrays(
    Map<A, B[]> first, Map<A, B[]> second) {
  if (first.size() != second.size()) {
    return false;
  }
  return first.entrySet().stream()
    .allMatch(e -> Arrays.equals(e.getValue(), 
      second.get(e.getKey())));
}


115 对映射排序

排序一个Map有几种解决方案。首先,假设Melon中的Map

public class Melon implements Comparable {
  private final String type;
  private final int weight;
  @Override
  public int compareTo(Object o) {
    return Integer.compare(this.getWeight(), ((Melon) o).getWeight());
  }
  // constructor, getters, equals(), hashCode(),
  // toString() omitted for brevity
}
Map<String, Melon> melons = new HashMap<>();
melons.put("delicious", new Melon("Apollo", 3000));
melons.put("refreshing", new Melon("Jade Dew", 3500));
melons.put("famous", new Melon("Cantaloupe", 1500));

现在,让我们来研究几种排序这个Map的解决方案。基本上,我们的目标是通过一个名为Maps的工具类公开以下屏幕截图中的方法:


让我们在下一节中看看不同的解决方案。



通过TreeMap和自然排序按键排序


Map进行排序的快速解决方案依赖于TreeMap。根据定义,TreeMap中的键按其自然顺序排序。此外,TreeMap还有一个TreeMap(Map<? extends K,? extends V> m)类型的构造器:

public static <K, V> TreeMap<K, V> sortByKeyTreeMap(Map<K, V> map) {
  return new TreeMap<>(map);
}


调用它将按键对映射进行排序:

// {delicious=Apollo(3000g), 
// famous=Cantaloupe(1500g), refreshing=Jade Dew(3500g)}
TreeMap<String, Melon> sortedMap = Maps.sortByKeyTreeMap(melons);



通过流和比较器按键和值排序

一旦我们为映射创建了一个Stream,我们就可以很容易地用Stream.sorted()方法对它进行排序,不管有没有Comparator。这一次,让我们使用一个Comparator

public static <K, V> Map<K, V> sortByKeyStream(
    Map<K, V> map, Comparator<? super K> c) {
  return map.entrySet()
    .stream()
    .sorted(Map.Entry.comparingByKey(c))
    .collect(toMap(Map.Entry::getKey, Map.Entry::getValue,
      (v1, v2) -> v1, LinkedHashMap::new));
}
public static <K, V> Map<K, V> sortByValueStream(
    Map<K, V> map, Comparator<? super V> c) {
  return map.entrySet()
    .stream()
    .sorted(Map.Entry.comparingByValue(c))
    .collect(toMap(Map.Entry::getKey, Map.Entry::getValue,
      (v1, v2) -> v1, LinkedHashMap::new));
}

我们需要依赖LinkedHashMap而不是HashMap。否则,我们就不能保持迭代顺序。

让我们把映射分类如下:

// {delicious=Apollo(3000g), 
//  famous=Cantaloupe(1500g), 
//  refreshing=Jade Dew(3500g)}
Comparator<String> byInt = Comparator.naturalOrder();
Map<String, Melon> sortedMap = Maps.sortByKeyStream(melons, byInt);
// {famous=Cantaloupe(1500g), 
//  delicious=Apollo(3000g), 
//  refreshing=Jade Dew(3500g)}
Comparator<Melon> byWeight = Comparator.comparing(Melon::getWeight);
Map<String, Melon> sortedMap 
  = Maps.sortByValueStream(melons, byWeight);



通过列表按键和值排序

前面的示例对给定的映射进行排序,结果也是一个映射。如果我们只需要排序的键(我们不关心值),反之亦然,那么我们可以依赖于通过Map.keySet()创建的List作为键,通过Map.values()创建的List作为值:

public static <K extends Comparable, V> List<K>
    sortByKeyList(Map<K, V> map) {
  List<K> list = new ArrayList<>(map.keySet());
  Collections.sort(list);
  return list;
}
public static <K, V extends Comparable> List<V>
    sortByValueList(Map<K, V> map) {
  List<V> list = new ArrayList<>(map.values());
  Collections.sort(list);
  return list;
}


现在,让我们对映射进行排序:

// [delicious, famous, refreshing]
List<String> sortedKeys = Maps.sortByKeyList(melons);
// [Cantaloupe(1500g), Apollo(3000g), Jade Dew(3500g)]
List<Melon> sortedValues = Maps.sortByValueList(melons);


如果不允许重复值,则必须依赖于使用SortedSet的实现:

SortedSet<String> sortedKeys = new TreeSet<>(melons.keySet());
SortedSet<Melon> sortedValues = new TreeSet<>(melons.values());




116 复制哈希映射

执行HashMap的浅拷贝的简便解决方案依赖于HashMap构造器HashMap(Map<? extends K,? extends V> m)。以下代码是不言自明的:


Map<K, V> mapToCopy = new HashMap<>();
Map<K, V> shallowCopy = new HashMap<>(mapToCopy);


另一种解决方案可能依赖于putAll(Map<? extends K,? extends V> m)方法。此方法将指定映射中的所有映射复制到此映射,如以下助手方法所示:

@SuppressWarnings("unchecked")
public static <K, V> HashMap<K, V> shallowCopy(Map<K, V> map) {
  HashMap<K, V> copy = new HashMap<>();
  copy.putAll(map);
  return copy;
}


我们还可以用 Java8 函数式风格编写一个辅助方法,如下所示:

@SuppressWarnings("unchecked")
public static <K, V> HashMap<K, V> shallowCopy(Map<K, V> map) {
  Set<Entry<K, V>> entries = map.entrySet();
  HashMap<K, V> copy = (HashMap<K, V>) entries.stream()
    .collect(Collectors.toMap(
       Map.Entry::getKey, Map.Entry::getValue));
  return copy;
}


然而,这三种解决方案只提供了映射的浅显副本。获取深度拷贝的解决方案可以依赖于克隆库在第 2 章中介绍,“对象、不变性和switch表达式”。将使用克隆的助手方法可以编写如下:

@SuppressWarnings("unchecked") 
public static <K, V> HashMap<K, V> deepCopy(Map<K, V> map) {
  Cloner cloner = new Cloner();
  HashMap<K, V> copy = (HashMap<K, V>) cloner.deepClone(map);
  return copy;
}



117 合并两个映射


合并两个映射是将两个映射合并为一个包含两个映射的元素的映射的过程。此外,对于键碰撞,我们将属于第二个映射的值合并到最终映射中。但这是一个设计决定。


让我们考虑以下两个映射(我们特意为键 3 添加了一个冲突):

public class Melon {
  private final String type;
  private final int weight;
  // constructor, getters, equals(), hashCode(),
  // toString() omitted for brevity
}
Map<Integer, Melon> melons1 = new HashMap<>();
Map<Integer, Melon> melons2 = new HashMap<>();
melons1.put(1, new Melon("Apollo", 3000));
melons1.put(2, new Melon("Jade Dew", 3500));
melons1.put(3, new Melon("Cantaloupe", 1500));
melons2.put(3, new Melon("Apollo", 3000));
melons2.put(4, new Melon("Jade Dew", 3500));
melons2.put(5, new Melon("Cantaloupe", 1500));


从 JDK8 开始,我们在Map: V merge(K key, V value, BiFunction<? super V,? super V,? extends V> remappingFunction)中有以下方法。


如果给定的键(K与值没有关联,或者与null关联,那么新的值将是V。如果给定键(K与非null值相关联,则基于给定的BiFunction计算新值。如果此BiFunction的结果是null,并且该键存在于映射中,则此条目将从映射中删除。


基于这个定义,我们可以编写一个辅助方法来合并两个映射,如下所示:

public static <K, V> Map<K, V> mergeMaps(
    Map<K, V> map1, Map<K, V> map2) {  
  Map<K, V> map = new HashMap<>(map1);
  map2.forEach(
    (key, value) -> map.merge(key, value, (v1, v2) -> v2));
  return map;
}

请注意,我们不会修改原始映射。我们更希望返回一个包含第一个映射的元素与第二个映射的元素合并的新映射。在键冲突的情况下,我们用第二个映射(v2中的值替换现有值。


基于Stream.concat()可以编写另一个解决方案。基本上,这种方法将两个流连接成一个Stream。为了从一个Map创建一个Stream,我们称之为Map.entrySet().stream()。在连接从给定映射创建的两个流之后,我们只需通过toMap()收集器收集结果:

public static <K, V> Map<K, V> mergeMaps(
    Map<K, V> map1, Map<K, V> map2) {
  Stream<Map.Entry<K, V>> combined 
    = Stream.concat(map1.entrySet().stream(), 
      map2.entrySet().stream());
  Map<K, V> map = combined.collect(
    Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue,
      (v1, v2) -> v2));
  return map;
}


作为奖励,Set(例如,整数的Set可以按如下方式排序:

List<Integer> sortedList = someSetOfIntegers.stream()
  .sorted().collect(Collectors.toList());


对于对象,依赖于sorted(Comparator<? super T>




118 删除集合中与谓词匹配的所有元素


我们的集合将收集一堆Melon

public class Melon {
  private final String type;
  private final int weight;
  // constructor, getters, equals(), 
  // hashCode(), toString() omitted for brevity
}

让我们在整个示例中假设以下集合(ArrayList,以演示如何从集合中移除与给定谓词匹配的元素:

List<Melon> melons = new ArrayList<>();
melons.add(new Melon("Apollo", 3000));
melons.add(new Melon("Jade Dew", 3500));
melons.add(new Melon("Cantaloupe", 1500));
melons.add(new Melon("Gac", 1600));
melons.add(new Melon("Hami", 1400));

让我们看看下面几节给出的不同解决方案。

相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
相关文章
|
3天前
|
存储 算法 Java
Java Set因其“无重复”特性在集合框架中独树一帜
【10月更文挑战第14天】Java Set因其“无重复”特性在集合框架中独树一帜。本文深入解析Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定的数据结构(哈希表、红黑树)确保元素唯一性,并提供最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的`hashCode()`与`equals()`方法。
14 3
|
1天前
|
算法 Java 数据处理
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。
从HashSet到TreeSet,Java集合框架中的Set接口及其实现类以其“不重复性”要求,彻底改变了处理唯一性数据的方式。HashSet基于哈希表实现,提供高效的元素操作;TreeSet则通过红黑树实现元素的自然排序,适合需要有序访问的场景。本文通过示例代码详细介绍了两者的特性和应用场景。
15 6
|
1天前
|
存储 Java 数据处理
Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。
【10月更文挑战第16天】Java Set:无序之美,不重复之魅!Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。通过 hashCode() 和 equals() 方法实现唯一性,适用于需要唯一性约束的数据处理。示例代码展示了如何使用 HashSet 添加和遍历元素,体现了 Set 的高效性和简洁性。
12 4
|
1天前
|
存储 Java
深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。
【10月更文挑战第16天】本文深入探讨了Java集合框架中的HashSet和TreeSet,解析了两者在元素存储上的无序与有序特性。HashSet基于哈希表实现,添加元素时根据哈希值分布,遍历时顺序不可预测;而TreeSet利用红黑树结构,按自然顺序或自定义顺序存储元素,确保遍历时有序输出。文章还提供了示例代码,帮助读者更好地理解这两种集合类型的使用场景和内部机制。
10 3
|
3天前
|
存储 Java 数据处理
Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。
Java Set:无序之美,不重复之魅!Set 是 Java 集合框架中的一个接口,不包含重复元素且不保证元素顺序。它通过 hashCode() 和 equals() 方法确保元素唯一性,适用于需要唯一性约束的数据处理。示例代码展示了如何使用 HashSet 实现这一特性。
12 5
|
4天前
|
Java 开发者
在Java的集合世界里,Set以其独特的特性脱颖而出,它通过“哈希魔法”和“红黑树防御”两大绝技
【10月更文挑战第13天】在Java的集合世界里,Set以其独特的特性脱颖而出。它通过“哈希魔法”和“红黑树防御”两大绝技,有效抵御重复元素的侵扰,确保集合的纯洁性和有序性。无论是“人海战术”还是“偷梁换柱”,Set都能从容应对,成为开发者手中不可或缺的利器。
19 6
|
1天前
|
存储 Java 数据处理
Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位
【10月更文挑战第16天】Java Set接口凭借其独特的“不重复”特性,在集合框架中占据重要地位。本文通过快速去重和高效查找两个案例,展示了Set如何简化数据处理流程,提升代码效率。使用HashSet可轻松实现数据去重,而contains方法则提供了快速查找的功能,彰显了Set在处理大量数据时的优势。
7 2
|
1天前
|
Java 开发者
在Java集合世界中,Set以其独特的特性脱颖而出,专门应对重复元素
在Java集合世界中,Set以其独特的特性脱颖而出,专门应对重复元素。通过哈希表和红黑树两种模式,Set能够高效地识别并拒绝重复元素的入侵,确保集合的纯净。无论是HashSet还是TreeSet,都能在不同的场景下发挥出色的表现,成为开发者手中的利器。
11 2
|
3天前
|
存储 Java 数据处理
在Java集合框架中,Set接口以其独特的“不重复”特性脱颖而出
【10月更文挑战第14天】在Java集合框架中,Set接口以其独特的“不重复”特性脱颖而出。本文通过两个案例展示了Set的实用性和高效性:快速去重和高效查找。通过将列表转换为HashSet,可以轻松实现去重;而Set的contains方法则提供了快速的元素查找功能。这些特性使Set成为处理大量数据时的利器。
12 4
|
3天前
|
Java
Java Set 是一个不包含重复元素的集合接口,确保每个元素在集合中都是唯一的
【10月更文挑战第14天】Java Set 是一个不包含重复元素的集合接口,确保每个元素在集合中都是唯一的。本文介绍了 Set 的独特特性和两个常用实现类:基于哈希表的 HashSet 和基于红黑树的 TreeSet。通过示例代码展示了它们如何高效地处理唯一性约束的数据。
15 3