首页 > Java > java教程 > 正文

java代码怎样实现映射(Map)数据结构 java代码映射表的实用实现教程​

爱谁谁
发布: 2025-08-13 16:16:01
原创
147人浏览过

在java中实现映射数据结构应优先使用java集合框架提供的map接口实现类,最常用的是hashmap,它基于哈希表实现,提供平均o(1)时间复杂度的插入、删除和查找操作,适用于大多数无需排序的场景;当需要键有序时应选择treemap,其基于红黑树实现,支持自然排序或自定义比较器,操作时间复杂度为o(log n);当需保持插入顺序或实现lru缓存时应使用linkedhashmap,它在hashmap基础上通过双向链表维护顺序,性能接近hashmap;在多线程环境下,hashmap、treemap和linkedhashmap均非线程安全,应优先选用concurrenthashmap,其采用cas和细粒度同步机制,提供高并发性能,而collections.synchronizedmap虽可提供线程安全但性能较差,仅适用于低并发场景;使用自定义对象作为键时必须正确重写hashcode()和equals()方法,并推荐使用不可变对象以避免哈希值变化导致的查找失败,合理设置hashmap的初始容量和加载因子可有效避免频繁扩容,提升性能,综上所述,根据具体需求选择合适的map实现类并理解其内部机制是高效使用映射的关键。

java代码怎样实现映射(Map)数据结构 java代码映射表的实用实现教程​

在Java中实现映射(Map)数据结构,我们通常不是从零开始“实现”一个Map,而是利用Java集合框架中已经提供的

Map
登录后复制
接口及其多种实现类。这些实现类,比如
HashMap
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
TreeMap
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
LinkedHashMap
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
,各自在性能、顺序以及线程安全性方面有着不同的特性,能够满足绝大多数的应用场景。理解它们的工作原理和适用场景,才是真正掌握“映射”的关键。

解决方案

要使用Java的映射表,最常见也是最基础的就是

HashMap
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
。它提供O(1)的平均时间复杂度进行插入、删除和查找操作,性能非常出色。

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

public class MapPracticalExample {

    public static void main(String[] args) {
        // 声明并初始化一个HashMap,键是String类型,值是Integer类型
        Map<String, Integer> studentScores = new HashMap<>();

        // 添加键值对
        studentScores.put("张三", 95);
        studentScores.put("李四", 88);
        studentScores.put("王五", 92);
        studentScores.put("张三", 98); // 如果键已存在,新值会覆盖旧值

        System.out.println("学生分数表: " + studentScores);

        // 根据键获取值
        int zhangsanScore = studentScores.get("张三");
        System.out.println("张三的分数: " + zhangsanScore);

        // 检查Map是否包含某个键
        boolean containsLisi = studentScores.containsKey("李四");
        System.out.println("是否包含李四: " + containsLisi);

        // 检查Map是否包含某个值
        boolean containsScore90 = studentScores.containsValue(90);
        System.out.println("是否包含分数90: " + containsScore90);

        // 移除键值对
        studentScores.remove("王五");
        System.out.println("移除王五后: " + studentScores);

        // 遍历Map的几种方式
        System.out.println("\n遍历学生分数表:");
        // 方式一:遍历entrySet(推荐,效率高)
        for (Map.Entry<String, Integer> entry : studentScores.entrySet()) {
            System.out.println("姓名: " + entry.getKey() + ", 分数: " + entry.getValue());
        }

        // 方式二:遍历keySet,再通过键获取值
        System.out.println("\n遍历学生姓名:");
        for (String name : studentScores.keySet()) {
            System.out.println("姓名: " + name + ", 分数: " + studentScores.get(name));
        }

        // 方式三:遍历values
        System.out.println("\n遍历学生分数(仅值):");
        for (Integer score : studentScores.values()) {
            System.out.println("分数: " + score);
        }

        // Java 8 引入的forEach方法
        System.out.println("\n使用forEach遍历:");
        studentScores.forEach((name, score) -> System.out.println("姓名: " + name + ", 分数: " + score));
    }
}
登录后复制

