JavaTutorialNetwork 中文系列教程(四)

原文:JavaTutorialNetwork

协议:CC BY-NC-SA 4.0

Java ConcurrentHashSet示例

原文: https://javatutorial.net/java-concurrenthashset-example

Java 8 最终允许我们(程序员)在 Java 中创建线程安全的ConcurrentHashSet。 在此之前,这根本是不可能的。 有一些变种试图简化上述类的实现,其中之一就是使用带有虚拟值ConcurrentHashMap 。 但是,您可能已经猜到,ConcurrentHashMap 的所有即兴创作都有其局限性和风险。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Java 8 允许我们使用keySet(defaultVal)newKeySet()方法来返回 Set,这恰好是一个合适的集合。 这样可以访问用户可以使用的许多必要功能:contains()remove()等。 注意: 这些方法在ConcurrentHashMap中可用,而不是在ConcurrentMap接口中可用, 意味着必须创建ConcurrentHashMap类型的变量,并将其用作引用。 另一种方法是对象的简单转换。

Java 并发 API 中包含许多Collection类,例如ArrayListCopyOnArrayListHashMapConcurrentHashMapHashSetCopyOnWriteArraySet。 但是,尽管有所有这些示例,但没有类似ConcurrentHashSet的东西。 许多开发人员说,他们可以使用具有相同值的ConcurrentHashMap来实现所需的集合,但是这种方法的问题在于,您拥有的是映射而不是集合。 因此,这将导致无法使用伪值对ConcurrentHashMap执行设置操作。 简而言之,它不是Set

还有其他尝试创建ConcurrentHashSet的即兴创作,但现在它们都已过去,因为 Java 8 添加了newKeySet(),它返回由ConcurrentHashMap支持的Set

如何在 Java 8 中创建ConcurrentHashSet

ConcurrentHashMap<String, Integer> example = new ConcurrentHashMap<>(); 
Set<String> exampleSet = example.newKeySet(); 
exampleSet.add("example"); 
exampleSet.add("example2");
exampleSet.contains("example2"); 
exampleSet.remove("example"); 

在上面的示例中,我们创建了集合,并向其中添加了 2 个元素,检查它是否包含某个元素并删除了某个元素。

注意: 这是在 Java 中创建线程安全Set的唯一方法。

Java 程序使用java.util.concurrent.ConcurrentHashMap类上添加的新方法创建ConcurrentHashSet

import java.util.Set; 
import java.util.concurrent.ConcurrentHashMap; 

public class Example {
    
     
   public static void main(String[] args) throws Exception {
    
     
      ConcurrentHashMap shoesCost = new ConcurrentHashMap<>(); 
      shoesCost.put("Nike", 80); 
      shoesCost.put("Adidas", 40); 
      shoesCost.put("Reebok", 76); 

      Set shoeCostSet = shoesCost.keySet(); 

      shoeCostSet = shoesCost.newKeySet(); 
      System.out.println("before adding element into concurrent set: " + shoeCostSet);
      shoeCostSet.add("Puma"); 
      System.out.println("after adding element into concurrent set: " + shoeCostSet); 
      shoeCostSet.contains("Adidas"); 
      shoeCostSet.remove("Reebok"); 
  } 
} 

输出

before adding an element into the concurrent set: [Nike, Adidas, Reebok] 
after adding an element into the concurrent set: [Nike, Adidas, Reebok, Puma] 

Java HashMap示例

原文: https://javatutorial.net/java-hashmap-example

数组的项目存储为有序集合,我们可以通过索引访问它们。 另一方面,Java 中的HashMap类将项存储为组对,即键/值。 可以通过其他类型的索引访问它们。 此类不能保证随着时间的流逝会有恒定的顺序。 假设正确实现,HashMap可为诸如getput之类的基本操作提供恒定时间的性能。 就像HashSet类一样,HashMap具有初始容量和负载因子。 容量是哈希表中的存储桶数,负载因子只是衡量哈希表在自动增加其容量之前可以容纳多少的度量。像HashSet一样,默认加载因子为 0.75。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为什么HashMap重要和有用

  • 由于其键/值对,因此易于组织数据。
  • HashMap允许 1 个null键和多个null值。
  • HashMap不允许重复的键,但允许重复的值。
  • HashMap扩展了抽象类AbstractMap

继承图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

HashMap中的构造方法摘要

  1. HashMap():使用默认的初始容量 16 和默认的加载因子 0.75 初始化一个空的HashMap
  2. HashMap(int initialCapacity):使用指定的初始容量和默认加载因子 0.75 初始化一个空的HashMap
  3. HashMap(int initialCapacity, float loadFactor):使用指定的初始容量和负载因子初始化一个空的HashMap
  4. HashMap(Map <? extends K, ? extends V> m):使用与指定Map相同的映射初始化一个新的HashMap

HashMap类中的方法

  1. void clear():从此映射中删除所有映射。
  2. Object clone():克隆另一个HashMap,但是不会克隆键和值本身。
  3. boolean containsKey(Object key):如果key在哈希图中,则返回true,否则返回false
  4. boolean containsValue(Object value):如果值在哈希映射中的任何键中,则返回true,否则返回false
  5. V get(Object key):返回指定键所映射到的值;如果此映射不包含该键的映射,则返回null
  6. boolean isEmpty():如果地图不包含任何元素,则返回true
  7. V put(K key, V value):将指定的值添加到指定的键。
  8. V remove(Object key):从哈希图中删除键。
  9. V replace(K key, V value):仅当当前映射到某个值时,才替换指定键的条目。
  10. int size():返回此映射中的键/值对的数量。

使用HashMap进行基本操作,以及它们如何工作以及如何相互交互

import java.util.HashMap;

public class HashMapExample {
    
    
  public static void main(String[] args) {
    
    
    HashMap<String, String> animals = new HashMap<String, String>();

    // putting a key-value pairs within a HashMap
    // animal -> name
    animals.put("Elephant", "Dicky");
    animals.put("Tiger", "Sammy");
    animals.put("Lion", "Sim");

    System.out.println(animals); 

    // accessing an item using get()
    // gives back the value to the specified key, which means it will return back "Sim"
    System.out.println("The name of 'Lion' is: " + animals.get("Lion");

    // removing an item using remove()
    animals.remove("Elephant");

    // getting the size of the hash map
    System.out.println("The size of the hash map before clearing: " + animals.size());

    // clearing/deleting a whole hash map using clear()
    animals.clear()

    // getting the size of the hash map
    System.out.println("The size of the hash map after clearing: " + animals.size());
  }
}

输出

[Lion=Sam, Tiger=Sammy, Elephant=Dicky]
The name of 'Lion' is: Sam
The size of the hash map before clearing: 2
The size of the hash map after clearing: 0

Java LinkedHashMap示例

原文: https://javatutorial.net/java-linkedhashmap-example

LinkedHashMap是哈希表和链表的组合,以可预测的迭代顺序实现Map接口。HashMapLinkedHashMap之间的区别在于LinkedHashMap维护着一个双向链表,该列表允许来回扫描所有条目。 顺序保持不变,这意味着将键插入映射的顺序。 如果将重新插入到映射中,则顺序不会受到影响。LinkedHashMap提供所有可选的Map操作,还允许空元素。 就时间复杂度而言,就像HashMap一样,它为诸如addcontainsremove之类的基本操作提供了恒定时间的性能。LinkedHashMap有两个影响其性能的参数,它们是初始容量和负载系数。 此类中的迭代不受容量的影响,因此在为初始容量选择过高的值方面,它比HashMap效率更高。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为什么LinkedHashMap有用

  1. 它将按照输入到映射中的顺序遍历所有条目。
  2. 就像在HashMap中一样,允许使用空值。
  3. 使用双向链接的链表,使扫描效率更高。
  4. 它对添加或访问项目的顺序有额外的了解。

继承图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

继承图

LinkedHashMap中的构造方法摘要

  1. LinkedHashMap():构造一个空的插入顺序的 LinkedHashMap,其默认初始容量(16)和默认负载因子(0.75)。
  2. LinkedHashMap(int initialCapacity):构造一个空的插入顺序的LinkedHashMap,并将容量设置为指定的 initialCapacity 参数和默认负载因子(0.75)。
  3. LinkedHashMap(int initialCapacity, float loadFactor):使用指定的容量和负载因子构造一个空的插入顺序的LinkedHashMap
  4. LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder):使用指定的初始容量,负载系数和排序方式构造一个空的LinkedHashMap实例。
  5. LinkedHashMap(Map <? extends K, ? extends V> m):构造一个插入顺序的LinkedHashMap实例,该实例具有与指定映射相同的映射。

LinkedHashMap类中的方法

  1. void clear():从此映射中删除所有映射。
  2. boolean containsValue(Object value):如果LinkedHashMap包含指定的值,则返回true,否则返回false
  3. Set<Map.Entry<K,V>> entrySet():返回当前映射中包含的映射的设置视图。
  4. V get(Object key):返回指定键所映射到的值;如果该映射不包含该键的映射,则返回null
  5. V getOrDefault(Object key, V defaultValue):返回指定键所映射到的值;如果该映射不包含该键的映射,则返回defaultValue
  6. Set<K> keySet:返回此映射中包含的键的设置视图。
  7. protected boolean removeEldestEntry(Map. Entry<K, V> eldest):返回此映射中包含的值的集合视图。
  8. void put(K key, V value):将指定的Value与指定的Key相关联。 (它是从 Java 中的Map类继承的)

有关EnumSet主要方法的更多信息,请随时访问原始 Oracle 文档

获取LinkedHashMap的大小,检查其在特定键中是否包含某个值,并检查其是否为空,最后从LinkedHashMap中删除键:

import java.util.*; 

public class LinkedHashMapExample
{
    
     
    public static void main(String args[]) 
    {
    
     
        LinkedHashMap<String, String> student = 
                       new LinkedHashMap<String, String>(); 
        student.put("name", "Joe"); 
        student.put("major", "Computer Science"); 
        student.put("marital status", "Single"); 

        System.out.println(student); 
        // printing the value of the key called name
        System.out.println("Key 'name's value: " + student.get("name")); 

        // getting the size of the linkedHashMap (size() is inherited from Map)
        System.out.println("Size of the LinkedHashMap: " + student.size()); 

        // checking whether the map is empty or not
        System.out.println("Is the map empty: " + student.isEmpty()); 

        // checking whether the linkedHashMap contains the key specified as an argument
        System.out.println("Does it contain 'marital status'? "+  student.containsKey("marital status")); 

        // deleting/removing an element from the linkedHashMap works by using the 
        //remove method
        System.out.println("Deleting element 'name': " + student.remove("name")); 
    } 
}

输出

{
    
    name=Joe, major=Computer Science, marital status = Single}
Key 'name's value: Joe
Size of the LinkedHashMap: 3
Is the map empty: false
Does it contain 'marital status'? true
Deleting element 'name': "Joe"

使用clear()清除LinkedHashMap

