Python工程师Java之路(n)手写中文分词

LangChain4j实战:Java工程师如何用Spring Boot构建生产级AI应用 大模型应用开发正从‘调用API’迈向‘工程化编排’,其核心是将LLM能力深度融入现有技术栈。LangChain4j作为Java生态首个面向生产环境设计的LLM框架,以Spring原生集成、可观测性支持、RAG开箱即用和工具链异步容错为关键能力,解决了事务一致性、上下文管理、语义检索与故障隔离等企业级痛点。它不替代Java工程师的领域经验,而是将其熟悉的Bean生命周期、AOP、线程池与监控体系延伸至AI能力层。尤其适合政务、金融等强合规场景,支撑合同审查、政策问答等需法律依据溯源、低延迟、高可靠的服务落地。 阅读详情

手写中文分词极简代码

  • 徒手编写Java中文分词【贝叶斯网络+动态规划】
  • 点击此处可查看中文分词算法原理
  • 用法:传入自定义词典(格式HashMap<String, Integer>)创建对象,然后cut即可
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

public class Tokenizer {
    private static final double minDouble = -9e99;
    private static final String reEn = "[a-zA-Z]+";
    private static final String reNum = "[0-9]+%?|[0-9]+[.][0-9]+%?";
    private HashMap<String, Integer> w2f; // word2frequency
    private int maxLen = 1;
    private double logTotal;

    class Pair {
        double _1;
        int _2;

        Pair(double a, int b) {
            _1 = a;
            _2 = b;
        }

        @Override
        public String toString() {
            return "(" + _1 + "," + _2 + ")";
        }
    }

    public Tokenizer(HashMap<String, Integer> w2f) {
        this.w2f = w2f;
        int total = 0;
        for (Map.Entry<String, Integer> kv : w2f.entrySet()) {
            int len = kv.getKey().length();
            if (len > maxLen) {
                maxLen = len;
            }
            total += kv.getValue();
        }
        logTotal = Math.log10(total);
    }

    public HashMap<Integer, Pair> calculate(String clause) {
        // 句子长度
        int len = clause.length();
        // 有向无环图
        HashMap<Integer, ArrayList<Integer>> DAG = new HashMap<>();
        // 词图扫描
        for (int head = 0; head < len; head++) {
            int tail = Math.min(len, head + maxLen);
            DAG.put(head, new ArrayList<>());
            DAG.get(head).add(head);
            for (int mid = head + 2; mid < tail + 1; mid++) {
                String word = clause.substring(head, mid);
                if (w2f.containsKey(word)) {
                    DAG.get(head).add(mid - 1); // 词库匹配
                } else if (word.matches(reEn)) {
                    DAG.get(head).add(mid - 1); // 英文匹配
                } else if (word.matches(reNum)) {
                    DAG.get(head).add(mid - 1); // 数字匹配
                }
            }
        }
        // 最短路径
        HashMap<Integer, Pair> route = new HashMap<>();
        route.put(len, new Pair(0.0, 0));
        // 动态规划
        for (int i = len - 1; i > -1; i--) {
            Pair maxStatus = new Pair(minDouble, 0);
            for (Integer x : DAG.get(i)) {
                double logFreq = Math.log10(w2f.getOrDefault(clause.substring(i, x + 1), 1));
                double status = logFreq - logTotal + route.get(x + 1)._1;
                if (status > maxStatus._1) {
                    maxStatus._1 = status;
                    maxStatus._2 = x;
                }
            }
            route.put(i, maxStatus);
        }
        return route;
    }

    public ArrayList<String> cut(String clause) {
        // 计算最短路径
        HashMap<Integer, Pair> route = calculate(clause);
        // 句子长度
        int len = clause.length();
        // 分词列表
        ArrayList<String> words = new ArrayList<>();
        // 根据最短路径取词
        int x = 0;
        while (x < len) {
            int y = route.get(x)._2 + 1;
            String l_word = clause.substring(x, y);
            words.add(l_word);
            x = y;
        }
        return words;
    }
}