深入理解 HashMap:内部机制与性能优化关键点

HashMap
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
之所以能提供如此高效的性能,关键在于它的散列(hashing)机制。简单来说,
HashMap
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
内部维护了一个数组,每个数组元素被称为一个“桶”(bucket)。当你往
HashMap
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
里放一个键值对时,它会首先计算键的
hashCode()
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
,然后根据这个哈希值来决定这个键值对应该放在数组的哪个桶里。如果不同的键计算出相同的哈希值(哈希冲突),或者不同的键映射到同一个桶(即使哈希值不同,但经过某种映射算法后落到同一位置),这些键值对就会以链表的形式存储在这个桶里。当链表过长时(JDK 8 之后,链表长度超过一定阈值,会转换为红黑树,以保证最坏情况下的性能),查找效率会下降。

立即学习Java免费学习笔记(深入)”;

从我个人的经验来看,

HashMap
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
的性能优化,主要围绕两个参数展开:初始容量(initial capacity)加载因子(load factor)

  • 初始容量:当你预估Map中将要存储的元素数量时,最好在创建
    HashMap
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    时就指定一个合适的初始容量。比如,
    new HashMap<>(16)
    登录后复制
    。如果初始容量太小,
    HashMap
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    在元素数量达到一定程度后会进行扩容(resize),这个过程涉及到重新计算所有元素的哈希值并转移到新的更大的数组中,这会带来显著的性能开销。
  • 加载因子:默认是0.75。当
    HashMap
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    中的元素数量达到
    容量 * 加载因子
    登录后复制
    时,
    HashMap
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    就会扩容。较高的加载因子可以减少内存占用,但会增加哈希冲突的概率,从而导致链表(或红黑树)变长,降低查找效率。较低的加载因子则相反,会增加内存占用,但能减少冲突,提高查找效率。多数情况下,默认的0.75是个不错的折衷点,但如果你的应用对性能要求极高,或者哈希冲突非常频繁,可以考虑调整。

另外,使用

HashMap
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
时,键(key)的
hashCode()
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
equals()
登录后复制
登录后复制
登录后复制
方法的正确实现至关重要
。如果你的自定义对象作为键,但没有正确重写这两个方法,那么
HashMap
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
可能无法正确地存储和检索对象。我见过太多因为这两个方法没写对,导致明明
put
登录后复制
进去了,
get
登录后复制
登录后复制
的时候却拿不到,或者出现重复键的“假象”。记住,
hashCode()
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
相同并不意味着
equals()
登录后复制
登录后复制
登录后复制
也相同,但
equals()
登录后复制
登录后复制
登录后复制
相同的对象,其
hashCode()
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
必须相同。而且,作为键的对象最好是不可变的(immutable),因为如果键在放入
HashMap
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
后被修改了,其
hashCode()
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
可能发生变化,导致Map再也找不到这个键了。

TreeMap 与 LinkedHashMap:何时选用?

虽然