import java.util.*; 

public class LinkedHashMapExample {
    
     
    public static void main(String[] args) 
    {
    
     
        LinkedHashMap<String, String> student = 
        new LinkedHashMap<String, String>(); 

        li_hash_map.put("name", "Joe"); 
        li_hash_map.put("major", "Computer Science"); 
        li_hash_map.put("marital status", "Single"); 

        System.out.println("Current stage of linkedHashMap: " + student); 

        // Clearing the linked hash map using clear() 
        li_hash_map.clear(); 

        System.out.println("Stage after the clear() method: " + student); 
    } 
}

输出

Current stage of linkedHashMap: {
    
    "name"="Joe", "major"="Computer Science", "marital status ="Single"}
Stage after the clear() method: {
    
    }

Java TreeMap示例

原文: https://javatutorial.net/java-treemap-example

TreeMap实现了Map接口,还实现了NavigableMap以及Abstract类。 映射是根据其键的自然顺序或通过提供初始化时间的比较器进行排序的。 就时间复杂度而言,此实现为containsKeygetputremove操作提供了log(n)成本。 请务必注意,TreeMap不会同步,因为如果地图被多个线程访问,并且如果至少胎面在结构上修改了地图,则必须在外部进行同步。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

有关TreeMap的要点

  • TreeMap实现Map接口。
  • TreeMap不允许使用空键。 而是,抛出NullPointerException。 虽然,多个空值可以与不同的键关联。
  • TreeMapJava 集合框架的成员。

TreeMap的继承图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

TreeMap继承图

TreeMap中的构造方法摘要

  1. TreeMap():使用其键的自然顺序构造一个新的空树映射。
  2. TreeMap(Comparator<? super K> comparator):构造一个新的空树映射,根据给定的比较器排序。
  3. TreeMap(Map<? extends K, ?extends V> m)::构造一个新的树映射,该树映射包含与给定图相同的映射,并根据其键的自然顺序进行排序。
  4. TreeMap(SortedMap<K, ? extends V> m):构造一个新的树映射,该树映射包含与指定排序图相同的映射并使用相同的排序。

TreeMap类中的方法

  1. void clear():删除树形图中的所有元素。
  2. Object clone():返回TreeMap实例的浅表副本。
  3. Comprator<? super K> comparator:返回用于对当前映射中的键进行排序的比较器;如果映射使用其键的自然排序,则返回null
  4. boolean containsKey(Object key):如果当前树映射中存在指定的键,则返回true
  5. boolean containsValue(Object value):如果当前任意一个地图键都存在指定值,则返回true
  6. V put(K key, V value):将指定的值放入指定的键。
  7. V remove(Object key):从当前映射中删除指定的键。
  8. V replace(K key, V value):仅当当前映射到某个值时,才替换指定键的条目。
  9. int size():获取当前树形图具有的元素数。
  10. Collection<V> values():返回当前映射中包含的值的集合视图。

有关EnumSet主要方法的更多信息,请随时访问原始 Oracle 文档

使用containsKey()containsValue()检查当前树形图是否包含指定的键或值,并使用put()填充TreeMap

import java.util.*; 

public class TreeMapExample {
    
     
    public static void main(String[] args) 
    {
    
     
        TreeMap<Integer, String> treeMapExample =  
        new TreeMap<Integer, String>(); 

        // assing values to keys
        treeMapExample.put(5, "java"); 
        treeMapExample.put(15, "tutorial"); 
        treeMapExample.put(20, "dot"); 
        treeMapExample.put(25, "net"); 

        System.out.println("Current stage of the treeMap: " + treeMapExample); 

        // Checking for the key '15' 
        System.out.println("Is key 15 present in the map: " +  
        treeMapExample.containsKey(15); 

        // Checking for the key '5' 
        System.out.println("Is the key 5 present? " +  
        treeMapExample.containsKey(5)); 

        // Checking for value "java"
        System.out.println("Is the value 'java' present? " +  
        treeMapExample.containsValue("java")); 

        // Checking for value "tutorial"
        System.out.println("Is the value 'tutorial' present? " +  
        treeMapExample.containsValue("tutorial"));
    } 
}

输出

Is the key 15 present? true
Is the key 5 present? true
Is the value 'java' present? true
Is the value 'tutorial' present? true

使用remove()TreeMap中删除元素

import java.util.*;  
public class treeMapExample {
    
      
   public static void main(String args[]) {
    
      
    TreeMap<Integer,String> treeMap=new TreeMap<Integer,String>();    
      // populating the tree map using put()
      map.put(5,"Joe");    
      map.put(10,"Mike");    
      map.put(15,"Antony");    
      System.out.println("Before remove(): ");  
      // looping through the tree map so we can get each element
      for(Map.Entry m:map.entrySet())  
      {
    
      
          // print key and value
          System.out.println(m.getKey()+" "+m.getValue());      
      }  
      map.remove(15);      
      System.out.println("After remove(): ");  
      for(Map.Entry m:map.entrySet())  
      {
    
      
           // print key and value
          System.out.println(m.getKey()+" "+m.getValue());      
      }  
      }  
}

输出

Before remove:
5 Joe
10 Mike
15 Antony
After Remove
5 Joe
10 Mike

Java EnumMap示例

原文: https://javatutorial.net/java-enummap-example

EnumMap类实现Map类,并允许使用枚举类型的键。 枚举映射按其键的自然顺序进行维护。 重要的是要注意不允许使用空键。 如果尝试添加空键,则将抛出NullPointerException

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

但是,即使不允许null键,也允许null值。 由于所有可能的密钥都是预先已知的,因此可以更快地进行哈希计算。

  • EnumMap不同步。
  • EnumMapHashMap快得多

EnumMap

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

EnumMap继承图

EnumMap中的构造方法

  1. EnumMap(Class<K> keyType):使用指定的密钥类型创建一个空的枚举映射。
  2. EnumMap(EnumMap<K, ? extends V> m):创建一个与指定枚举图具有相同键类型的枚举图,最初包含相同的映射(如果有)。
  3. EnumMap(Map<K, ? extends V> m):创建一个从指定映射初始化的枚举映射。

EnumMap中的方法

  1. void clear():从此映射中删除所有映射。
  2. EnumMap<K, V> clone():返回一个EnumMap,它表示另一个副本。
  3. boolean containsKey(Object key):如果当前映射包含指定的键,则返回 true,否则返回 false。
  4. boolean containsValue(Object value):如果当前映射包含指定值,则返回 true,否则返回 false。
  5. boolean equals(Object o):比较指定对象与当前映射是否相等。
  6. V get(Object key):返回指定键所映射到的值;如果键不包含任何值,则返回 null。
  7. int hashCode():返回当前地图的哈希码。
  8. V put(K key, V value):将指定的值映射到指定的键。
  9. V remove(Object key):从地图上移除指定的键。
  10. int size():返回地图中的对数。

有关所有方法的文档,请访问 Oracle 官方文档页面

一个程序中某些方法的示例

import java.util.EnumMap; 

public class EnumMapExample
{
    
     
    public enum Hardware 
    {
    
     
        MONITOR, MOUSE, KEYBOARD;
    } 

    public static void main(String args[])  
    {
    
         
        EnumMap<Hardware, String> hardwareMapInstance = new EnumMap<Hardware, String>(Hardware.class); 

        /* hardwareMapInstance is empty. Let's populate it. */

        hardwareMapInstance.put(Hardware.MONITOR, "Samsung"); 
        hardwareMapInstance.put(Hardware.MOUSE, "Logitech g403"); 
        hardwareMapInstance.put(Hardware.KEYBOARD, "Razer"); 

        /* After having put items, let's see the map size. */
        System.out.println("Size: " + hardwareMapInstance.size()); // we could use .size() to loop through an enummap

        /* This would print the EnumMap in natural order. (MONITOR => MOUSE => KEYBOARD) */
        System.out.println("EnumMap: " + hardwareMapInstance); 

        /* Getting a specific value from an EnumMap. */
        System.out.println("Key: " + Hardware.MONITOR +" Value: " + hardwareMapInstance.get(Hardware.MONITOR)); 

        /* Checking if the EnumMap contains a particular key. */
        System.out.println("Does Hardware has " + Hardware.MOUSE + ": " + hardwareMapInstance.containsKey(Hardware.MOUSE)); 

        /* Checking if EnumMap contains a particular value */
        System.out.println("Does Hardware has " + Hardware.KEYBOARD + " : " + hardwareMapInstance.containsValue("Razer")); 

        /* Let's clear the EnumMap */
        System.out.println("Clearing the map...");
        hardwareMapInstance.clear();

        /* Let's get the size now again */
        System.out.println("The size after clear() is: " + hardwareMapInstance.size());
    } 
}

输出

Size: 3
EnumMap: {
    
    MONITOR=Samsung, MOUSE=Logitech g403, KEYBOARD=Razer}
Key: MONITOR Value: Samsung
Does Hardware has MOUSE: true
Does Hardware has KEYBOARD : true
Clearing the map...
The size after clear() is: 0

Java WeakHashMap示例

原文: https://javatutorial.net/java-weakhashmap-example

Java 中的WeakHashMap实现了Map接口,并表示一个具有弱键的哈希表。 如果按键不是通常使用的,则将从地图中自动删除该条目。 这就是它与其他Map实现的区别。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

支持空值和非空值,并且在初始容量和负载因子方面,其性能类似于HashMap类。

默认情况下,此类不同步。

HashMapWeakHashMap之间的主要区别

  1. HashMap 具有强引用,而WeakHashMap具有弱引用
  2. HashMap在垃圾收集器中占主导地位。
  3. WeakHashMap类中没有clone()方法,因为它仅实现Map接口。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

WeakHashMap实现Map并扩展AbstractMap

WeakHashMap中的构造方法

  1. WeakHashMap():创建一个空的WeakHashMap,默认初始容量为 16,负载系数为 0.75。
  2. WeakHashMap(int initialCapacity):创建具有指定容量的空WeakHashMap
  3. WeakHashMap(int initialCapacity, float loadFactor):创建具有指定容量和负载因子的空WeakHashMap
  4. WeakHashMap(Map<? extends K, ? extends V> m):创建一个具有与指定映射相同的映射的新WeakHashMap

WeakHashMap中的方法

  1. void clear():从当前映射中删除所有映射。
  2. boolean containsKey(Object key):如果当前映射包含指定键的映射,则返回true
  3. boolean containsValue(Object value):如果当前映射中有映射到指定值的一个或多个键,则返回true
  4. V get(Object key):返回指定键所映射到的值;如果该映射不包含指定键的映射,则返回null
  5. boolean isEmpty():如果映射为空,则返回true,否则返回false
  6. V put(K key, V value):将指定值“放入”当前映射中的指定键。
  7. V remove(Object key):如果存在,则从此WeakHashMap中删除键的映射。
  8. int size():返回映射中的映射数。

有关所有方法的文档,请访问 Oracle 官方文档页面

一个执行上述某些方法的示例程序:

// importing the necessary library which is under java.util.*
import java.util.*; 

public class WeakHashMapExample 
{
    
     
    public static void main(String args[])throws Exception 
    {
    
     
        // declaration of an instance of WeakHashMap that takes a number as a key and string as a value
        Map<Number, String> animals = new WeakHashMap<Number, String>(); 
        // populating the map
        animals.put(1, "Elephant"); 
        animals.put(2, "Tiger"); 
        animals.put(3, "Lion"); 

        // condition that checks for a certain value
        if(animals.containsValue("Tiger")) 
            System.out.println("Tiger exists."); 

        // condition that checks for a certain key
        if(animals.containsKey(3)) 
            System.out.println("'3' key exists."); 

        // removing a specific key
        animals.remove(1);

        System.out.println(animals);

        // deletes all mappings
        animals.clear(); 

        // check if weakhashmap is empty
        if(animals.isEmpty()) 
            System.out.println(animals); 
    }
}

输出

Tiger exists.
'3' key exists.
{
    
    3=Lion, 2=Tiger}
{
    
    }

Java IdentityHashMap示例

原文: https://javatutorial.net/java-identityhashmap-example

IdentityHashMap实现Map接口,并且当检查为k1 == k2时,两个键被认为是相等的(不是通过使用等于方法)。 这本身违反了Map的一般合同,这意味着IdentityHashMap显然不是而非通用Map的实现。 在很多情况下,此类会很有用。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

IdentityHashMap允许空值和空键以及所有可选的映射操作。 但是,此类不能保证顺序会随着时间的推移保持恒定。

就时间复杂度而言,此类为基本操作提供了恒定时间的性能(例如getput)。

另外,需要注意的是,在必须指定最大大小的意义上,此类不是动态(就像在数组中一样)。 如果映射的大小被充分超过,它将非常昂贵,因此这就是始终提供较大的最大大小的一个好主意。 (比简单的错误要便宜得多!)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

IdentityHashMap:实现和扩展的内容。

IdentityHashMap中的构造方法

  1. IdentityHashMap():创建一个新的身份哈希映射,默认最大大小为 21。
  2. IndetityHashMap(int ExpectedMaxSize):创建一个具有预期最大大小的新空映射。
  3. IdentityHashMap(Map<? extends K, ? extends V> m):创建一个新的Identity,其映射包含与指定映射相同的映射。

IdentityHashMap中的方法

  1. void clear():从此映射中删除所有映射。
  2. Object clone():返回此标识哈希图的副本(重要的是要注意,键和值本身未克隆)。
  3. boolean containsKey(Object key):如果映射包含指定的键,则返回true,否则返回false
  4. boolean containsValue(Object value):如果映射包含指定值,则返回true,否则返回false
  5. boolean equals(Object o):比较指定对象与此映射是否相等。
  6. V get(Object key):返回指定键所映射到的值;如果映射不包含对该键的映射,则返回null
  7. int hashCode():返回此映射的哈希码值。
  8. boolean isEmpty():如果映射为空,则返回true,否则返回false
  9. V put(K key, V value):将指定值与映射中的指定键关联。
  10. V remove(Object key):删除到映射中指定键的映射(如果存在)。
  11. int size():返回此标识哈希图中的键值映射数。

有关所有方法的文档,请访问 Oracle 官方文档页面

使用上述大多数方法的程序示例:

import java.util.Map; 
import java.util.HashMap; 
import java.util.IdentityHashMap; 

public class IdentityHashMapExample  
{
    
     
    public static void main(String[] args)  
    {
    
     
        Map identityHashMap = new IdentityHashMap(); 

        identityHashMap.put("key", "value");  
        System.out.println("Size of IdentityHashMap: " + identityHashMap.size());  

        identityHashMap.put("key1", "value1");
        System.out.println("Size of IdentityHashMap: " + identityHashMap.size());  

        System.out.println("Does it contain key 'key1': " + identityHashMap.containsKey("key1"));

        System.out.println("Value of key 'key1': " + identityHashMap.get("key1"));

        System.out.println("Size of map before clear: " + identityHashMap.size());

        identityHashMap.clear();

        System.out.println("Size of map after clear: " + identityHashMap.size());
    } 
}

输出

Size of IdentityHashMap: 1
Size of IdentityHashMap: 2
Does it contain key 'key1': true
Value of key 'key1': value1
Size of map before clear: 2
Size of map after clear: 0

Java SortedMap示例

原文: https://javatutorial.net/java-sortedmap-example

SortedMap接口扩展了映射,并确保所有条目都按升序排列(因此,SortedMap)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果要按降序排列它,则需要重写SortedMap中的Compare方法,我们将在稍后进行操作。TreeMap实现SortedMap,并按其自然顺序或指定的比较器对键进行排序。在TreeMap 中,不允许使用空键和空值。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

方法摘要

  1. Comparator <? super K> comparator():返回用于对当前映射中的键进行排序的比较器;如果当前映射使用其键的自然顺序,则返回null
  2. Set<Map.Entry<K,V>> entrySet():返回当前映射中包含的映射的Set视图。
  3. K firstKey():返回映射中当前的第一个键(最低还是最高,取决于实现映射的方式(升序还是降序)。
  4. SortedMap<K,V> headMap(K toKey):返回当前映射中其键严格小于toKey的部分的视图。
  5. Set<K> keySet():返回当前映射中包含的键的Set视图
  6. K lastKey():返回映射中当前的最后一个(最高或最低)键
  7. SortedMap<K,V> subMap(K fromKey, K toKey):返回当前映射的部分视图,其有效范围从fromKeytoKey
  8. SortedMap<K,V> tailMap(K fromKey):返回当前映射中键大于或等于fromKey的部分的视图
  9. Collection <V> values():返回当前映射中包含的值的集合视图

有关这些方法的更多详细信息,请查阅官方 Oracle 文档

代码实现

import java.util.*;

public class SortedHashMapExample {
    
    
   public static void main(String args[]) {
    
    
      Map<Double, String> players = new TreeMap<Double, String>();

      // health, name
      players.put(new Double(100.00), "Hill");
      players.put(new Double(120.00), "John");
      players.put(new Double(150.00), "Sabrina");
      players.put(new Double(105.00), "Caitlyn");
      players.put(new Double(110.00), "Rachel");
      players.put(new Double(130.00), "Michael");
      players.put(new Double(140.00), "Mark");

      // get a set of the entries
      Set setOfEntries = players.entrySet();

      // get an iterator
      Iterator iterator = setOfEntries.iterator();

      while(iterator.hasNext()) {
    
    
          // create an entry of the map
         Map.Entry entry = (Map.Entry)iterator.next();
         System.out.println("Key: " + entry.getKey());
         System.out.println("Value: " + entry.getValue());
      }
   }
}

输出

Key: 100.0
Value: Hill
Key: 105.0
Value: Caitlyn
Key: 110.0
Value: Rachel
Key: 120.0
Value: John
Key: 130.0
Value: Michael
Key: 140.0
Value: Mark
Key: 150.0
Value: Sabrina

如您所见,它将自动按升序对它们进行分组。 它的生命值从 100.00 开始,直到 150.00。 我将健康作为关键,并将名称作为值的原因只是为了向您表明它提升了他们。

但是,如果我们希望按降序排列它们怎么办?

使用降序实现

import java.util.*;

public class SortedHashMapExample {
    
    
   public static void main(String args[]) {
    
    
      Map<Double, String> players = new TreeMap<Double, String>(new Comparator<Double>() {
    
    

        @Override
        public int compare(Double x, Double y) {
    
    
          return y.compareTo(x);
        }

      });

      // name, health
      players.put(new Double(100.00), "Hill");
      players.put(new Double(120.00), "John");
      players.put(new Double(150.00), "Sabrina");
      players.put(new Double(105.00), "Caitlyn");
      players.put(new Double(110.00), "Rachel");
      players.put(new Double(130.00), "Michael");
      players.put(new Double(140.00), "Mark");

      // get a set of the entries
      Set setOfEntries = players.entrySet();

      // get an iterator
      Iterator iterator = setOfEntries.iterator();

      while(iterator.hasNext()) {
    
    
          // create an entry of the map
         Map.Entry entry = (Map.Entry)iterator.next();
         System.out.println("Key: " + entry.getKey());
         System.out.println("Value: " + entry.getValue());
      }
   }
}

输出

Key: 150.0
Value: Sabrina
Key: 140.0
Value: Mark
Key: 130.0
Value: Michael
Key: 120.0
Value: John
Key: 110.0
Value: Rachel
Key: 105.0
Value: Caitlyn
Key: 100.0
Value: Hill

走你,再简单不过了吧? 我们所做的只是覆盖比较方法,而不是x => y(升序),我们将其更改为y => x(降序)。

Java ConcurrentMap示例

原文: https://javatutorial.net/java-concurrentmap-example

即使Map 由许多类实现,但其中许多不是线程安全的,或者其中一些效率不高。 这就是为什么在 Java 1.5 中引入ConcurrentMap的原因。 它是线程安全且高效的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

覆盖的默认实现:

  • count
  • replaceAll
  • forEach
  • getOrDefault
  • computerIfAbsent
  • computerIfPresent

ConcurrentMap由表示为表存储桶的节点数组组成,它们在第一次插入后初始化。

ConcurrentMapHashMap之间的效率比较

  • 如果要尽快处理数据,则必须使用所有线程,因此这意味着ConcurrentMap在这里更有效。
  • 如果只需要单线程访问,则HashMap更快。
  • 如果由HashMap实现,则添加方法的速度快 3 倍。
  • 如果由ConcurrentMap实现,则 get 方法会更快。

如果程序需要多个线程访问,则ConcurrentMap是更好的选择。 但是,如果程序仅使用 1 个线程,则HashMap将是更好的选择。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ConcurrentMap

ConcurrentMap中的方法

  1. default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction):尝试计算指定键及其当前映射值的映射。
  2. default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction):如果作为参数给出的键与值不相关(或为null),则尝试计算其值并输入 进入此映射,除非为null
  3. default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction):如果指定键的值存在且非空,则尝试计算新映射,给定键及其当前映射值。
  4. default void forEach(BiConsumer<? super K, ? super V> action):对当前映射中的每个条目执行给定的操作,直到所有条目都已处理。
  5. default V getOrDefault(Object key, V defaultValue):返回指定键所映射到的值或如果映射不包含该键的映射关系,则返回defaultValue(作为第二个参数)。
  6. default V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction):如果键尚未与值关联或与null关联,则它将关联指定的非null值。
  7. V putIfAbsent(K key, V value):如果指定的键尚未与值关联,则将其与给定的值关联。
  8. boolean remove(Object key, Object value):仅当当前映射到给定值时,才删除键的条目。
  9. V replace(K key, V value):仅当当前映射到某个值时才替换键的条目。
  10. boolean replace(K key, V oldValue, V newValue):仅当当前映射到给定值时才替换项的条目。