测试:

HashMap<String, Integer> w2f = new HashMap<>();
w2f.put("空调", 2);
w2f.put("调和", 2);
w2f.put("和风", 2);
w2f.put("风扇", 2);
w2f.put("和", 2);
Tokenizer tk = new Tokenizer(w2f);
System.out.println(tk.cut("空调和风扇99元"));
// 结果打印:[空调, 和, 风扇, 99, 元]

HMM分词(不带词典)

import java.util.ArrayList;
import java.util.HashMap;

public class HmmTokenizer {
    private static double minDouble = -9e90;
    private static double minDouble99 = -9e99;
    private static char[] states = new char[]{'B', 'M', 'E', 'S'};
    private static HashMap<Character, char[]> prevStatus = new HashMap<>();
    private static HashMap<Character, Double> startP = new HashMap<>();
    private static HashMap<Character, HashMap<Character, Double>> transP = new HashMap<>();
    static HashMap<Character, HashMap<Character, Double>> emitP = new HashMap<>();

    static {
        // 前状态
        prevStatus.put('B', new char[]{'E', 'S'});
        prevStatus.put('M', new char[]{'M', 'B'});
        prevStatus.put('S', new char[]{'S', 'E'});
        prevStatus.put('E', new char[]{'B', 'M'});
        // 初始概率
        startP.put('B', -0.26268660809250016);
        startP.put('E', minDouble);
        startP.put('M', minDouble);
        startP.put('S', -1.4652633398537678);
        // 转移概率
        transP.put('B', new HashMap<>());
        transP.put('E', new HashMap<>());
        transP.put('M', new HashMap<>());
        transP.put('S', new HashMap<>());
        transP.get('B').put('E', -0.51082562376599);
        transP.get('B').put('M', -0.916290731874155);
        transP.get('E').put('B', -0.5897149736854513);
        transP.get('E').put('S', -0.8085250474669937);
        transP.get('M').put('E', -0.33344856811948514);
        transP.get('M').put('M', -1.2603623820268226);
        transP.get('S').put('B', -0.7211965654669841);
        transP.get('S').put('S', -0.6658631448798212);
        // 发射概率
        emitP.put('B', new HashMap<>());
        emitP.put('E', new HashMap<>());
        emitP.put('M', new HashMap<>());
        emitP.put('S', new HashMap<>());
    }

    public ArrayList<Character> viterbi(String obs) {
        int len = obs.length();
        ArrayList<HashMap<Character, Double>> V = new ArrayList<>();
        V.add(new HashMap<>());
        HashMap<Character, ArrayList<Character>> path = new HashMap<>();
        for (char y : states) {
            V.get(0).put(y, startP.get(y) + emitP.get(y).getOrDefault(obs.charAt(0), minDouble));
            path.put(y, new ArrayList<>());
            path.get(y).add(y);
        }
        for (int t = 1; t < len; t++) {
            V.add(new HashMap<>());
            HashMap<Character, ArrayList<Character>> newPath = new HashMap<>();
            for (char y : states) {
                double emP = emitP.get(y).getOrDefault(obs.charAt(t), minDouble);
                double maxProb = minDouble99;
                char state = 0;
                for (char y0 : prevStatus.get(y)) {
                    double prob = V.get(t - 1).get(y0) + transP.get(y0).getOrDefault(y, minDouble) + emP;
                    if (prob > maxProb) {
                        maxProb = prob;
                        state = y0;
                    }
                }
                V.get(t).put(y, maxProb);
                newPath.put(y, new ArrayList<>());
                newPath.get(y).addAll(path.get(state));
                newPath.get(y).add(y);
            }
            path = newPath;
        }
        double probE = V.get(len - 1).get('E');
        double probS = V.get(len - 1).get('S');
        char state;
        if (probE > probS) {
            state = 'E';
        } else {
            state = 'S';
        }
        return path.get(state);
    }