HashMap
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
是日常开发中的主力,但在某些特定场景下,
TreeMap
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
LinkedHashMap
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
会是更好的选择。

  • TreeMap
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    :需要键的有序性时
    TreeMap
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    实现
    SortedMap
    登录后复制
    接口,它内部基于红黑树(Red-Black Tree)实现。这意味着
    TreeMap
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    会根据键的自然顺序(如果键实现了
    Comparable
    登录后复制
    接口)或者通过你提供的
    Comparator
    登录后复制
    来对键进行排序。当你需要遍历Map时,能够按照键的升序(或降序)获取元素,这是
    HashMap
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    无法提供的。 它的缺点是性能不如
    HashMap
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    ,因为涉及到树的平衡操作,插入、删除、查找的平均时间复杂度是O(log n)。 使用场景:需要按键范围查询,或者需要遍历时保持键的自然顺序。比如,存储学生成绩,并希望按学生姓名(拼音)排序输出;或者按时间戳存储事件,并希望按时间顺序处理。

      import java.util.TreeMap;
      import java.util.Comparator;
    
      // ...在某个方法内...
      Map<String, Integer> sortedScores = new TreeMap<>(); // 默认按键的自然顺序(字母序)排序
      sortedScores.put("王五", 92);
      sortedScores.put("张三", 95);
      sortedScores.put("李四", 88);
      System.out.println("TreeMap (按键排序): " + sortedScores); // 输出会是:李四=88, 王五=92, 张三=95
    
      // 也可以提供自定义Comparator
      Map<String, Integer> customSortedScores = new TreeMap<>(Comparator.reverseOrder()); // 按键逆序
      customSortedScores.put("王五", 92);
      customSortedScores.put("张三", 95);
      customSortedScores.put("李四", 88);
      System.out.println("TreeMap (自定义逆序): " + customSortedScores); // 输出会是:张三=95, 王五=92, 李四=88
    登录后复制
  • LinkedHashMap
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    :需要保持插入顺序或实现LRU缓存时
    LinkedHashMap
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    继承自
    HashMap
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    ,它在
    HashMap
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    的基础上增加了一个双向链表,用于维护键值对的插入顺序。这意味着当你遍历
    LinkedHashMap
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    时,元素的顺序就是它们被插入的顺序。它也支持按照访问顺序进行排序(通过构造函数参数控制),这使得它非常适合实现LRU(Least Recently Used)缓存策略。 它的性能与
    HashMap
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    相似,也是O(1)的平均时间复杂度,但由于维护链表的额外开销,通常会比
    HashMap
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    略慢一点,内存占用也稍高。 使用场景

    1. 需要保持元素插入顺序的Map。比如,用户界面上某个配置项的显示顺序,就是按照用户添加的顺序。
    2. 实现LRU缓存。通过重写
      removeEldestEntry
      登录后复制
      登录后复制
      方法,可以轻松地在Map达到最大容量时自动移除最久未使用的元素。
      import java.util.LinkedHashMap;
    
      // ...在某个方法内...
      Map<String, Integer> insertionOrderMap = new LinkedHashMap<>();
      insertionOrderMap.put("香蕉", 1);
      insertionOrderMap.put("苹果", 2);
      insertionOrderMap.put("橘子", 3);
      System.out.println("LinkedHashMap (插入顺序): " + insertionOrderMap); // 输出会是:香蕉=1, 苹果=2, 橘子=3
    
      // LRU缓存示例 (访问顺序)
      // 构造函数参数: initialCapacity, loadFactor, accessOrder (true表示按访问顺序,false表示按插入顺序)
      Map<String, Integer> lruCache = new LinkedHashMap<>(16, 0.75f, true) {
          @Override
          protected boolean removeEldestEntry(Map.Entry<String, Integer> eldest) {
              return size() > 3; // 当Map大小超过3时,移除最老的(或最久未访问的)元素
          }
      };
      lruCache.put("A", 1);
      lruCache.put("B", 2);
      lruCache.put("C", 3);
      System.out.println("LRU Cache (初始): " + lruCache); // A, B, C
    
      lruCache.get("A"); // 访问A,A会移动到链表末尾(最新访问)
      lruCache.put("D", 4); // 插入D,C会被移除(最久未访问)
      System.out.println("LRU Cache (访问A后插入D): " + lruCache); // B, C, A, D (如果按访问顺序) 或 B, A, D (如果C被移除了)
                                                                   // 实际上是 B, C, A,然后D进来,B被移除,变成 C, A, D
                                                                   // 实际输出会是:C=3, A=1, D=4
    登录后复制

    这里要稍微纠正一下,

    LinkedHashMap
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    accessOrder
    登录后复制
    true
    登录后复制
    时,
    get
    登录后复制
    登录后复制
    操作确实会将元素移到链表末尾,但
    removeEldestEntry
    登录后复制
    登录后复制
    移除的是链表头部的元素(即最老的或最久未访问的)。所以上面LRU的例子中,
    C
    登录后复制
    被移除后,会是
    A
    登录后复制
    D
    登录后复制

线程安全与并发场景下的 Map 选择

默认的