有关 EnumSet 主要方法的更多信息,请随时访问原始 Oracle 文档

使用上述某些方法的示例程序

import java.util.concurrent.*; 
class ConcurrentHashMapExample {
    
     
    public static void main(String[] args) 
    {
    
     
        ConcurrentHashMap conCurrHashMap = new ConcurrentHashMap(); 
        conCurrHashMap.put(100, "Elephant"); 
        conCurrHashMap.put(101, "Tiger"); 
        conCurrHashMap.put(102, "Lion"); 
        conCurrHashMap.put(103, "Cow"); 

        // since 103 already exists, this won't work
        conCurrHashMap.putIfAbsent(103, "Goat"); 

        conCurrHashMap.remove(103, "Goat"); 
        System.out.println("After removal: " + conCurrHashMap);

        // since 103 was removed, this now works
        conCurrHashMap.putIfAbsent(103, "Leopard"); 
        System.out.println("After put: " + conCurrHashMap);

        // changing Goat to Cheetah
        conCurrHashMap.replace(103, "Leopard", "Cheetah"); 
        System.out.println("Final: " + conCurrHashMap); 
    } 
}

输出

After removal: {
    
    100=Elephant, 101=Tiger, 102=Lion, 103=Cow}
After put: {
    
    100=Elephant, 101=Tiger, 102=Lion, 103=Cow}