    public ArrayList<String> cut_without_dict(String clause) {
        ArrayList<Character> posList = viterbi(clause);
        int begin = 0;
        int nextI = 0;
        ArrayList<String> words = new ArrayList<>();
        int len = clause.length();
        for (int i = 0; i < len; i++) {
            char pos = posList.get(i);
            if (pos == 'B') {
                begin = i;
            } else if (pos == 'E') {
                words.add(clause.substring(begin, i + 1));
                nextI = i + 1;
            } else if (pos == 'S') {
                words.add(clause.substring(i, i + 1));
                nextI = i + 1;
            }
        }
        if (nextI < len) {
            words.add(clause.substring(nextI));
        }
        return words;
    }
}

测试:

HmmTokenizer.emitP.get('B').put('南', -5.5);
HmmTokenizer.emitP.get('M').put('南', -9.9);
HmmTokenizer.emitP.get('E').put('南', -9.9);
HmmTokenizer.emitP.get('S').put('南', -9.9);
HmmTokenizer.emitP.get('B').put('门', -9.9);
HmmTokenizer.emitP.get('M').put('门', -9.9);
HmmTokenizer.emitP.get('E').put('门', -4.4);
HmmTokenizer.emitP.get('S').put('门', -9.9);
HmmTokenizer.emitP.get('B').put('大', -9.9);
HmmTokenizer.emitP.get('M').put('大', -8.8);
HmmTokenizer.emitP.get('E').put('大', -3.3);
HmmTokenizer.emitP.get('S').put('大', -9.9);
HmmTokenizer.emitP.get('B').put('的', -9.9);
HmmTokenizer.emitP.get('M').put('的', -9.9);
HmmTokenizer.emitP.get('E').put('的', -9.9);
HmmTokenizer.emitP.get('S').put('的', -1.1);
HmmTokenizer tk = new HmmTokenizer();
System.out.println(tk.viterbi("南大的南大门"));          // [B, E, S, B, M, E]
System.out.println(tk.cut_without_dict("南大的南大门")); // [南大, 的, 南大门]

代码下载

https://download.youkuaiyun.com/download/Yellow_python/16498500

Python实现LSA文本降维:电商客服语义分析实战 潜在语义分析(LSA)是一种基于矩阵分解的文本降维技术,通过奇异值分解(SVD)挖掘词与文档间的隐含语义关联,实现词汇层面的语义泛化与噪声抑制。其原理不依赖深度学习,而是利用共现统计与线性代数重构低维语义空间,在效果、速度与可解释性之间取得工程平衡。相比TF-IDF的词汇孤立性和BERT类模型的黑箱高成本,LSA具备数学可追溯、参数可控、轻量部署等技术价值,特别适用于10万级中文文本的主题发现、跨文档相似度计算与下游聚类/检索任务。本文聚焦Python生态下的端到端落地,涵盖中文分词适配、TF-IDF加权校 阅读详情

相关推荐

Happy-LLM中文大模型实践教程:从手写Attention到RAG Agent落地

大模型技术正从理论研究加速走向工程落地,而中文场景下的实践门槛尤为突出——Tokenizer适配、LoRA微调显存控制、RAG中文语义检索、Agent指令遵循等关键环节,远非简单翻译英文教程可解。本文聚焦‘可执行的中文大模型工程能力’,深入解析基于PyTorch手写Multi-Head Attention的原理实现与维度调试逻辑,结合PEFT框架下LoRA在中文微调中的秩(r)与缩放因子(alpha)协同优化策略;进一步延伸至bge-m3嵌入+Faiss分层量化索引构建高相关性中文向量库,并通过crewai

angou6476的博客 583

ElasticSearch——手写一个ElasticSearch分词器(附源码)

ElasticSearch——手写一个ElasticSearch分词器(附源码)

scorpio的博客 2743

Gemma 3n端侧多模态实战:Android VLM应用从架构到落地

