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

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 MongoDB,独享型 2核8GB
推荐场景:
构建全方位客户视图
简介: 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
相关文章
|
1天前
|
Java 调度 开发者
Java并发编程:深入理解线程池
在Java的世界中,线程池是提升应用性能、实现高效并发处理的关键工具。本文将深入浅出地介绍线程池的核心概念、工作原理以及如何在实际应用中有效利用线程池来优化资源管理和任务调度。通过本文的学习,读者能够掌握线程池的基本使用技巧,并理解其背后的设计哲学。
|
2天前
|
缓存 Java 编译器
JAVA并发编程synchronized全能王的原理
本文详细介绍了Java并发编程中的三大特性:原子性、可见性和有序性,并探讨了多线程环境下可能出现的安全问题。文章通过示例解释了指令重排、可见性及原子性问题,并介绍了`synchronized`如何全面解决这些问题。最后,通过一个多窗口售票示例展示了`synchronized`的具体应用。
|
6天前
|
Java 开发者
【Java编程新纪元】JDK 22:超级构造函数来袭,super(...) 前导语句改写编程规则!
【9月更文挑战第6天】JDK 22的超级构造函数特性是Java编程语言发展史上的一个重要里程碑。它不仅简化了代码编写,还提升了代码的可读性和维护性。我们有理由相信,在未来的Java版本中,还将有更多令人兴奋的新特性等待我们去发现和应用。让我们共同期待Java编程新纪元的到来!
|
6天前
|
Oracle Java 关系型数据库
【颠覆性升级】JDK 22:超级构造器与区域锁,重塑Java编程的两大基石!
【9月更文挑战第6天】JDK 22的发布标志着Java编程语言在性能和灵活性方面迈出了重要的一步。超级构造器和区域锁这两大基石的引入,不仅简化了代码设计,提高了开发效率,还优化了垃圾收集器的性能,降低了应用延迟。这些改进不仅展示了Oracle在Java生态系统中的持续改进和创新精神,也为广大Java开发者提供了更多的可能性和便利。我们有理由相信,在未来的Java编程中,这些新特性将发挥越来越重要的作用,推动Java技术不断向前发展。
|
2天前
|
安全 Java 数据安全/隐私保护
- 代码加密混淆工具-Java 编程安全性
在Java编程领域,保护代码安全与知识产权至关重要。本文探讨了代码加密混淆工具的重要性,并介绍了五款流行工具:ProGuard、DexGuard、Jscrambler、DashO 和 Ipa Guard。这些工具通过压缩、优化、混淆和加密等手段,提升代码安全性,保护知识产权。ProGuard 是开源工具,用于压缩和混淆Java代码;DexGuard 专为Android应用程序设计,提供强大加密功能;Jscrambler 基于云,保护Web和移动应用的JavaScript及HTML5代码;DashO 支持多种Java平台和
15 1
|
2天前
|
算法 Java 数据处理
Java并发编程:解锁多线程的力量
在Java的世界里,掌握并发编程是提升应用性能和响应能力的关键。本文将深入浅出地探讨如何利用Java的多线程特性来优化程序执行效率,从基础的线程创建到高级的并发工具类使用,带领读者一步步解锁Java并发编程的奥秘。你将学习到如何避免常见的并发陷阱,并实际应用这些知识来解决现实世界的问题。让我们一起开启高效编码的旅程吧!
|
4天前
|
Java 开发者
Java中的多线程编程基础与实战
【9月更文挑战第6天】本文将通过深入浅出的方式,带领读者了解并掌握Java中的多线程编程。我们将从基础概念出发,逐步深入到代码实践,最后探讨多线程在实际应用中的优势和注意事项。无论你是初学者还是有一定经验的开发者,这篇文章都能让你对Java多线程有更全面的认识。
14 1
|
1天前
|
安全 Java UED
Java并发编程:解锁多线程的潜力
在Java的世界里,并发编程如同一场精心编排的交响乐,每个线程扮演着不同的乐手,共同奏响性能与效率的和声。本文将引导你走进Java并发编程的大门,探索如何在多核处理器上优雅地舞动多线程,从而提升应用的性能和响应性。我们将从基础概念出发,逐步深入到高级技巧,让你的代码在并行处理的海洋中乘风破浪。
|
1天前
|
Java 程序员
Java编程中的对象和类: 初学者指南
【9月更文挑战第9天】在Java的世界中,对象和类构成了编程的基石。本文将引导你理解这两个概念的本质,并展示如何通过它们来构建你的程序。我们将一起探索类的定义,对象的创建,以及它们如何互动。准备好了吗?让我们开始这段Java的旅程吧!
|
9天前
|
存储 Java
Java编程中的对象序列化与反序列化
【9月更文挑战第2天】在Java的世界里,对象序列化和反序列化就像是给数据穿上了一件隐形的斗篷。它们让数据能够轻松地穿梭于不同的系统之间,无论是跨越网络还是存储在磁盘上。本文将揭开这层神秘的面纱,带你领略序列化和反序列化的魔法,并展示如何通过代码示例来施展这一魔法。
11 0