Final: {
    
    100=Elephant, 101=Tiger, 102=Lion, 103=Cow}

Java Hashtable示例

原文: https://javatutorial.net/java-hashtable-example

Hashtable实现一个哈希表(顾名思义),并将键映射到值(例如LinkedHashMap)。Hashtable类允许将非null对象用作键或值。就像HashMap一样,Hashtable有两个影响其性能的参数:初始容量和负载因子。 容量是哈希表中存储桶的数量,初始容量是创建哈希表时存在的容量。 负载因子是在容量自身增加之前允许哈希表获得多大容量的度量。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

它是如何工作的?

哈希表使用哈希函数,其目的是计算到插槽(或存储桶)数组中的索引,从中可以找到正确的值。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

哈希表工作流程

好处

  • 平均查找成本与表中存储的元素数量无关。
  • 允许任意插入和删除键值对。
  • 在许多情况下搜索树或任何其他表时效率更高。

Hashtable中的构造方法摘要

  1. Hashtable():构造一个具有默认初始容量(11)(与HashMapLinkedHashMap不同)和负载因子(0.75)的新哈希表。
  2. Hashtable(int initialCapacity):构造一个具有指定容量和默认负载因子(0.75)的新哈希表。
  3. Hashtable(int initialCapacity, float loadFactor):构造一个具有指定容量和指定负载因子的新哈希表。
  4. Hashtable (Map<? extends K, ? extends V> t):使用与给定Map相同的映射构造一个新的哈希表。

Hashtable类中的方法

  1. void clear:清除当前哈希表,该哈希表删除/删除所有键/值对。
  2. Object clone():创建此哈希表的浅表副本。
  3. V contains(Object value):测试当前值是否映射到哈希表中的任何键。
  4. boolean containsKey(Object key):测试哈希表中是否存在指定的键。
  5. boolean containsValue(Object value):测试指定的值是否映射到哈希表中的任何键。
  6. boolean equals(Object o):将指定的Object与该Map比较是否相等。
  7. V get(Object key):返回键所映射到的值;如果此表/映射不包含键的映射关系,则返回null
  8. int hashCode():根据 Map 接口中的定义,返回此表/地图的哈希码值。
  9. boolean isEmpty():测试哈希表是否没有键映射到值。
  10. V put(K key, V value):将指定的值映射到指定的键。
  11. V remove(Object key):从此哈希表中删除指定的键。
  12. V replace(K key, V value):仅当当前映射到某个值时,才替换指定键的条目。
  13. int size():返回此哈希表中的键数。

有关Hashtable类的主要方法的更多信息,请随时访问原始 Oracle 文档

Hashtable中删除所有键并克隆Hashtable

import java.util.*; 
class HashTableExample {
    
     
    public static void main(String[] arg) 
    {
    
     
        Hashtable<Integer, String> hashTable = 
                      new Hashtable<Integer, String>(); 

        Hashtable<Integer, String> hashTableCopy = 
                      new Hashtable<Integer, String>(); 

        hashTable.put(1, "javatutorial"); 
        hashTable.put(2, "dot"); 
        hashTable.put(3, "net"); 

        // create a clone of hashtable 'hashTable'
        hashTableCopy= (Hashtable<Integer, String>)hashTable.clone(); 

        System.out.println("values in clone: " + hashTableCopy); 

        hashTable.clear(); 

        System.out.println("after clearing: " + hashTable); 
    } 
}

输出

values in clone: {
    
    1="javatutorial", 2="dot", 3="net"}
after clearing: {
    
    }

Java 中ArrayListLinkedList之间的区别

原文: https://javatutorial.net/difference-between-arraylist-and-linkedlist-in-java

本文介绍了ArrayListLinkedList之间的区别,在这种情况下,我们应该优先选择一个。

由于继承了相同的接口 – List,因此ArrayListLinkedList共享相同的属性。 但是ArrayListLinkedList有什么区别? 简单地说 – ArrayList对于一次写入多次读取操作很有用,但不利于从前端或中间进行添加/删除。 另一方面,LinkedList更适合插入和删除数据。

性能表现

下表通过对LinkedListArrayList执行不同的操作来显示平均算法复杂度

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

ArrayListLinkedList操作的复杂度

ArrayListLinkedList性能示例

下面的示例演示使用ArrayListLinkedList上的相同数据进行addsetremove操作的性能

package javatutorial.net;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class ArrayListVsLinkedListExample {
    
    

	private static final int ELCOUNT = 50000;

	public static void main(String[] args) {
    
    

		List<String> alist = new ArrayList<String>();
		List<String> llist = new LinkedList<String>();

		// Insertion 

		// ArrayList
		long start = System.currentTimeMillis();
		for (int i = 0; i < ELCOUNT; i++) {
    
    
			alist.add("element #" + i);
		}
		long totalTimeMs = System.currentTimeMillis() - start;
		System.out.println("Adding 50K elements in ArrayList took " + totalTimeMs + " ms");

		// LinkedList
		start = System.currentTimeMillis();
		for (int i = 0; i < ELCOUNT; i++) {
    
    
			llist.add("element #" + i);
		}
		totalTimeMs = System.currentTimeMillis() - start;
		System.out.println("Adding 50K elements in LinkedList took " + totalTimeMs + " ms");

		// Modification /

		// ArrayList
		start = System.currentTimeMillis();
		for (int i = 0; i < ELCOUNT; i++) {
    
    
			alist.set(i, "modified element #" + i);
		}
		totalTimeMs = System.currentTimeMillis() - start;
		System.out.println("Modifying 50K elements in ArrayList took " + totalTimeMs + " ms");

		// LinkedList
		start = System.currentTimeMillis();
		for (int i = 0; i < ELCOUNT; i++) {
    
    
			llist.set(i, "modified element #" + i);
		}
		totalTimeMs = System.currentTimeMillis() - start;
		System.out.println("Modifying 50K elements in LinkedList took " + totalTimeMs + " ms");

		// Removal //

		System.out.println("ArrayList size before removal " + alist.size());
		System.out.println("LinkedList size before removal " + llist.size());

		// ArrayList
		start = System.currentTimeMillis();
		for (int i = 0; i < ELCOUNT; i++) {
    
    
			alist.remove(0);
		}
		totalTimeMs = System.currentTimeMillis() - start;
		System.out.println("Removing 50K elements in ArrayList took " + totalTimeMs + " ms");

		// LinkedList
		start = System.currentTimeMillis();
		for (int i = 0; i < ELCOUNT; i++) {
    
    
			llist.remove(0);
		}
		totalTimeMs = System.currentTimeMillis() - start;
		System.out.println("Removing 50K elements in LinkedList took " + totalTimeMs + " ms");

		System.out.println("ArrayList size after removal " + alist.size());
		System.out.println("LinkedList size after removal " + llist.size());
	}

}

这是执行示例代码的输出。 结果将因不同的计算机配置而异

Adding 50K elements in ArrayList took 10 ms
Adding 50K elements in LinkedList took 7 ms
Modifying 50K elements in ArrayList took 7 ms
Modifying 50K elements in LinkedList took 6315 ms
ArrayList size before removal 50000
LinkedList size before removal 50000
Removing 50K elements in ArrayList took 135 ms
Removing 50K elements in LinkedList took 4 ms
ArrayList size after removal 0
LinkedList size after removal 0

如您在上面的输出中看到的:

  • LinkedList在访问和修改元素方面明显较慢
  • LinkedList添加元素的速度较慢
  • LinkedList从列表开头删除元素的速度要快得多
  • ArrayList插入新元素的速度较慢
  • ArrayList在访问和修改元素方面明显更快
  • ArrayList从列表开头删除元素的速度明显较慢

结论

在所有情况下,算法复杂度和运算性能都不是恒定的。 您必须考虑两个主要因素 - 列表的大小以及我们使用的元素在列表中的放置位置(在开头,中间或结尾)。 唯一不变的规则是:如果要更快地检索元素,请使用ArrayList,如果要更快地处理数据,请使用LinkedList

Java HashMap迭代示例

原文: https://javatutorial.net/java-iterate-hashmap-example

本示例说明如何在 Java 中遍历HashMap

在 Java 中遍历集合或数据集是一项非常常见的任务。 您可以使用它来打印或处理数据。 以下示例显示了有关如何遍历HashMap的三种不同方法。 根据您的 Java 版本,您可以选择其中之一。

使用for遍历HashMap

这是建议的方法。 它使您可以完全控制地图中的键和值。 使用这种方法,您实际上遍历了映射的EntrySet,并获得了循环内每个条目的键和值。

每个版本在 Java 版本 1.5 中均可用

import java.util.HashMap;
import java.util.Map;

public class IterateHashMap {
    
    

	public static void main(String[] args) {
    
    
		Map<String, String> map = new HashMap<String, String>();
		map.put("key1", "value1");
		map.put("key2", "value2");

		for (Map.Entry<String, String> entry : map.entrySet()) {
    
    
		    System.out.println(entry.getKey() + " = " + entry.getValue());
		}
	}
}

在 Java 8 中使用 Lambda 表达式遍历HashMap

Java 8+ 版本中提供了这种方法。

import java.util.HashMap;
import java.util.Map;

public class IterateHashMap {
    
    

	public static void main(String[] args) {
    
    
		Map<String, String> map = new HashMap<String, String>();
		map.put("key1", "value1");
		map.put("key2", "value2");

		map.forEach((key,value) -> System.out.println(key + " = " + value));
	}
}

使用Iterator遍历HashMap

此方法使用java.util.Iterator浏览HashMap。 这是 Java 1.4 和更早版本中的默认方法。 尽管下面的示例使用泛型,所以您需要 Java 1.5+ 才能执行它

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;