视觉语言模型(VLM)是实现图像理解与自然语言生成协同的关键技术,其核心原理在于图像编码器与语言模型通过交叉注意力机制对齐多模态表征。在端侧部署时,需突破内存受限、算力薄弱、功耗敏感等硬约束,技术价值体现在离线可用、隐私保护与实时响应三大维度。典型应用场景包括老年健康助手、IoT设备交互、医疗辅具识别等对数据不出设备有强需求的嵌入式AI产品。本文聚焦轻量化VLM——Gemma 3n在Android平台的工程化落地,系统阐述其模型压缩、三级流水线架构、ZSTD内存映射加载、MobileViTv2特征蒸馏及本地

weixin_34115824的博客 487

【中文分词】亲手开发一款中文分词器--前言

所谓分词,就是将一个完整的句子按正确的意思将每一个词语提取出来的操作。分词是自然语言处理的第一个关卡,是让计算机处理人类语言的基础。但是计算机是无法识别人类语言中词汇这一概念的,更何况如何去识别理解词语了。所以如何通过有效的手段来进行分词是一个重要节点性问题。

mychaint 1087

NLP实操手记:从文本切分到合同信息提取的工业级落地

自然语言处理(NLP)是让机器理解人类语言的基础技术,其核心在于将非结构化文本转化为可计算的结构化表示。原理上,NLP并非端到端黑箱,而是由文本预处理、分词标准化、向量化、任务建模与评估迭代构成的可拆解流水线。技术价值体现在平衡效果、效率与可维护性——例如TF-IDF在关键词强相关场景下仍具不可替代性,而tokenization质量直接决定BERT等大模型的输入可靠性。典型应用场景包括采购合同关键信息提取、客服意图识别、电商搜索优化等需快速落地的业务需求。本文聚焦非算法背景从业者,基于Hugging Fac

weixin_30387423的博客 326

2026年华为od机试真题题库

OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)OD统一考试(E卷)

面试高手的博客 19万+

大数据(7z)Scala手写中文分词

徒手写中文分词scala实现 Scala不调包实现中文分词 贝叶斯网络+动态规划 Spark中文分词 中文分词+Spark词频统计 中文分词+Spark文本分类

ArYe 984

200万+中文分词词库实战指南:从数据清洗到NLP系统集成

中文分词是自然语言处理(NLP)的基础任务,其核心原理是将连续的中文字符序列按照语义切分成独立的词语单元。这项技术通过词典匹配、统计模型或深度学习算法实现,对提升搜索引擎、智能客服和文本分析的准确性具有关键价值。在实际工程中,高质量的分词词库是构建健壮NLP系统的基石,尤其当词库规模达到200万条时,其覆盖的通用词汇、专业术语和网络新词能为各类应用场景提供有力支撑。本文聚焦于如何将大规模词库高效集成到Jieba、HanLP等开源工具中,并通过Trie树、AC自动机等数据结构优化性能,同时结合新词发现和领域自

weixin_30321709的博客 388

如何用Python做中文分词?

打算绘制中文词云图?那你得先学会如何做中文文本分词。跟着我们的教程,一步步用Python来动手实践吧。 需求 在《如何用Python做词云》一文中,我们介绍了英文文本的词云制作方法。大家玩儿得可还高兴? 文中提过,选择英文文本作为示例,是因为处理起来最简单。但是很快就有读者尝试用中文文本做词云了...

weixin_34352005的博客 605

Python3爬虫中关于中文分词的详解

在本篇文章里小编给大家整理的是关于Python3爬虫中关于中文分词的详解内容,需要的朋友们可以参考下。

实战解析:利用Wireshark解密QQ通信协议

本文提供了一份利用Wireshark工具分析QQ通信协议的实战指南。文章详细介绍了从环境准备、捕获数据包到解析协议层的完整流程,重点演示了如何识别QQ号码字段、观察加密数据特征以及区分不同通信类型(如文本、文件传输)。通过此实践,读者可以深入理解网络分层模型和现代应用加密通信机制,提升网络协议分析与安全诊断能力。

weixin_29210727的博客 411