HashMap
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
TreeMap
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
LinkedHashMap
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
登录后复制
都不是线程安全的。这意味着在多线程环境下,如果多个线程同时对同一个Map进行读写操作,可能会导致数据不一致、死锁甚至
ConcurrentModificationException
登录后复制
等问题。

  • Collections.synchronizedMap()
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    :简单粗暴的同步 Java提供了一个工具方法
    Collections.synchronizedMap()
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    ,它可以将任何非线程安全的Map包装成一个线程安全的Map。它的原理很简单,就是对Map的所有操作都加上同步锁。

      import java.util.Collections;
      import java.util.HashMap;
    
      Map<String, Integer> syncMap = Collections.synchronizedMap(new HashMap<>());
      // 现在syncMap的所有操作都是线程安全的了
      syncMap.put("key", 1);
    登录后复制

    缺点:这种方式的同步粒度太粗,每次操作都会锁住整个Map。在高并发场景下,这会导致严重的性能瓶颈,因为所有线程都必须等待锁释放,串行化执行。

  • ConcurrentHashMap
    登录后复制
    登录后复制
    登录后复制
    :高并发场景下的首选
    ConcurrentHashMap
    登录后复制
    登录后复制
    登录后复制
    是Java并发包(
    java.util.concurrent
    登录后复制
    )中专门为高并发场景设计的Map实现。它提供了非常高效的并发访问能力,通常比
    Collections.synchronizedMap()
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    性能好很多。 它的内部实现非常精妙,在JDK 8之前,它使用分段锁(Segment Locking)的机制,将Map分成多个段,每个段独立加锁,这样不同线程可以同时访问不同的段,大大提高了并发度。JDK 8之后,它放弃了分段锁,转而采用CAS(Compare-And-Swap)操作和
    synchronized
    登录后复制
    关键字结合的方式,对每个桶进行细粒度锁定,进一步优化了性能,尤其是在写操作时。 优点:高并发性能好,读操作通常不需要加锁,写操作通过CAS和细粒度锁保证线程安全。 使用场景:几乎所有需要Map的并发读写操作的场景,比如缓存、共享配置、统计计数等。

      import java.util.concurrent.ConcurrentHashMap;
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      import java.util.concurrent.TimeUnit;
    
      public class ConcurrentMapExample {
          public static void main(String[] args) throws InterruptedException {
              ConcurrentHashMap<String, Integer> concurrentMap = new ConcurrentHashMap<>();
              ExecutorService executor = Executors.newFixedThreadPool(10);
    
              // 多个线程同时写入
              for (int i = 0; i < 100; i++) {
                  final int taskId = i;
                  executor.submit(() -> {
                      String key = "Task-" + taskId % 10; // 模拟一些键冲突
                      concurrentMap.compute(key, (k, v) -> (v == null) ? 1 : v + 1);
                      // compute 方法是原子性的,可以安全地进行读改写操作
                  });
              }
    
              executor.shutdown();
              executor.awaitTermination(1, TimeUnit.MINUTES);
    
              System.out.println("ConcurrentHashMap 最终结果: " + concurrentMap);
              // 验证结果,通常会是 Task-X=10
              System.out.println("Task-0 计数: " + concurrentMap.get("Task-0"));
          }
      }
    登录后复制

    在选择并发Map时,我个人的习惯是,如果只是偶尔有并发访问,或者并发量很低,

    Collections.synchronizedMap()
    登录后复制
    登录后复制
    登录后复制
    登录后复制
    可能也够用,胜在简单。但只要涉及到一定程度的并发,或者对性能有要求,我会毫不犹豫地选择
    ConcurrentHashMap
    登录后复制
    登录后复制
    登录后复制
    。它的设计更健壮,也更能适应现代多核处理器的并发模型。

以上就是java代码怎样实现映射(Map)数据结构 java代码映射表的实用实现教程​的详细内容,更多请关注php中文网其它相关文章!

java速学教程(入门到精通)
java速学教程(入门到精通)

java怎么学习?java怎么入门?java在哪学?java怎么学才快?不用担心,这里为大家提供了java速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号