public class IterateHashMap {
    
    

	public static void main(String[] args) {
    
    
		Map<String, String> map = new HashMap<String, String>();
		map.put("key1", "value1");
		map.put("key2", "value2");

		Iterator<Entry<String, String>> it = map.entrySet().iterator();
		while (it.hasNext()) {
    
    
			Map.Entry<String, String> pair = (Map.Entry<String, String>) it.next();
			System.out.println(pair.getKey() + " = " + pair.getValue());
		}
	}
}

Java HashMap内联初始化

原文: https://javatutorial.net/java-hashmap-inline-initialization

下面的示例演示如何使用标准方法和内联方法初始化 Java HashMap

为什么我们需要直接初始化HashMap

尽管Map被广泛用于以动态方式收集和处理数据,但通常您需要创建一个带有预定义键和值的小映射,以测试一种简短的算法或您正在研究的概念。 手动初始化 Java HashMap的另一个用例是测试程序或算法如何使用特定值执行。 在编写简短的演示程序时,您很可能更愿意直接初始化映射,而不是从文件或某种流中读取数据并用值填充映射。 您可以想象,与单行HashMap初始化相比,您需要花费更多的精力。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Java 初始化HashMap

初始化HashMap的必要方法

直接的解决方案是声明一个Map,然后只需put()进入Map。 我们将使用此示例作为参考,以与其他更节省类型的技术进行比较。

Map<String,String> mymap = new HashMap<String, String>();
test.put("A","one");
test.put("B","two");

如您在上面的示例中看到的,首先创建一个映射对象,然后在其中放置条目。 这里要提到的另一重要事项是,通过使用此方法,您可以创建动态映射,并且可以编辑,删除或创建新条目。

使用匿名子类初始化HashMap

这将创建HashMap的匿名子类,其实例初始化程序将这些值放入。 换句话说,它将创建一个继承自HashMap的新类。 这是创建和初始化动态HashMap的最短方法

Map<Integer, String> mymap = new HashMap<Integer, String>() {
    
    
	{
    
    
		put(1, "one");
		put(2, "two");
	}
};

初始化不可变映射

与动态映射(如上例所示)相比,不可变映射是不可编辑的。 这意味着您不能在其中添加新条目,删除条目或更新它们。 如果您想测试一个简单的算法,它们将对您很好。

在 Java SE 8 和更早版本中初始化不可变映射

以下示例演示了如何在 Java 8 或更早版本的 Java 中初始化不可变Map

Map<Integer,String> mymap = new HashMap<>();
mymap.put(1,"one");
mymap.put(2,"two");
mymap.put(3,"three");
Map<Integer,String> immutableMap = Collections.unmodifiableMap(mymap);

如您所见,您以老式方式创建和初始化Map。 使映射不可变的原因是对Collections.unmodifiableMap(Map map)的调用。 好吧,它看起来不太像内联初始化。 不用担心,下一个示例可以。

以 Guava 方式内联初始化HashMap

Guava 是第一个引入真正的单行Map初始化的人。 参见下面的例子

Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);

用 Java 9 方式内联初始化HashMap

最后,在 Java 9 中,我们可以使用单行语句来初始化不可变的HashMap,而无需第三方库。

Map<Integer,String> map = Map.of(1, "A", 2, "B", 3, "C");

您可能需要阅读完整的 Java 9 不可变映射示例,以了解更多详细信息。

Java 中HashMapTreeMap之间的区别

原文: https://javatutorial.net/difference-between-hashmap-and-treemap-in-java

在本文中,我将解释 java HashMap和 java TreeMap之间的区别

尽管两者都实现了Map接口并提供了大多数相同的功能,但是HashMapTreeMap具有不同的实现。 最重要的区别是通过条目进行迭代的顺序

查看下表,直观了解HashMapTreeMap之间的区别

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Java 中HashMapTreeMap之间的区别

HashMapTreeMap之间的主要区别

TreeMapSortedMap的示例,由红黑树实现,这意味着对键的顺序进行了排序。 遍历键时,您可以依靠它们会井然有序的事实。 键的顺序由元素的compareTo()方法或外部提供的比较器确定。

HashMap则不做任何保证。 它由哈希表实现。 因此,当迭代HashMap的键时,您不能确定它们将以什么顺序排列。

看下面的例子:

package javatutorial.net;

import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class HashMapVsTreeMapExample {
    
    

	public static void main(String[] args) {
    
    
		Map<Integer, String> hMap = new HashMap<Integer, String>();
		hMap.put(5, "A");
		hMap.put(11, "C");
		hMap.put(4, "Z");
		hMap.put(77, "Y");
		hMap.put(9, "P");
		hMap.put(66, "Q");
		hMap.put(0, "R");

		Map<Integer, String> tMap = new TreeMap<Integer, String>();
		tMap.put(5, "A");
		tMap.put(11, "C");
		tMap.put(4, "Z");
		tMap.put(77, "Y");
		tMap.put(9, "P");
		tMap.put(66, "Q");
		tMap.put(0, "R");

		System.out.println("HashMap iteration order =======");
		for (Map.Entry<Integer, String> entry : hMap.entrySet()) {
    
    
			System.out.println(entry.getKey() + " = " + entry.getValue());
		}

		System.out.println("\nTreeMap iteration order =======");
		for (Map.Entry<Integer, String> entry : tMap.entrySet()) {
    
    
			System.out.println(entry.getKey() + " = " + entry.getValue());
		}
	}
}

现在看一下该程序的输出:

HashMap iteration order =======
0 = R
66 = Q
4 = Z
5 = A
9 = P
11 = C
77 = Y

TreeMap iteration order =======
0 = R
4 = Z
5 = A
9 = P
11 = C
66 = Q
77 = Y

如您所见,在HashMap上进行迭代时,我们以“随机”顺序获得条目。 另一方面,TreeMap 迭代以其自然顺序返回条目。

实现复杂度差异

由于HashMap实现的复杂度为O(1),因此通常可以认为HashMap效率更高,因此无论您何时在乎键的顺序,都可以使用它。 另一方面,TreeMap中获取,放置和删除操作的复杂度为 O(log n)

允许的键和值的差异

另一个重要的区别是,HashMap允许使用null键和值,而TreeMap仅允许将null用作其值。

同步(无差异)

请注意,两个实现都不同步,这意味着在这些映射上进行操作不是线程安全的。 如果需要线程安全的Map,则可能要从java.util.concurrent包中选择ConcurrentHashMap类。 这是Map的线程安全实现,比Collections.synchronizedMap(Map<K,V> m)提供更好的并发性

Java 图示例

原文: https://javatutorial.net/graphs-java-example

图通常由顶点和弧线组成。 有时,它们也称为节点(而不是顶点)和边(而不是弧)。 为了本教程的缘故,我将使用节点和边作为参考。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图通常看起来像这样:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图可视化

在许多情况下,节点和边被分配了值。 一个非常有用的图的著名示例是,当节点代表城市并且边沿代表这两个节点(或与此有关的城市)之间的距离时。 这样的例子可以在下面看到:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

从上图判断,很容易理解它代表什么,也很容易阅读。 芝加哥到纽约的距离是 791.5 英里,纽约和华盛顿特区的距离是 227.1 英里。

这只是一个简单的示例,说明如何使用图很有用,但是还有更多示例。

图的其他有用示例可能是表示家谱,facebook 联系人,甚至是旅行路线。

无向图

当图无向时,这意味着可以在两个方向上遍历边。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

无向图

有向图

定向图时,这意味着只能沿其“指向”的方向遍历这些边。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

有向图

Java 中的图实现

Node.java

import java.util.*; 

public class Node {
    
    
    private int id;
    private List<Edge> neighbours = new ArrayList<Edge>();

    public int getNodeId() {
    
    
        return this.id;
    }

    public void addNeighbour(Edge e) {
    
    
        if(this.neighbours.contains(e)) {
    
    
            System.out.println("This edge has already been used for this node.");
        } else {
    
    
            System.out.println("Successfully added " + e);
            this.neighbours.add(e);
        }
    }

    public void getNeighbours() {
    
    
        System.out.println("List of all edges that node " + this.id +" has: ");
        System.out.println("=================================");
        for (int i = 0; i < this.neighbours.size(); i++ ){
    
    
            System.out.println("ID of Edge: " + neighbours.get(i).getId() + "\nID of the first node: " + neighbours.get(i).getIdOfStartNode() + 
            "\nID of the second node: " + neighbours.get(i).getIdOfEndNode());
            System.out.println();
        }
        System.out.println(neighbours);
    }

    public Node(int id) {
    
    
        this.id = id;
    }
}

Node.java有 3 个方法和 1 个构造函数。

getNodeId()仅返回每个节点的 ID。

addNeighbour(Edge e)通过边创建连接,该边作为参数传递到另一个节点。 这是通过将指定的边添加到Node类的边列表中来完成的。 注意,存在一个if条件,用于检查此节点的当前边中是否已经存在指定的边e

getNeighbours()仅用于显示目的。 查看输出,以查看此方法显示信息的精确程度。

构造函数将id作为参数。

Edge.java

public class Edge {
    
    
    private Node start;
    private Node end;
    private double weight;
    private int id;

    public int getId() {
    
    
        return this.id;
    }

    public Node getStart() {
    
    
        return this.start;
    }

    public int getIdOfStartNode() {
    
    
        return this.start.getNodeId();
    }

    public Node getEnd() {
    
     
        return this.end; 
    }

    public int getIdOfEndNode() {
    
    
        return this.end.getNodeId();
    }

    public double getWeight() {
    
    
        return this.weight;
    }

    public Edge(Node s, Node e, double w, int id) {
    
    
        this.start = s;
        this.end = e;
        this.weight = w;
        this.id = id;
    }
}

Edge.java有 6 个方法和 1 个构造函数。

getId()仅返回当前边的 ID。

getStart()返回边从其开始的Node对象。

getIdOfStartNode()返回边从其开始的Node对象的 ID。

getEnd()返回边“停止”在的Node对象。

getIdOfEndNode()返回边“停止”在的Node对象的 ID。

getWeight()获取当前Node对象的权重。

Edge构造函数采用 4 个参数,并使用它们初始化构造函数。

Graph.java

import java.util.*;

public class Graph {
    
    
    private List<Node> nodes = new ArrayList<Node>();
    private int numberOfNodes = 0;

    public boolean checkForAvailability() {
    
     // will be used in Main.java
        return this.numberOfNodes > 1;
    }

    public void createNode(Node node) {
    
    
        this.nodes.add(node);
        this.numberOfNodes++; // a node has been added
    }

    public int getNumberOfNodes() {
    
    
        return this.numberOfNodes;
    }
}

Graph.java只有 3 个方法,没有构造函数。

checkForAvailability()检查是否有多个节点。 如果节点数不超过 1 个,则无法建立连接,因为节点本身不能具有优势。 它必须与另一个节点建立连接。