用python调度hanlp

下载jpype1:https://www.lfd.uci.edu/~gohlke/pythonlibs/#jpype 安装对应版本:pip install xxxxxx(对应文件) 注意: pip install jpype1报错 下载安装hanlp: 官方文档:github:https://github.com/hankcs/HanLP 下载地址:https://github.com/hankc...

leofionn的博客 2213

AI编程助手Cursor如何革新经济学研究:从计量模型到可视化全流程实战

在数据科学和人工智能技术蓬勃发展的今天,如何高效地将前沿AI工具融入专业领域的研究工作流,成为提升科研效率的关键。其核心原理在于通过自然语言交互,将研究者的专业问题转化为机器可执行的指令,从而实现人机协作的范式转移。这一技术的核心价值在于大幅降低非计算机背景研究者的编程门槛,将精力从繁琐的代码实现中解放出来,更聚焦于学科本身的思想与逻辑。尤其在经济学、金融学等高度依赖数据分析和计量建模的领域,AI编程助手能够快速处理数据清洗、模型估计、可视化及结果解释等任务。具体到应用场景,研究者可以利用结构化提示词工程,

weixin_42531579的博客 300

在Python中调用Java扩展包HanLP测试记录

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34240657的博客 144

别再手动写用例了!用AI Agent自动生成测试用例的保姆级实战(附Python代码)

本文详细介绍了如何利用AI Agent技术自动生成测试用例,大幅提升测试工程师的工作效率。通过Python代码实战演示,从环境搭建到核心Agent构建(需求解析、场景挖掘、用例编排),再到与企业工具链(TestRail、Jira)的深度集成,帮助团队实现测试用例的智能化生成与管理。

weixin_30897233的博客 649

Minio HTTPS证书配置实战:从申请到部署全流程

本文详细介绍了Minio HTTPS证书配置的全流程,从申请到部署,确保对象存储服务的安全性。内容包括证书类型选择、文件处理、服务配置调整及常见问题解决,帮助用户在生产环境中快速实现HTTPS加密传输,提升数据安全。

weixin_42638178的博客 433

TensorFlow手写Seq2Seq机器翻译实战:从数据清洗到Java部署

机器翻译是自然语言处理的基础任务之一,其核心在于建立源语言与目标语言之间的结构化语义映射。原理上依赖序列到序列建模、注意力机制与词表对齐等关键技术,技术价值体现在可控性强、领域适配灵活、轻量级部署友好。典型应用场景包括企业内部多语言文档翻译、嵌入式NLP模块集成及小样本技术文档本地化。本文聚焦Python+TensorFlow生态下的工业级落地实践,深入解析BPE子词切分、Luong注意力实现、SparseCategoricalCrossentropy损失设计及SavedModel导出规范,覆盖数据清洗、模

weixin_34417183的博客 342

Java对接(BSC)币安链 | BNB与BEP20的开发实践(三)水龙头 WEB3

这一节我们讲一个币安测试链如何获取到BNB、USDT等BEP20数字货币(水龙头)来让我们前期测试开发。如图:我们将创建的地址填入相应输入框,来获取到测试BNB以及其他BEP20数字币。发币可能需要交易等待,请耐心等待即可,交易完成后,币会到达您的地址内。上一节我们用代码来实现BNB转账、BEP20转账、链上交易监控。2.MetaMask(小狐狸)浏览器插件 🪜。使用EDGE浏览器可以在浏览器直接打开。使用Chrome浏览器,请使用🪜打开。基本工作就绪后,即可进行开发测试。3. 测试区块链浏览器 🪜。

大猩猩的专栏 2790

智能体开发工具对比:Coze、Dify与n8n选型指南

智能体开发是自动化决策与任务执行的关键技术,其核心在于工作流编排与AI能力集成。开发者常面临工具选型难题,主流方案包括注重AI集成的Coze、开源灵活的Dify以及擅长复杂工作流的n8n。Coze凭借拖拽式界面和中文支持,特别适合快速验证AI创意;Dify的开源特性满足企业级定制需求;n8n则以300+连接器成为跨平台集成的首选。选型时需权衡开发难度、功能扩展性和部署成本,例如Python生态推荐Dify,Node.js项目适合n8n,而无编程基础者可优先考虑Coze。