createNode(Node node)接受类型为Node的参数,并将该节点添加到节点List中。 添加节点后,当前图会将节点数增加 1。这样,我们就可以在某个时候将checkForAvailability()方法评估为true

getNumberOfNodes()返回节点数。

Main.java

public class Main {
    
    
    public static void main(String args[]) {
    
    
        Graph graph = new Graph();

        Node node1 = new Node(1); // create a new node that contains id of 1
        Node node2 = new Node(2); // create a new node that contains id of 2
        Node node3 = new Node(3); // create a new node that contains id of 3

        graph.createNode(node1); // numberOfNodes should increment by 1
        graph.createNode(node2); // numberOfNodes should increment by 1
        graph.createNode(node3); // numberOfNodes should increment by 1

        Edge e12 = new Edge(node1, node2, 5, 1); // create an edge that connects node1 to node2 and contains weight of 5
        Edge e13 = new Edge(node1, node3, 10, 2); // create an edge that connects node1 to node3 and contains weight of 10

        if (graph.checkForAvailability()) {
    
    
            // two nodes can be connected via edge
            node1.addNeighbour(e12); // connect 1 and 2 (nodes)
            node1.addNeighbour(e13);
            node1.getNeighbours();
        } else {
    
    
            System.out.println("There are less than 2 nodes. Add more to connect.");
        }
    }
}

Main.java只有一个main方法。

main方法中创建一个图。 之后,将创建 3 个Node实例。 然后,使用createNode(Node node)方法将这些Node实例添加到图中。 之后,将创建 2 个Edge实例。 第一个将节点 1 连接到节点 2。第二个将节点 1 连接到节点 3。

此后,存在一个if条件,该条件检查节点数是否大于 1,如果超过,则将Neighbour添加​​到node1。 (e12是连接node1node2的边。)(e13是连接node1node3的边)。

输出

Successfully added Edge@15db9742
Successfully added Edge@6d06d69c

List of all edges that node 1 has:
=================================
ID of Edge: 1
ID of the first node: 1
ID of the second node: 2

ID of Edge: 2
ID of the first node: 1
ID of the second node: 3

[Edge@15db9742, Edge@6d06d69c]

可视化以上输出

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

问题:是上述程序生成的无向还是有向图? 如果它生成未定义的,您可以修改 API 来生成定向的图吗? 如果生成有向图,您是否可以修改 API 以生成无向

答案:上面的图产生一个定向的图,因为顾名思义,弧线“指向”某个位置。 要使其成为无向,您只需删除圆弧的“箭头”,然后将其作为一条简单的线即可。 就像下面的图片代表无向图一样。

Java 深度优先搜索示例

原文: https://javatutorial.net/depth-first-search-example-java

当涉及从 Java 中的给定数据结构访问数据时,搜索和/或遍历同样重要。 图和树是可以使用不同方法搜索和/或遍历的数据结构的示例。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

深度优先搜索(简称 DFS)从一个未访问的节点开始,然后开始选择一个相邻节点,直到没有剩余节点为止。 执行完该“过程”之后,您将回溯到另一个选择节点的选择,如果没有,则只需选择另一个未访问的节点即可。

使用栈实现

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

上图访问的节点的顺序为:5 10 25 30 35 40 15 20

使用栈数据结构实现 DFS

Node.java代表上图中的每个“球”或“圆”。 它有一个 ,它代表每个球的“值”。 它也有一个名为Visited的布尔变量,顾名思义,它表示遍历是否访问了Node。 第三个实例变量Node类具有一个ArrayList,它表示当前节点与的所有相邻节点(或相邻节点)。 (如果您想了解有关ArrayList的更多信息,可以查看本教程。)

就此类中的方法而言,有一个简单的构造函数(该函数接受一个值并创建一个空的ArrayList),Setter 和 Getter 方法以及允许添加相邻Node的方法。

Node.java

import java.util.*;

public class Node{
    
    
    private int val;
    private boolean visited;
    private List<Node> adjecents;

    public Node(int val) {
    
    
        this.val = val;
        this.adjecents = new ArrayList<>();
    }

    public void addAdjecents(Node n) {
    
    
        this.adjecents.add(n);
    }

    public List<Node> getAdjacenets() {
    
    
        return adjecents;
    }

    public int getVal() {
    
    
        return this.val;
    }

    public boolean isVisited() {
    
    
        return this.visited;
    }

    public void setVal(int v) {
    
    
        this.val = v;
    }

    public void setVisited(boolean visited) {
    
    
        this.visited = visited;
    }
}

DFS.java

此类只有一种方法:解决方案。

它使用栈数据结构,并以节点为元素。 它将指定的元素添加到节点,然后将其标记为已访问。 在那之后,有一个while循环,不断检查栈是否为空。 如果不是,则从栈中删除一个元素,获取要删除的元素的邻居。 然后,存在另一个循环,其目的是将每个邻居节点标记为已访问,并将该邻居节点添加到栈中。

import java.util.*;

public class DFS {
    
    
    public void stackSolution(Node node) {
    
    
		Stack<Node> DFS_stack = new Stack<Node>();
		DFS_stack.add(node);
		node.setVisited(true);
		while (!DFS_stack.isEmpty()) {
    
    
			Node nodeRemove = DFS_stack.pop();
			System.out.print(nodeRemove.getVal() + " ");

			List<Node> adjs = nodeRemove.getAdjacenets();
			for (int i = 0; i < adjs.size(); i++) {
    
    
				Node currentNode = adjs.get(i);
				if(currentNode != null && !currentNode.isVisited()) {
    
    
					DFS_stack.add(currentNode);
					currentNode.setVisited(true);
				}
			}
		}
	}
}

Main.java

在此类中,主要方法是创建Node类的 8 个实例并传递一些值。 (请记住,下面的示例使用上图(图像)。我们将不同的节点作为邻居添加到不同的节点。此后,我们从node5开始并遍历它)。

import java.util.*;

public class Main {
    
    
    public static void main(String [] args) {
    
    
        Node node5 = new Node(5);
        Node node10 = new Node(10);
        Node node15 = new Node(15);
        Node node20 = new Node(20);
        Node node25 = new Node(25);
        Node node30 = new Node(30);
        Node node35 = new Node(35);
        Node node40 = new Node(40);

        node5.addAdjecents(node10);
        node10.addAdjecents(node15);
        node15.addAdjecents(node20);
        node10.addAdjecents(node25);
        node25.addAdjecents(node35);
        node35.addAdjecents(node40);
        node25.addAdjecents(node30);

        DFS demo = new DFS();

        System.out.println("DFS traversal of above graph: ");
        demo.stackSolution(node5);
    }
}

输出

DFS traversal of above graph:
5 10 25 30 35 40 15 20

Java 广度优先搜索示例

原文: https://javatutorial.net/breadth-first-search-example-java

当涉及从给定数据结构访问数据时,搜索或遍历非常重要。 在这些数据结构中,例如和树,有多种遍历/搜索元素的方法。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

广度优先搜索是这些方法的一个示例。 BFS 是一种遍历树或图形的算法,它从树的根(树中的最高节点)开始或仅从顶部开始,并在当前深度扫描所有相邻节点,然后再继续移动到节点或元素。 下一个深度级别。

简而言之,BFS 必须完成一层,然后继续进行下一层直到没有剩余的任何层。

BFS 使用与深度优先搜索完全相反的工作流程,反之亦然。

在 BFS 和 DFS 之间的实现方面,一个很大的不同是 BFS 使用队列,而 DFS 使用栈。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

BFS 的工作流程

BFS 的实现

实现 BFS 时要遵循两个简单的规则:

  1. 访问给定层上的每个元素
  2. 移至下一层

一个例子:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在继续进行第 2 层之前,必须先通过第 1 层。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在那之后,这是应该怎么做的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

伪代码

public void breadthFirstSearch(Graph G, Object S)
// G => Graph ; S => Source node
{
    
    
     define a queue named as Queue;
     insert the source node in the Q
     mark s as visited

     perform a while loop that keeps looping until the queue is not empty
        removing the current element from the queue as it will be visited now

        perform a for loop that goes through all neighbours of the current element
            if condition that checks if the current element/node/vertex is not visited
                if it has not been visited, enqueue it and mark it as visited
}

实际代码实现

public void BFS(int s, int l) 
    {
    
     
        // create an array that holds boolean values that will have length 'l'
        boolean visited[] = new boolean[l]; 

        // create a queue
        LinkedList<Integer> q = new LinkedList<Integer>(); 

        // mark the current node as visited and add it to the queue
        visited[s]=true; 
        q.add(s); 

        while (q.size() != 0) 
        {
    
     
            // dequeuing a vertex from queue 
            s = q.poll(); 

            // get all adjacent vertices of the dequeued vertex  if a adjacent has not 
            // been visited, then mark it visited and enqueue it 
            Iterator<Integer> k = adj[s].listIterator(); 
            while (k.hasNext()) 
            {
    
     
                int j = k.next(); 
                if (!visited[j]) 
                {
    
     
                    visited[j] = true; 
                    q.add(j); 
                } 
            } 
        } 
    }

不同的算法时间复杂度

原文: https://javatutorial.net/algorithm-time-complexities

在开始解释不同的时间复杂度之前,让我们首先看一下实际的算法是什么。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

算法的正式定义是“在计算或其他解决问题的操作(尤其是计算机)中要遵循的过程或规则集”。 因此,换句话说,算法是定义的路径,例如计算机使用该算法来完成给定问题的解决方案。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

算法工作流程

尽管听起来很简单且不复杂,但事实恰恰相反。 许多算法要花费大量时间才能完成某件事,而有些则没有。 这就是为什么了解给定算法的复杂度非常重要的原因。 那是因为通过了解算法的复杂度,可以使我们了解算法的“价值”,或者说效率如何。

算法示例:

  • 图片搜寻
  • 语音输入
  • 意见建议
  • 谷歌地图
  • 谷歌新闻
  • 等等

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

具有讽刺意味的算法示例

不同类型的算法复杂度

  • 恒定时间:O(1)

如果时间量不取决于输入大小,则可以说算法大小以恒定时间运行。

一个例子是从数组访问元素。 您只需“调用”数组的索引即可访问数组的元素。

  • 线性时间:O(n)

线性时间是指算法取决于的输入大小。 如果输入大小为 n ,则复杂度也将为 n。

具有这种时间复杂度的算法的一个著名示例是线性搜索。

  • 对数时间:O(log n)

如果执行时间与输入大小的对数成正比,则可以说该算法以对数时间运行。

这种时间复杂度的算法的一个著名示例是二分搜索。

  • 二次时间:O(n^2)

二次时间是指执行时间为输入大小的平方。

例如冒泡排序,选择排序,插入排序。

  • “大 Ω”的定义

大欧米茄,也称为下界,用Ω符号表示。

大 O

如果执行时间与输入大小的对数成正比,则可以说该算法以对数时间运行。 例如,如果 Java 中有一个数组,其中包含 5 个苹果,并且您需要打印每个苹果,则该数组将为O(5)或换句话说,为O(数组长度)O(n)

这种时间复杂度的算法的一个著名示例是二分搜索。

大 Θ

如果T(n)Θ(f(n)),则意味着T(n)增长(精确)与f(n)一样快。n + n仍然是 n。 是不是有点满嘴? 让我们尝试一个更简单的解释。

您可以将大 Θ 视为:

“花费的时间不会超过且不短于”

如何确定给定程序的复杂度

int sumArray(int[] aiNumbers)
{
    
    
   int iSum = 0;
   for (int i=0; i<aiNumbers.length; i++)
      iSum += aiNumbers[i];
   return iSum;
}

该程序的复杂度只是aiNumbers.length。 因此,如果此数组的长度为 4,则复杂度为 4。如果aiNumbers.length为 6,则复杂度为 6。

复杂度是aiNumbers.length的原因是因为它会循环aiNumbers.length次。 因此,复杂度为O(N)

N = in.length;
i = 0;
while (i < N) {
    
     
   for (int i=N-2; i<N; i++) {
    
    
      System.out.println("Do something.");
   }
}

上面程序的复杂度为N * N,即 N 乘以 2 的幂。这是因为for循环每次将运行 N 次,而整个循环将运行 N 次。 因此,N *N。因此,该算法的复杂度是二次(O(n^2)

for (int i = 0; i < n; i++) {
    
    
   // do something
}

for (int i = 0; i < n; i++) {
    
    
   // do something
}

在上面的示例中,算法的时间复杂度为 n 。 这样做的原因是因为有 2 个循环 n 次循环 – n + n。 简而言之,n + n就是 n 。

每种算法的可视化表示

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

视觉表示

图片来源: https://adrianmejia.com/most-popular-algorithms-time-complexity-every-programmer-should-know-free-online-tutorial-course/

在算法和复杂度方面,请尽可能尝试优化算法。 这样做的一个好方法是使用集合等在输入数据中找到共同点。请记住:内存很昂贵,您的时间也很昂贵。

Java 序列化示例

原文: https://javatutorial.net/java-serialization-example

序列化的对象。 这意味着什么? Java 提供了一种功能,该功能将对象表示为字节序列,其中包括对象的数据以及有关对象类型和该对象中存储的数据类型的信息。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

将序列化的对象写入文件后,可以稍后对其进行反序列化,这反过来意味着–表示对象及其数据的类型信息和字节可用于在内存中重新创建对象。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

创建的字节流与平台无关。 这意味着,如果对象在一个平台上序列化,则可以在另一个平台上反序列化。

ObjectInputStreamObjectOutputStrem是高级流,其中包含用于序列化和反序列化对象的方法。

ObjectInputStream提供了一个称为readObject()的方法,该方法可检索对象并将其反序列化。 它返回一个Object值,这意味着它将需要强制转换为适当的数据类型。 它抛出IOException

public final Object readObject()

ObjectOutputStream提供了许多写入方法,但使用最广泛的是

public final void writeObject(Object object)

writeObject()方法序列化对象并将其发送到输出流。 如果序列化出错,则可能会引发IOException

因此,为了可视化这些方法的确切位置,让我们在上面的图片中显示它们。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Java 序列化示例

DemoObject.java

import java.io.*; 

public class DemoObject implements java.io.Serializable {
    
     
    private String name; 
    private int age;

    public DemoObject (String name, int age) {
    
     
        this.name = name; 
        this.age = age;  
    } 

    public String getName() {
    
    
        return this.name;
    }

    public int getAge() {
    
    
        return this.age;
    }
}

Main.java

import java.io.*;  

public class Main {
    
     
    public static void main(String[] args) {
    
        
        DemoObject objectExample = new DemoObject("John", 19); 
        String filename = "file.ser"; 
        DemoObject obj = null; 

        // serialization  
        try {
    
        
            FileOutputStream file = new FileOutputStream(filename); 
            ObjectOutputStream output = new ObjectOutputStream(file); 

            output.writeObject(objectExample);    
            output.close(); 

            file.close(); 

            System.out.println("Serialization of object has been completed"); 
          } 

        catch(IOException err) {
    
     
            System.out.println("IOException occurred"); 
        } 

        // Deserialization 
        try {
    
        
            FileInputStream file = new FileInputStream(filename); 
            ObjectInputStream input = new ObjectInputStream(file); 

            obj = (DemoObject) input.readObject(); // cast to the appropriate type

            input.close(); 
            file.close(); 

            System.out.println("Deserialization of object has been completed"); 
            System.out.println();
            System.out.println("Values of deserialized object are:");
            System.out.println("==================================");
            System.out.println("Name = " + obj.getName()); 
            System.out.println("Age  = " + obj.getAge()); 
            System.out.println("==================================");
        } 
        catch(IOException err)  {
    
     
            System.out.println("IOException is caught"); 
        } 

        catch(ClassNotFoundException err) {
    
     
            System.out.println("ClassNotFoundException is caught"); 
        } 

    } 
}

输出

Serialization of object has been completed
Deserialization of object has been completed

Values of deserialized object are:
==================================
Name = John
Age  = 19
==================================

以上的代码实现的细分

我们有一个DemoObject类,它是一个虚拟类,将对其进行序列化然后反序列化。 它有 2 个实例变量,我们称为名称和年龄。 我们可以将这些变量设置为公开,但是我们希望始终保持安全,因此在上面的示例中将它们声明为私有。

Main.java强制转换writeObjectreadObject并基本上将错误处理并打印到控制台。 在示例中,我们有一个设置为null的对象。 设置为null是因为它充当反序列化对象的“占位符”。 基本上,我们可以将反序列化的对象复制到该空对象中。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注意:如果您的编辑器显示此警告:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

您可以忽略它或将您的 IDE 配置为自动生成 ID。 我总是建议让您的 IDE 为您创建唯一的标识符。

什么时候需要序列化

序列化是一种通用且有效的协议,可在组件之间传输对象。 序列化用于遵循此协议传输对象。

最后一点:请记住,使用一个 Java 版本进行序列化的对象可能无法在另一 Java 版本上运行。 例如:用 Java 6 序列化对象不是一个好主意,用 Java 8 反序列化它们,反之亦然。

Java 反射示例

原文: https://javatutorial.net/java-reflection-example

反射(这是 Java 的功能)允许执行 Java 程序检查自身(或其他代码)并操纵程序的内部属性,例如获取成员的名称并对其执行某些操作,例如删除或显示它们。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

默认情况下,Java 中的每个对象都有getClass(),它基本上确定当前对象的类(即使在编译时未知)。

反射使您可以编写在编译时不必“识别”所有内容的程序,而可以使程序动态化,因为它们可以在运行时链接在一起。

Java 反射的简短示例

import java.lang.reflect.*;

public class Main {
    
    

    public void print() {
    
    
        System.out.println("print");
    }
    public static void main(String args[])
    {
    
    
        try {
    
    
            Class c = Class.forName("java.lang.String");
            Method m[] = c.getDeclaredMethods();

            for (int i = 0; i < m.length; i++)
                System.out.println(m[i].toString());

        } catch (Throwable e) {
    
    
            // manipulate e
            System.out.println();
        }
    }
}

在这里,class.forName为我们提供了指定的类,然后,它调用getDeclaredMethods,其目的是检索在该类中定义的方法的列表。

方法m[]存储我们要在其上调用该方法的类的所有已声明方法,在这种情况下为String

尝试并在这里捕获是必需的,就好像您没有它一样,您会收到此错误:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

输出

public boolean java.lang.String.equals(java.lang.Object)
public java.lang.String java.lang.String.toString()
public int java.lang.String.hashCode()
public int java.lang.String.compareTo(java.lang.String)
public int java.lang.String.compareTo(java.lang.Object)
public int java.lang.String.indexOf(java.lang.String,int)
public int java.lang.String.indexOf(java.lang.String)
public int java.lang.String.indexOf(int,int)
public int java.lang.String.indexOf(int)
static int java.lang.String.indexOf(char[],int,int,char[],int,int,int)
static int java.lang.String.indexOf(char[],int,int,java.lang.String,int)
public static java.lang.String java.lang.String.valueOf(int)
public static java.lang.String java.lang.String.valueOf(long)
public static java.lang.String java.lang.String.valueOf(float)
public static java.lang.String java.lang.String.valueOf(boolean)
public static java.lang.String java.lang.String.valueOf(char[])
public static java.lang.String java.lang.String.valueOf(char[],int,int)
public static java.lang.String java.lang.String.valueOf(java.lang.Object)
public static java.lang.String java.lang.String.valueOf(char)
public static java.lang.String java.lang.String.valueOf(double)
public char java.lang.String.charAt(int)
private static void java.lang.String.checkBounds(byte[],int,int)
public int java.lang.String.codePointAt(int)
public int java.lang.String.codePointBefore(int)
public int java.lang.String.codePointCount(int,int)
public int java.lang.String.compareToIgnoreCase(java.lang.String)
public java.lang.String java.lang.String.concat(java.lang.String)
public boolean java.lang.String.contains(java.lang.CharSequence)
public boolean java.lang.String.contentEquals(java.lang.CharSequence)
public boolean java.lang.String.contentEquals(java.lang.StringBuffer)
public static java.lang.String java.lang.String.copyValueOf(char[])
public static java.lang.String java.lang.String.copyValueOf(char[],int,int)
public boolean java.lang.String.endsWith(java.lang.String)
public boolean java.lang.String.equalsIgnoreCase(java.lang.String)
public static java.lang.String java.lang.String.format(java.util.Locale,java.lang.String,java.lang.Object[])
public static java.lang.String java.lang.String.format(java.lang.String,java.lang.Object[])
public void java.lang.String.getBytes(int,int,byte[],int)
public byte[] java.lang.String.getBytes(java.nio.charset.Charset)
public byte[] java.lang.String.getBytes(java.lang.String) throws java.io.UnsupportedEncodingException
public byte[] java.lang.String.getBytes()
public void java.lang.String.getChars(int,int,char[],int)
void java.lang.String.getChars(char[],int)
private int java.lang.String.indexOfSupplementary(int,int)
public native java.lang.String java.lang.String.intern()
public boolean java.lang.String.isEmpty()
public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.CharSequence[])
public static java.lang.String java.lang.String.join(java.lang.CharSequence,java.lang.Iterable)
public int java.lang.String.lastIndexOf(int)
public int java.lang.String.lastIndexOf(java.lang.String)
static int java.lang.String.lastIndexOf(char[],int,int,java.lang.String,int)
public int java.lang.String.lastIndexOf(java.lang.String,int)
public int java.lang.String.lastIndexOf(int,int)
static int java.lang.String.lastIndexOf(char[],int,int,char[],int,int,int)
private int java.lang.String.lastIndexOfSupplementary(int,int)
public int java.lang.String.length()
public boolean java.lang.String.matches(java.lang.String)
private boolean java.lang.String.nonSyncContentEquals(java.lang.AbstractStringBuilder)
public int java.lang.String.offsetByCodePoints(int,int)
public boolean java.lang.String.regionMatches(int,java.lang.String,int,int)
public boolean java.lang.String.regionMatches(boolean,int,java.lang.String,int,int)
public java.lang.String java.lang.String.replace(char,char)
public java.lang.String java.lang.String.replace(java.lang.CharSequence,java.lang.CharSequence)
public java.lang.String java.lang.String.replaceAll(java.lang.String,java.lang.String)
public java.lang.String java.lang.String.replaceFirst(java.lang.String,java.lang.String)
public java.lang.String[] java.lang.String.split(java.lang.String)
public java.lang.String[] java.lang.String.split(java.lang.String,int)
public boolean java.lang.String.startsWith(java.lang.String,int)
public boolean java.lang.String.startsWith(java.lang.String)
public java.lang.CharSequence java.lang.String.subSequence(int,int)
public java.lang.String java.lang.String.substring(int)
public java.lang.String java.lang.String.substring(int,int)
public char[] java.lang.String.toCharArray()
public java.lang.String java.lang.String.toLowerCase(java.util.Locale)
public java.lang.String java.lang.String.toLowerCase()
public java.lang.String java.lang.String.toUpperCase()
public java.lang.String java.lang.String.toUpperCase(java.util.Locale)
public java.lang.String java.lang.String.trim()