weixin_33965305的博客 505

常用元器件使用方法13:物理层接口芯片DP83848的使用方法

介绍: DP83848是以太网物理层接口芯片,具有多种型号,从便于焊接角度考虑LQFP-48封装较为合适,采用LQFP-48封装的芯片,有2种型号,分别为DP83848I和DP83848C,如下图所示。 其主要区别是使用温度不同,如下图所示。 这2种芯片芯片网上均有销售,按理说DP83848C价格应该更加便宜,实际不然,DP83848I的价格低于DP83848C,考虑或与芯片的出货量...

snmplink的博客 1万+

微信小程序 - 微信支付报错 “调用支付JSAPI缺少参数: total_fee“,微信小程序强力解决调用支付JSAPI缺少参数:total_fee解决办法(包括微信小程序云开发对接微信支付报错问题)

调用支付jsapi缺少参数 total_fee,微信小程序拉起支付报: 调用支付JSAPI缺少参数: total_fee,微信小程序云开发支付 调用支付jsapi缺少参数:total_fee,微信小程序云开发微信支付报错,拉起支付失败,调用支付jsapi缺少参数:total_fee_小程序支付问题怎么解决?使用微信小程序云开发对接微信支付,拷贝官方的示例代码运行,详细解决方案,如何解决这个情况,后端没问题,还是依旧报错,微信小程序 微信支付 total_fee 缺失参数 云开发 统一下单 JSAPI,报错:

🏆 前端领域优质创作者 2960

作为一个普通的程序员,到底应不应该转型AI工程师?

作为一个普通的程序员,到底应不应该转型AI工程师?

小码农 TT 4057

YOLO26 终极蒸馏指南:从入门到 SOTA (The Ultimate Distillation Guide)

《YOLO26 蒸馏技术指南》深入解析了知识蒸馏在目标检测中的应用,提出五大蒸馏范式:响应蒸馏(学习最终输出)、特征蒸馏(模仿中间特征)、关系蒸馏(捕捉通道关联)、CWD(通道语义对齐)和FGD(前景背景分离)。实验表明,"Response KD + CWD + FGD"组合效果最佳,使YOLO26n在0.5ms推理速度下达到0.956 mAP50,甚至超越教师模型。指南提供模块化代码实现和超参数调优建议,强调蒸馏能平衡模型精度与效率,是轻量化部署的有效方案。

weixin_51988935的博客 717

Python模糊字符串匹配实战:从算法选型到生产部署

模糊字符串匹配是处理非结构化文本语义等价性的关键技术,其核心在于解决‘形异义同’的工程问题——如错别字、缩写、格式差异导致的匹配失败。它基于编辑距离、词集相似度、子串覆盖等原理,通过可解释、可配置、可阈值化的相似度计算,支撑搜索推荐、数据清洗、OCR后处理与主数据合并等关键场景。本文聚焦Python生态中`rapidfuzz`与领域规则协同的落地实践,深度融合`token_set_ratio`和预处理标准化两大热词,提供从单次匹配、批量去重到高并发API服务的全链路解决方案。

weixin_33739646的博客 364

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

普通程序员转型AI工程师:年薪50万不是梦,成功案例分享!

动不动就是50万的毕业生年薪,动不动就是100万起步价的海归AI高级人才,普通员到底应不应该转型AI工程师,普通程序员到底应该如何转型AI工程师?

2401_84204413的博客 1331

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

上一篇: Python工程师Java之路(i)泛型、Iterator、Collection、Map
下一篇: Python工程师Java之路(k)文件读写
小基基o_O
博客等级 码龄9年 4414粉丝 467原创
评论 4
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小基基o_O

您的鼓励是我创作的巨大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值