从输出中可以看到,String有很多方法。

设置反射

  1. 首先,必须获得一个类对象。 获得它的最常见方法是

    Class class = Class.forName("java.lang.'class name, for example String'");
    
  2. 下一步是调用方法(例如getDeclaredMethods()getFields()等)

  3. 下一步是使用反射 API 来操纵/更改信息

另一个示例,遵循上述的步骤

import java.lang.reflect.*;

class Demo 
{
    
    
    private double exampleVariable; 

    public Demo()  {
    
      
        exampleVariable = 2.3; 
    } 

    public void method1()  {
    
     
        System.out.println("The instance variable: " + exampleVariable); 
    } 

    public void method2(int n)  {
    
     
        System.out.println("The number passed: " + n); 
    } 

    private void method3() {
    
     // private method
        System.out.println("Private method has been called"); 
    } 
} 

class Main 
{
    
     
    public static void main(String args[]) throws Exception 
    {
    
     
        Demo classToBeTested = new Demo(); 

        // creating class object from the class using getClass()
        Class classObject = classToBeTested.getClass(); 
        System.out.println("Name of class: " + classObject.getName()); 

        // getting the constructor using getConstructor()
        Constructor constructor = classObject.getConstructor(); 
        System.out.println("Name of constructor: " + constructor.getName()); 

        // stores all the methods the class has
        Method[] methods = classObject.getMethods(); 

        // printing all method names 
        for (Method method : methods) 
            System.out.println(method.getName()); 
    } 
}

输出

Name of class: Demo
Name of constructor: Demo
method1
method2
wait
wait
wait
equals
toString
hashCode
getClass
notify
notifyAll

Java 中的弱引用

原文: https://javatutorial.net/weak-references-in-java

本文讨论 Java 中弱引用的概念。

在开始之前,让我们先了解一些基础知识。

Java 中有 4 种引用类型:

  1. 强引用
  2. 弱引用
  3. 软引用
  4. 幻影引用

在这里,我们将讨论弱引用。

弱引用与 Java 中的垃圾收集有关。 垃圾收集只是从内存中自动取消分配未引用对象的方法。

弱引用是指不足以使对象保留在内存中的引用。 因此,弱引用可以让垃圾收集器确定对象的可及性以及所讨论的对象是否应保留在内存中。

弱引用需要显式声明,因为默认情况下,Java 将引用标记为强引用。

什么是弱可达性?

这意味着一个对象既没有强引用也没有软引用指向它,并且只能通过遍历弱引用来访问。

因此,如果该对象被弱引用,则垃圾收集器会将其从内存中删除,这将清除更多空间并实现更好的内存管理。

垃圾收集器删除了弱引用之后,将该引用放入引用队列中,并最终确定以前弱可访问的对象。

弱引用在哪里使用?

  • 弱引用主要用于规范化映射的实现中。规范化映射是指映射仅包含特定值的一个实例。

  • 弱引用还广泛用于WeakHashMap类中。 这是Map接口的实现,其中每个键值都存储为弱引用。 键值对扩展了WeakReference类。 因此,垃圾收集器删除此键也会导致实体也被删除。

代码示例:

私有静态类TryingOut<K, V>扩展了WeakReference<Object>实现Map.Entry<K, V>

  • 失效监听器问题也使用弱引用。 在这种情况下,可以通过弱引用来处理内存泄漏问题。

实现弱引用:

java.lang.ref.WeakReference类在处理和创建弱引用时使用。

可以使用弱引用的一种实际的实际场景是在建立数据库连接时使用,当使用数据库的应用程序关闭时,垃圾收集器可能会清除该数据库连接。

Java 中的弱引用的编码示例如下所示:

// Illustrating Weak references in Java 
import java.lang.ref.WeakReference; 
class WeakestRef 
{
    
     
    //coding starts from here
    public void something() 
    {
    
     
        System.out.println("This is printed out on the screen"); 
    } 
} 

public class TryingOutWeak
{
    
     
    public static void main(String[] args) 
    {
    
     
        // Strong Reference 
        WeakestRef obj1 = new WeakestRef ();    
        obj1.something(); 

        // Creating Weak Reference to WeakestRef -type object to which 'obj1' is also pointing. 
        WeakReference< WeakestRef > weakref = new WeakReference< WeakestRef >(obj1); 

        //Now, WeakestRef -type object to which 'obj1' was pointing earlier is not available for garbage   //collection. But will be only be garbage collected when JVM needs memory. 
        Obj1 = null;  

        // Note: You can also retrieve back the object which has been weakly referenced. It succesfully     //calls the method. 
        Obj1 = weakref.get();  

        Obj1.something(); 
    } 
}

代码的输出:

这被打印在屏幕上

这被打印在屏幕上

Java 8 日期时间 API

原文: https://javatutorial.net/java-8-date-time-api

Java 8 引入了新的日期时间 API,其目的是弥补旧的日期时间 API 的缺点。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

以前的日期时间 api 不是线程安全的,新日期时间 api 的替代品是它没有任何设置方法。 新 API 修复的另一个缺点是设计不佳。 旧的 API 具有较少直接的日期操作方法。 而且旧 API 的另一个缺点是程序员必须编写大量代码来处理时区问题。

新的 API 不仅解决了所有这些问题,而且还引入了java.time包中的 2 个重要类:

  • 本地 – 时区处理没有复杂度
  • 时区 – 处理各种时区的更复杂的日期时间 API

本地时间 API 在不需要时区时应使用

使用主要方法调用的本地日期时间 API 的示例

import java.time.*; 
import java.time.format.DateTimeFormatter; 

public class Main {
    
     

    public static void LocalDateTimeAPI() {
    
     

        LocalDate date = LocalDate.now(); 
        LocalTime time = LocalTime.now(); 
        LocalDateTime now = LocalDateTime.now();
        DateTimeFormatter format = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss");
        String formatedTime = now.format(format);      

        Month month = now.getMonth(); 
        int day = now.getDayOfMonth(); 
        int seconds = now.getSecond(); 

        System.out.println("Current date: " + date); 
        System.out.println("Current time: " + time);          
        System.out.println("Current date and time: " + now);         
        System.out.println("in foramatted manner " + formatedTime); 
        System.out.println("Month: " + month + "\nDay: " + day + "\nSeconds: " + seconds);
    } 

    public static void main(String[] args) {
    
     
        LocalDateTimeAPI(); 
    } 
}

输出

Current date: 2019-07-20
Current time: 14:10:58.492
Current date and time: 2019-07-20T14:10:58.492
in foramatted manner 20-07-2019 14:10:58
Month: JULY
Day: 20
Seconds: 58

时区日期时间 API 示例

import java.time.ZonedDateTime;
import java.time.ZoneId;

public class ZonedTime {
    
    

    public void testZonedDateTime() {
    
    
        ZoneId zone = ZoneId.of("Europe/Paris");
        System.out.println("ZoneId: " + zone);

        ZoneId currentZone = ZoneId.systemDefault();
        System.out.println("CurrentZone: " + currentZone);
    }

   public static void main(String args[]) {
    
    
        ZonedTime zonedTimeExample = new ZonedTime();
        zonedTimeExample.testZonedDateTime();
   }
}

输出

ZoneId: Europe/Paris
CurrentZone: Europe/London

如您所见,时区日期时间 API 可以让您访问特定时区,还可以为您提供时区或系统的默认时区。

计时单位示例

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class ChronoUnits {
    
    

    public void chromoUnits() {
    
    
        LocalDate today = LocalDate.now();
        System.out.println("Current date: " + today);

        LocalDate week = today.plus(1, ChronoUnit.WEEKS);
        System.out.println("1 week from now: " + week);

        LocalDate month = today.plus(1, ChronoUnit.MONTHS);
        System.out.println("1 month from now: " + month);

        LocalDate year = today.plus(1, ChronoUnit.YEARS);
        System.out.println("1 year from now: " + year);

        LocalDate decade = today.plus(1, ChronoUnit.DECADES);
        System.out.println("1 decade from now: " + decade);
     }

   public static void main(String args[]) {
    
    
        ChronoUnits ChronoUnitsExample = new ChronoUnits();
        ChronoUnitsExample.chromoUnits();
   }
}

输出

Current date: 2019-07-20
1 week from now: 2019-07-27
1 month from now: 2019-08-20
1 year from now: 2020-07-20
1 decade from now: 2029-07-20

周期和持续时间

周期处理基于日期的时间,而持续时间处理基于时间的时间。

import java.time.temporal.ChronoUnit;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Duration;
import java.time.Period;

public class PeriodDuration {
    
    
    public void testDuration() {
    
    
        LocalTime currentTime = LocalTime.now();
        Duration offtime5 = Duration.ofHours(5);

        LocalTime timeOff5Hours = currentTime.plus(offtime5);
        Duration duration = Duration.between(currentTime, timeOff5Hours);

        System.out.println("Duration: " + duration);
    }

    public void testPeriod() {
    
    
        LocalDate currentDate = LocalDate.now();
        System.out.println("Current date: " + currentDate);

        LocalDate nextMonth = currentDate.plus(1, ChronoUnit.MONTHS);
        System.out.println("Next month: " + nextMonth);

        Period timePeriod = Period.between(nextMonth, currentDate);
        System.out.println("Period: " + timePeriod);
    }

   public static void main(String args[]) {
    
    
        PeriodDuration periodDuration = new PeriodDuration();
        periodDuration.testPeriod();
        periodDuration.testDuration();
   }
}

输出

Current date: 2019-07-20
Next month: 2019-08-20
Period: P-1M
Duration: PT-19H