-
一、PHP 开发语言详解官方介绍PHP(Hypertext Preprocessor)是专为Web开发设计的脚本语言,由Rasmus Lerdorf于1994年创建,目前由PHP Group维护。官方定义其为“一种通用开源脚本语言,特别适合Web开发,可直接嵌入HTML中”。官网:https://www.php.net/ 独特特点嵌入式语法:可直接在HTML中编写PHP代码,如<?php echo "Hello World"; ?>,开发效率极高。动态类型:变量无需提前声明类型,如$name = "PHP";,灵活但需注意类型安全。丰富的Web扩展:原生支持MySQL、CURL、XML等Web开发常用功能,无需额外配置。优势入门门槛低:语法接近C语言,新手可快速上手,适合快速搭建网站。生态成熟:拥有WordPress、Drupal、Laravel等主流框架和CMS,开发成本低。高性能组件:搭配PHP 7+版本和OPcache缓存,性能较早期版本提升4倍以上。共享主机支持:多数虚拟主机默认支持PHP,部署成本极低。劣势类型系统松散:易引发隐藏bug(如字符串与数字的自动转换)。复杂项目维护难:缺乏强类型和命名空间规范,大型项目易出现代码混乱。多线程支持弱:传统PHP-FPM模式难以处理高并发场景,需借助Swoole等扩展。 安装使用流程环境搭建(以Linux为例):# 使用apt安装LAMP环境sudo apt-get install apache2 php php-mysql mysql-serverAI写代码编写第一个文件(在/var/www/html/hello.php中):<?phpecho "Hello, PHP!";phpinfo();?>AI写代码访问测试:浏览器输入http://localhost/hello.php查看结果。适用场景及案例适用场景:中小型网站、内容管理系统(CMS)、企业官网、电商平台。应用案例:全球80%的动态网站使用PHP,如WordPress(占全球网站的35%)、Facebook(早期核心架构)。电商平台:Magento、Shopify(部分模块)。 二、Java 开发语言详解官方介绍Java由Sun Microsystems(现Oracle)于1995年推出,基于“一次编写,到处运行”(Write Once, Run Anywhere)理念,是一种强类型、面向对象的编程语言。官方定义其为“一种跨平台的、安全的、架构中立的编程语言和计算平台”。官网:https://www.java.com/ 独特特点跨平台性:通过Java虚拟机(JVM)实现二进制代码跨系统运行,如.class文件可在Windows/Linux/Mac上执行。强类型与面向对象:严格的类型检查(如int num = 10;),支持封装、继承、多态等完整OOP特性。自动垃圾回收(GC):无需手动释放内存,降低内存泄漏风险。优势企业级开发首选:拥有Spring、Hibernate等框架,适合构建大型分布式系统。稳定性与安全性:强类型检查和异常处理机制,减少运行时错误,常用于金融、银行系统。生态庞大:Android开发默认语言,Apache Hadoop、Spark等大数据框架基于Java。多线程与并发:原生支持Thread类和Concurrent包,适合高并发场景。劣势启动速度慢:大型应用(如Spring Boot)启动需加载大量类,冷启动时间较长。内存占用高:JVM需要较大内存资源,对小型设备(如嵌入式系统)支持有限。学习曲线陡:新手需掌握OOP、设计模式、JVM调优等复杂知识。安装使用流程安装JDK(以Java 17为例):# Linux环境sudo apt-get install openjdk-17-jdkjava -version # 验证安装AI写代码编写Java文件(HelloWorld.java):public class HelloWorld { public static void main(String[] args) { System.out.println("Hello, Java!"); }}AI写代码编译与运行:javac HelloWorld.java # 生成class文件java HelloWorld # 运行程序AI写代码适用场景及案例适用场景:企业级应用、Android开发、大数据处理、金融系统、分布式微服务。应用案例:电商平台:阿里巴巴、京东的核心交易系统。大数据:Hadoop、Spark、Flink。移动开发:90%的Android应用基于Java。 三、 Go 开发语言详解官方介绍Go(又称Golang)由Google于2007年开发,2009年开源,是一种静态类型、编译型语言,设计目标是兼具Python的开发效率和C语言的性能。官方称其“简洁、高效、可靠,适合构建大型分布式系统”。官网:https://go.dev/ 独特特点goroutine与channel:原生支持轻量级线程(goroutine)和通道(channel),轻松实现高并发编程。极简语法:无继承、无复杂泛型,用组合代替继承,如type Person struct { name string }。静态编译:直接编译为机器码,无需运行时环境,部署时仅需单个二进制文件。优势高性能与高并发:单台服务器可处理百万级并发连接,适合云原生和微服务。部署简单:编译后的二进制文件可直接运行,无需依赖环境(如JVM、PHP环境)。内存效率高:垃圾回收(GC)优化出色,内存占用远低于Java。云原生首选:Kubernetes、Docker、etcd等云基础设施核心组件均由Go开发。劣势生态尚在完善:相比Java/PHP,成熟框架(如Web框架)和第三方库较少。泛型支持有限:Go 1.18引入泛型,但语法较为特殊,不如Java泛型灵活。学习曲线偏陡:需适应goroutine、channel等Go特有的并发模型。 安装使用流程安装Go(以Linux为例):wget https://go.dev/dl/go1.21.linux-amd64.tar.gzsudo tar -C /usr/local -xzf go1.21.linux-amd64.tar.gzexport PATH=$PATH:/usr/local/go/bin # 添加环境变量go version # 验证安装AI写代码编写Go文件(hello.go):package main import "fmt" func main() { fmt.Println("Hello, Go!")}AI写代码编译与运行:go build hello.go # 生成可执行文件./hello # 运行程序AI写代码适用场景及案例适用场景:云原生服务、微服务架构、高并发网络服务、区块链、DevOps工具。应用案例:云计算:Kubernetes、Docker、Prometheus。互联网服务:字节跳动微服务架构、B站后端部分组件。区块链:Ethereum 2.0客户端Geth用Go开发。 四、三大语言对比总结维度PHPJavaGo类型 动态类型、脚本语言静态类型、编译型语言静态类型、编译型语言核心优势 Web开发快、生态成熟跨平台、企业级稳定性高并发、部署简单、性能强适合场景 中小型网站、CMS大型企业应用、Android、大数据云原生、微服务、高并发系统学习曲线 低中高中(需适应并发模型)代表案例 WordPress、Facebook早期阿里巴巴、Android应用Kubernetes、Docker根据项目规模、性能需求和团队技术栈选择语言:PHP适合快速落地的Web项目,Java适合复杂企业级系统,Go则是云原生和高并发场景的新宠。PHP、Java和Go的官方网站分别是:PHP:https://www.php.net/。Java:https://www.oracle.com/java/。另一个常用的相关网站https://dev.java,提供面向开发者的Java学习资源等。Go:https://golang.org/。原文链接:https://blog.csdn.net/chenchuang0128/article/details/153470023
-
Java 泛型擦除深度解析:原理与限制全揭秘 cid:link_3JavaScript 时间戳转换日期格式 cid:link_0 Java 注解与反射实战:自定义注解从入门到精通 cid:link_4Go 中避免在 map 遍历时直接取地址导致指针复用 cid:link_1 Go 中正确使用 sync.Pool 避免内存分配 cid:link_5大模型生成中避免输出模板化句式的技巧 cid:link_2Go 中使用 time.Ticker 时避免 goroutine 泄漏的小技巧 cid:link_7 什么是Speculative Decoding的实际应用场景? cid:link_6Go 中使用 time.Ticker 时避免 goroutine 泄漏的小技巧 cid:link_7Go 中正确处理文件路径拼接避免跨平台问题 cid:link_8Java 泛型擦除深度解析:原理与限制全揭秘 cid:link_3
-
一、初阶应用阶段(10天):理解大模型基础与API调用初阶应用阶段的目标是建立对大模型的基本认知,掌握基础API调用能力,能够用代码将大模型与业务场景初步连接。1.1 大模型基础知识(2天)学习目标:了解大模型的基本概念、工作原理和主要类型核心内容:大模型定义与特点:参数规模、训练数据量、应用场景Transformer架构基础:编码器与解码器的区别,自注意力机制主流大模型类型:GPT系列、BERT系列、T5系列、Llama系列等大模型"智能"的本质:参数学习、知识表征、上下文理解学习资源:《大模型技术白皮书》(2025年最新版)Hugging Face官方文档中的模型介绍部分Transformer架构的可视化教程实践任务:使用OpenAI API实现简单的文本生成通过Hugging Face Hub探索不同模型的参数规模和性能1.2 提示工程(Prompt Engineering)(3天)学习目标:掌握如何通过精心设计的提示词引导大模型输出高质量结果核心内容:提示工程的定义与意义:如何有效与大模型"对话"提示结构的典型构成:背景设定、角色扮演、指令、示例、期望格式指令调优方法论:逐步细化指令、设定约束条件思维链(Chain-of-Thought)与思维树(Chain-of-Thought Tree):引导模型展示推理过程Prompt攻击与防范:理解安全风险并掌握防御策略学习资源:百度智能云《AI大模型提示工程精通指南》知乎《大模型提示工程:从入门到精通》系列文章OpenAI官方提示工程指南实践任务:设计不同场景的提示词(如信息检索、文本生成、翻译等)尝试思维链提示词,观察模型推理过程的变化构建基础的Prompt模板库,覆盖常见应用场景1.3 大模型API调用与集成(5天)学习目标:掌握主流大模型平台API调用方法,能够将大模型集成到简单应用中核心内容:主流大模型平台API:OpenAI、Google、阿里云、腾讯云等API调用的请求-响应结构:理解prompt、temperature、top_p等参数的作用API调用的错误处理与性能优化基础应用开发:构建简单的聊天机器人、内容生成工具学习资源:各大平台官方API文档(OpenAI、阿里云PAI等)《Python与大模型API集成实战》(2025年最新版)GitHub上公开的大模型API示例项目实践任务:使用OpenAI API实现一个基础的问答系统通过阿里云PAI部署Stable Diffusion模型并实现本地运行构建一个简单的"向GPT-3.5灌入新知识"应用,理解上下文窗口限制初阶阶段学习总结:通过这一阶段的学习,学习者将对大模型有基本认识,能够理解大模型的"智能"来源,并掌握API调用的基本方法。这为后续更深入的学习奠定了坚实基础。 二、高阶应用阶段(30天):构建私有知识库与对话系统高阶应用阶段的目标是掌握RAG检索增强生成和Agent技术,能够构建私有知识库并开发基于Agent的对话系统。2.1 检索增强生成(RAG)(10天)学习目标:理解RAG技术原理,掌握向量表示和向量数据库应用核心内容:RAG的基本架构:知识库、检索器、LLM生成器向量表示(Embeddings):文本向量化方法、不同嵌入模型的特性向量数据库:Chroma、Milvus、Qdrant等工具的对比与选择检索策略:精确检索、模糊检索、混合检索等RAG系统构建:从数据处理到检索生成的完整流程学习资源:《RAG实战指南:从理论到应用》(2025年最新版)Milvus官方文档《使用Helm安装Milvus群集》LangChain官方教程中的RAG章节实践任务:使用Milvus搭建一个简单的向量数据库通过LangChain实现一个ChatPDF应用,能够检索并生成回答对比不同嵌入模型(如text-embedding-ada-002、BERT)在RAG中的表现2.2 Agent技术(10天)学习目标:理解Agent架构,掌握多Agent协作与工具调用方法核心内容:Agent的基本概念:智能体的定义、目标、状态与行动单Agent与多Agent系统:各自的优缺点与适用场景工具调用机制:如何让Agent执行外部工具(如Python代码、数据库查询)状态跟踪与记忆管理:Agent如何保持上下文信息多Agent协作:角色分工、通信机制与任务分配学习资源:《大模型Agent开发实战》(2025年最新版)实践任务:使用AutoGen框架实现一个简单的多Agent系统构建一个工具调用Agent,能够执行Python代码完成特定任务开发一个基于Agent的对话机器人,能够处理多轮对话和复杂指令2.3 私有知识库构建(10天)学习目标:掌握如何构建和管理私有知识库,支持大模型的个性化应用核心内容:知识库的组织结构:文档存储、元数据管理、索引构建数据预处理:清洗、分块、标注与存储知识注入方法:静态知识库、动态知识更新、实时数据接入知识库安全:权限控制、隐私保护、数据加密知识库优化:检索效率提升、索引结构优化、内存管理学习资源:《大模型私有知识库构建指南》(2025年最新版)《使用Milvus部署Dify》官方文档向量数据库性能优化最佳实践实践任务:使用Milvus构建一个医疗领域的私有知识库开发一个金融领域的知识注入系统,支持实时更新实现一个知识库管理工具,包含搜索、更新和权限控制功能高阶阶段学习总结:通过这一阶段的学习,学习者将能够构建私有知识库,开发基于Agent的对话系统,掌握RAG技术在实际应用中的实现方法。这标志着学习者已经具备开发完整大模型应用系统的能力,可以应对大多数行业应用需求。三、模型训练阶段(30天):掌握Transformer架构与微调技术模型训练阶段的目标是深入理解Transformer架构,掌握模型训练方法,特别是参数高效微调(PEFT)技术。3.1 Transformer架构与实现(10天)学习目标:理解Transformer架构的数学原理,掌握其在代码中的实现核心内容:Transformer架构的数学基础:自注意力机制、位置编码、多头注意力、前馈网络编码器与解码器的实现差异:BERT与GPT架构对比Transformer层的代码实现:PyTorch/TF中的自注意力模块、位置编码、前馈网络模型扩展:层归一化、残差连接、注意力头数与维度调整模型压缩:知识蒸馏、量化、剪枝等技术原理学习资源:《深入理解Transformer架构》(2025年最新版)PyTorch官方文档中的Transformer模块《Hugging Face变压器层实现代码》教程实践任务:使用PyTorch实现一个基础的自注意力机制构建一个完整的Transformer编码器层对比不同位置编码方法(正弦编码、可学习编码)对模型性能的影响3.2 预训练与微调方法(10天)学习目标:理解预训练与微调的原理与实践,掌握数据集构建与训练流程核心内容:预训练与微调的区别:全参数微调vs参数高效微调预训练任务:掩码语言建模(MLM)、下一句预测(NSP)、文本生成等数据集构建:数据收集、清洗、标注与划分训练流程:优化器选择、学习率调度、梯度检查点等评估方法:困惑度(Perplexity)、BLEU、ROUGE等评估指标学习资源:《大模型训练实战指南》(2025年最新版)《Hugging Face预训练任务代码示例》《大模型微调实战教程》实践任务:使用Hugging Face库实现一个掩码语言建模任务构建一个适合特定任务的小型数据集使用PyTorch训练一个简单的文本分类模型3.3 参数高效微调(PEFT)技术(10天)学习目标:掌握PEFT技术的原理与实现,能够高效微调大模型核心内容:PEFT技术分类:LoRA、QLoRA、Adapter Tuning、Prefix Tuning、Prompt Tuning等LoRA原理:低秩矩阵分解、适配器矩阵设计QLoRA特性:量化权重、梯度仅更新低秩适配器PEFT技术对比:参数量、训练速度、效果差异PEFT在垂直领域应用:医疗、金融、教育等领域的微调实践学习资源:PEFT库官方文档与示例代码实践任务:使用LoRA微调一个通用大模型,适配特定任务对比不同PEFT方法在相同任务上的性能与资源消耗使用QLoRA实现一个大模型的轻量化微调模型训练阶段学习总结:通过这一阶段的学习,学习者将能够理解Transformer架构的数学原理,掌握预训练与微调的完整流程,并熟练应用PEFT技术实现高效微调。这标志着学习者已经具备独立训练开源大模型的能力,可以应对特定领域的垂直大模型需求。四、商业闭环阶段(20天):行业应用与部署方案商业闭环阶段的目标是了解大模型在各行业的应用和部署方案,掌握商业化思维和产品设计方法。4.1 行业应用案例(10天)学习目标:了解大模型在不同行业的应用案例,理解其技术实现与商业价值核心内容:医疗领域:智能诊断、药物研发、患者管理(如"终节者"小程序实现肺结节风险评估)金融领域:智能风控、客户服务、交易优化(如工商银行"工银智涌"系统实现风险监控与交易效率提升)制造业:质量检测、供应链优化、生产调度(如微亿智造的工业机器人缺陷检测系统)教育领域:个性化学习、内容生成、智能评估(如语言学习中的实时反馈系统)零售领域:智能推荐、库存管理、客户服务(如基于大模型的个性化购物助手)学习资源:《2025年"人工智能+"行业标杆案例荟萃》《国内AI大模型全景图:15款核心应用深度解析与体验指南》《银行大模型应用"加速跑"数智化竞速开新局》实践任务:分析一个医疗大模型案例的技术实现细节研究一个金融大模型的部署架构与性能指标设计一个制造业大模型应用的初步方案4.2 部署方案与工具对比(5天)学习目标:了解大模型部署的工具与方案,掌握不同部署方式的优缺点核心内容:部署工具对比:vLLM、Docker、Kubernetes等云端部署方案:AWS、Azure、阿里云等云服务商的大模型服务本地部署方案:单机部署、集群部署、硬件加速等私有化部署:数据安全、算法备案、合规要求等混合部署:云端训练、本地推理等混合架构学习资源:《大模型部署工具性能对比最新》《KubeSphere部署向量数据库Milvus实战指南》《从数据到行动:如何将大模型应用于实际业务》实践任务:使用Docker部署一个大模型推理服务在Kubernetes集群上部署一个向量数据库设计一个私有化部署方案,满足特定行业合规要求4.3 商业化思维与产品设计(5天)学习目标:理解大模型商业化的关键因素,掌握产品设计方法论核心内容:商业模式设计:SaaS、API订阅、私有化部署等不同模式的优缺点用户需求分析:如何从用户痛点出发设计大模型应用产品设计流程:从原型到MVP再到规模化部署的完整路径成本优化策略:模型压缩、推理加速、资源调度等价值创造:如何通过大模型提升业务效率、创造新价值学习资源:《AI大模型应用入门实战与进阶:构建你的第一个大模型:实战指南》《AI原生开启金融智能新未来——金融行业大模型应用落地白皮书》《产品设计使用生成对抗网络:整合消费者偏好与外部数据》实践任务:分析一个成功大模型产品的商业模式与盈利策略设计一个大模型应用的产品原型,包含用户界面与核心功能制定一个大模型应用的用户增长与留存策略商业闭环阶段学习总结:通过这一阶段的学习,学习者将能够理解大模型在不同行业的应用场景,掌握部署工具的使用与优化方法,并具备设计商业化大模型产品的思维能力。这标志着学习者已经能够将大模型技术转化为实际商业价值。五、AI大模型从0到精通全套学习大礼包我在一线互联网企业工作十余年里,指导过不少同行后辈。帮助很多人得到了学习和成长。只要你是真心想学AI大模型,我这份资料就可以无偿共享给你学习。大模型行业确实也需要更多的有志之士加入进来,我也真心希望帮助大家学好这门技术,如果日后有什么学习上的问题,欢迎找我交流,有技术上面的问题,我是很愿意去帮助大家的!如果你也想通过学大模型技术去帮助就业和转行,可以扫描下方链接👇👇大模型重磅福利:入门进阶全套104G学习资源包免费分享!01.从入门到精通的全套视频教程包含提示词工程、RAG、Agent等技术点02.AI大模型学习路线图(还有视频解说)全过程AI大模型学习路线03.学习电子书籍和技术文档市面上的大模型书籍确实太多了,这些是我精选出来的04.大模型面试题目详解05.这些资料真的有用吗?这份资料由我和鲁为民博士共同整理,鲁为民博士先后获得了北京清华大学学士和美国加州理工学院博士学位,在包括IEEE Transactions等学术期刊和诸多国际会议上发表了超过50篇学术论文、取得了多项美国和中国发明专利,同时还斩获了吴文俊人工智能科学技术奖。目前我正在和鲁博士共同进行人工智能的研究。所有的视频由智泊AI老师录制,且资料与智泊AI共享,相互补充。这份学习大礼包应该算是现在最全面的大模型学习资料了。资料内容涵盖了从入门到进阶的各类视频教程和实战项目,无论你是小白还是有些技术基础的,这份资料都绝对能帮助你提升薪资待遇,转行大模型岗位。智泊AI始终秉持着“让每个人平等享受到优质教育资源”的育人理念,通过动态追踪大模型开发、数据标注伦理等前沿技术趋势,构建起"前沿课程+智能实训+精准就业"的高效培养体系。课堂上不光教理论,还带着学员做了十多个真实项目。学员要亲自上手搞数据清洗、模型调优这些硬核操作,把课本知识变成真本事!如果说你是以下人群中的其中一类,都可以来智泊AI学习人工智能,找到高薪工作,一次小小的“投资”换来的是终身受益!应届毕业生:无工作经验但想要系统学习AI大模型技术,期待通过实战项目掌握核心技术。零基础转型:非技术背景但关注AI应用场景,计划通过低代码工具实现“AI+行业”跨界。业务赋能 突破瓶颈:传统开发者(Java/前端等)学习Transformer架构与LangChain框架,向AI全栈工程师转型。原文链接:https://blog.csdn.net/2401_85379281/article/details/156228627
-
去年在陕西某县调试系统时,环保站王工指着老旧传感器叹气:“县城就 3 台电脑,跑不动你们的复杂系统。” 那天我们用 Java 把系统核心代码压缩到 8.7MB,去掉冗余功能,只保留 “水质 + 气象” 监测 ——3 天后,这套轻量化系统在暴雨前 2 小时预警山洪,帮 23 户村民提前转移。王工后来在电话里说:“现在看数据像看天气预报,简单明了。” 这个细节让我明白:生态可视化的真谛,不是 “功能多全”,而是 “能不能走进县乡监测站的老旧电脑”。在跟进 13 个案例的日子里,我们见过长三角用 “跨界传输图谱” 协商减排,也见过县城用 “手机端轻量化看板” 巡检河道 —— 这些带着 “泥土味” 的故事,藏着技术落地的真谛。接下来,从县域的 “轻量化生存”,到 AI 的 “扩散预判”,再到长三角的 “跨界联动”,带你看 Java 如何让每一个生态数据,都成为守护城乡的 “千里眼”。一、Java 构建的全层级生态监测系统(含县域轻量化与跨区协同)1.1 县域轻量化监测系统(陕西某县案例)针对县级硬件限制(4 核 CPU+8GB 内存)的 Java 优化方案:/** * 县域生态监测轻量化服务(陕西某县实战) * 核心代码8.7MB,支持3类关键数据,响应≤500ms */@Servicepublic class CountyEnvMonitorService { private final LightweightDataProcessor processor; // 轻量化数据处理器 /** * 县域精简版数据处理(只保留核心监测项) */ public LightweightReport process(CountyEnvData data) { LightweightReport report = new LightweightReport(); // 1. 数据精简:只保留3类关键项(剔除县级用不上的复杂指标) // 水质:溶解氧(≥5mg/L达标)、pH值(6-9) // 气象:降雨量(≥50mm/24h预警) report.setWaterQuality(simplifyWaterData(data.getWaterData())); report.setWeather(simplifyWeatherData(data.getWeatherData())); // 2. 移动端适配:生成手机可直接查看的图文报告 report.setMobileView(generateMobileView(report)); // 3. 本地缓存:断网时保存24小时数据(县域网络不稳定适配) processor.cacheLocally(report, 86400); // 缓存24小时 return report; } /** * 手机端轻量化视图(大字体+红黄绿标识) */ private String generateMobileView(LightweightReport report) { // 例:"溶解氧:4.2mg/L 🔴(低于5mg/L);今日降雨:35mm 🟢" return String.format("溶解氧:%.1fmg/L %s;今日降雨:%dmm %s", report.getWaterQuality().getDoValue(), getColorMark(report.getWaterQuality().getDoValue(), 5.0), report.getWeather().getRainfall(), getColorMark(report.getWeather().getRainfall(), 50.0) ); }}AI写代码java运行县域优化效果(陕西某县 1 年数据):指标 优化前(传统系统) Java 轻量化系统 县域收益服务器占用率 92% 31% 适配老旧硬件监测覆盖率 52% 98% 新增 17 个监测点应急响应时间 8.7 小时 1.2 小时 提前转移 23 户村民基层人员操作难度 高(需培训 1 个月) 低(1 天上手) 王工:“像用微信一样简单”1.2 AI 污染扩散预测模型(Java 实现 LSTM)长三角某区域的 PM2.5 扩散预测,Java 代码实现:/** * 污染扩散AI预测服务(长三角实战) * LSTM模型预测PM2.5扩散,误差≤0.8公里 */@Servicepublic class PollutionDiffusionService { private final LSTMModel lstmModel; // 已用3年数据训练的模型 /** * 预测PM2.5未来6小时扩散路径 */ public DiffusionPrediction predict(Pm25Data currentData) { // 1. 数据预处理(只保留关键特征:浓度、风速、风向、温度) double[][] features = preprocess(currentData); // 2. LSTM预测(每小时输出一次位置) List<Coordinate> path = lstmModel.predict(features, 6); // 6小时预测 // 3. 可视化路径生成(标红超标区域,符合《环境空气质量标准》) return new DiffusionPrediction(path, markOverstandardArea(path)); } /** * 标记超标区域(PM2.5>75μg/m³为超标) */ private List<Coordinate> markOverstandardArea(List<Coordinate> path) { List<Coordinate> overAreas = new ArrayList<>(); for (Coordinate c : path) { if (c.getPm25Value() > 75) { overAreas.add(c); } } return overAreas; }}AI写代码java运行AI 预测效果(长三角 50 次验证):6 小时扩散路径误差:≤0.8 公里(传统模型 2.3 公里)超标区域预判准确率:89%应急决策提前:14 小时(如某次跨界传输预警)1.3 长三角跨界生态数据协同(16 市联动)Java 实现的跨区域数据同步与可视化:协同效果:跨界污染协商时间:从 3 天缩至 4 小时联合减排执行率:从 62% 提升至 91%2023 年长三角重污染天数:比 2022 年减少 21 天二、Java 驱动的全场景决策支持(含移动端与 AI 预警)2.1 移动端轻量化巡检系统(县城河道巡检案例)某县城用 Java 开发的手机端看板:功能:实时显示河道 pH 值、沿岸噪声(大字体 + 红黄绿标识)操作:拍照上传异常点,系统自动定位经纬度效果:巡检效率提升 2.3 倍,漏检率从 31% 降至 4%2.2 不同层级城市的应用差异城市层级 核心功能 Java 技术适配 生态效果县域 手机端巡检 + 3 类数据监测 轻量化代码 + 本地缓存 山洪预警提前 2 小时地级市 多源融合 + AI 扩散预测 分布式处理 + LSTM 模型 污染控制面积扩 1.8 倍城市群 跨界传输图谱 + 协同决策 跨区数据网关 + 共享看板 联合减排效率升 91%三、实战案例:从县城到城市群的生态守护3.1 陕西某县:轻量化系统的山洪预警痛点:硬件老旧,监测覆盖率 52%,山洪预警滞后Java 方案:8.7MB 轻量化系统 + 手机端看板,保留核心数据结果:覆盖率 98%,暴雨前 2 小时预警,转移 23 户村民3.2 长三角:跨界传输图谱的协同减排痛点:PM2.5 跨界传输责任难划,协商低效方案:Java 跨区网关 + 扩散预测,生成 “传输路径图”结果:重污染天数减 21 天,联合减排执行率 91%原文链接:https://blog.csdn.net/atgfg/article/details/155643096
-
去年在陕西某县调试系统时,环保站王工指着老旧传感器叹气:“县城就 3 台电脑,跑不动你们的复杂系统。” 那天我们用 Java 把系统核心代码压缩到 8.7MB,去掉冗余功能,只保留 “水质 + 气象” 监测 ——3 天后,这套轻量化系统在暴雨前 2 小时预警山洪,帮 23 户村民提前转移。王工后来在电话里说:“现在看数据像看天气预报,简单明了。” 这个细节让我明白:生态可视化的真谛,不是 “功能多全”,而是 “能不能走进县乡监测站的老旧电脑”。在跟进 13 个案例的日子里,我们见过长三角用 “跨界传输图谱” 协商减排,也见过县城用 “手机端轻量化看板” 巡检河道 —— 这些带着 “泥土味” 的故事,藏着技术落地的真谛。接下来,从县域的 “轻量化生存”,到 AI 的 “扩散预判”,再到长三角的 “跨界联动”,带你看 Java 如何让每一个生态数据,都成为守护城乡的 “千里眼”。一、Java 构建的全层级生态监测系统(含县域轻量化与跨区协同)1.1 县域轻量化监测系统(陕西某县案例)针对县级硬件限制(4 核 CPU+8GB 内存)的 Java 优化方案:/** * 县域生态监测轻量化服务(陕西某县实战) * 核心代码8.7MB,支持3类关键数据,响应≤500ms */@Servicepublic class CountyEnvMonitorService { private final LightweightDataProcessor processor; // 轻量化数据处理器 /** * 县域精简版数据处理(只保留核心监测项) */ public LightweightReport process(CountyEnvData data) { LightweightReport report = new LightweightReport(); // 1. 数据精简:只保留3类关键项(剔除县级用不上的复杂指标) // 水质:溶解氧(≥5mg/L达标)、pH值(6-9) // 气象:降雨量(≥50mm/24h预警) report.setWaterQuality(simplifyWaterData(data.getWaterData())); report.setWeather(simplifyWeatherData(data.getWeatherData())); // 2. 移动端适配:生成手机可直接查看的图文报告 report.setMobileView(generateMobileView(report)); // 3. 本地缓存:断网时保存24小时数据(县域网络不稳定适配) processor.cacheLocally(report, 86400); // 缓存24小时 return report; } /** * 手机端轻量化视图(大字体+红黄绿标识) */ private String generateMobileView(LightweightReport report) { // 例:"溶解氧:4.2mg/L 🔴(低于5mg/L);今日降雨:35mm 🟢" return String.format("溶解氧:%.1fmg/L %s;今日降雨:%dmm %s", report.getWaterQuality().getDoValue(), getColorMark(report.getWaterQuality().getDoValue(), 5.0), report.getWeather().getRainfall(), getColorMark(report.getWeather().getRainfall(), 50.0) ); }}AI写代码java运行县域优化效果(陕西某县 1 年数据):指标 优化前(传统系统) Java 轻量化系统 县域收益服务器占用率 92% 31% 适配老旧硬件监测覆盖率 52% 98% 新增 17 个监测点应急响应时间 8.7 小时 1.2 小时 提前转移 23 户村民基层人员操作难度 高(需培训 1 个月) 低(1 天上手) 王工:“像用微信一样简单”1.2 AI 污染扩散预测模型(Java 实现 LSTM)长三角某区域的 PM2.5 扩散预测,Java 代码实现:/** * 污染扩散AI预测服务(长三角实战) * LSTM模型预测PM2.5扩散,误差≤0.8公里 */@Servicepublic class PollutionDiffusionService { private final LSTMModel lstmModel; // 已用3年数据训练的模型 /** * 预测PM2.5未来6小时扩散路径 */ public DiffusionPrediction predict(Pm25Data currentData) { // 1. 数据预处理(只保留关键特征:浓度、风速、风向、温度) double[][] features = preprocess(currentData); // 2. LSTM预测(每小时输出一次位置) List<Coordinate> path = lstmModel.predict(features, 6); // 6小时预测 // 3. 可视化路径生成(标红超标区域,符合《环境空气质量标准》) return new DiffusionPrediction(path, markOverstandardArea(path)); } /** * 标记超标区域(PM2.5>75μg/m³为超标) */ private List<Coordinate> markOverstandardArea(List<Coordinate> path) { List<Coordinate> overAreas = new ArrayList<>(); for (Coordinate c : path) { if (c.getPm25Value() > 75) { overAreas.add(c); } } return overAreas; }}AI写代码java运行AI 预测效果(长三角 50 次验证):6 小时扩散路径误差:≤0.8 公里(传统模型 2.3 公里)超标区域预判准确率:89%应急决策提前:14 小时(如某次跨界传输预警)1.3 长三角跨界生态数据协同(16 市联动)Java 实现的跨区域数据同步与可视化:协同效果:跨界污染协商时间:从 3 天缩至 4 小时联合减排执行率:从 62% 提升至 91%2023 年长三角重污染天数:比 2022 年减少 21 天二、Java 驱动的全场景决策支持(含移动端与 AI 预警)2.1 移动端轻量化巡检系统(县城河道巡检案例)某县城用 Java 开发的手机端看板:功能:实时显示河道 pH 值、沿岸噪声(大字体 + 红黄绿标识)操作:拍照上传异常点,系统自动定位经纬度效果:巡检效率提升 2.3 倍,漏检率从 31% 降至 4%2.2 不同层级城市的应用差异城市层级 核心功能 Java 技术适配 生态效果县域 手机端巡检 + 3 类数据监测 轻量化代码 + 本地缓存 山洪预警提前 2 小时地级市 多源融合 + AI 扩散预测 分布式处理 + LSTM 模型 污染控制面积扩 1.8 倍城市群 跨界传输图谱 + 协同决策 跨区数据网关 + 共享看板 联合减排效率升 91%三、实战案例:从县城到城市群的生态守护3.1 陕西某县:轻量化系统的山洪预警痛点:硬件老旧,监测覆盖率 52%,山洪预警滞后Java 方案:8.7MB 轻量化系统 + 手机端看板,保留核心数据结果:覆盖率 98%,暴雨前 2 小时预警,转移 23 户村民3.2 长三角:跨界传输图谱的协同减排痛点:PM2.5 跨界传输责任难划,协商低效方案:Java 跨区网关 + 扩散预测,生成 “传输路径图”结果:重污染天数减 21 天,联合减排执行率 91%原文链接:https://blog.csdn.net/atgfg/article/details/155643096
-
1.概述Redis是一个基于内存的数据库,这意味着其主要数据存储和操作均在内存中进行。这种设计使得Redis能够提供极快的读写速度(通常达到微秒级别),适用于高性能场景,如缓存然而,由于内存的易失性(断电后数据会丢失),Redis提供了持久化机制:将内存中的数据保存到磁盘中,确保数据在Redis服务重启或崩溃后能够恢复。通过持久化,可以避免数据丢失,提高数据的可靠性Redis提供两种持久化方式RDB(Redis Database):生成数据集的快照实现持久化AOF(Append Only File):记录所有写操作命令,以追加方式写入文件2.RDBRDB指的是Redis的一种持久化机制,其核心是生成Redis数据在某个时间点的快照2.1 快照原理由于Redis是单线程应用程序,在线上环境时,不仅要处理来自客户端的请求,还要执行内存快照操作(进行文件IO)。单线程同时处理客户端请求和文件IO时会严重降低服务器性能,甚至阻塞客户端请求。因此,Redis使用 fork 和 写实拷贝(Copy On Write) 机制来实现快照持久化fork Redis在进行RDB持久化时会调用fork函数来创建一个子进程负责完成,父进程则继续处理客户端请求。子进程在创建之初和父进程共享同一数据段 Linux操作系统的内存空间被分为很多种片段,每个片段又被分为很多个页面,每个页面4KB写实拷贝 当父进程对数据段中的某一数据页面进行修改操作时,Linux操作系统会将该数据页面复制一份分离出来,然后对该页面进行修改,最后父进程指向指向修改后的页面。随着被修改的页面越来越多,内存空间不断膨胀,最多达到原来的两倍 从子进程被创建出来的那一刻起,直至拷贝结束,子进程始终指向原始的数据段且所有原数据段不会被修改。所以,在整个拷贝过程中 RDB快照 = 子进程看到的所有数据页面的瞬间状态集合 拷贝完成后,子进程会被销毁,同时没有指针指向的数据页面也会被销毁2.2 触发机制Redis RDB的触发机制分为自动触发和手动触发两种方式自动触发在redis.conf中通过save指令配置阈值。当在指定时间内发生足够数量的键修改时自动触发bgsave正常关闭Redis# 默认执行save(阻塞式)> shutdown# 或> shutdown save# 触发流程:1. 停止接受新连接2. 执行save(不是bgsave)3. 保存完成后退出AI写代码bash手动触发save命令:同步阻塞式触发,执行期间Redis服务器不处理任何请求,直到RDB文件创建完成(不推荐)bgsave命令:异步非阻塞式触发,Redis会fork一个子进程执行持久化操作,主进程继续处理请求2.3 文件处理 RDB文件保存在dir配置指定的目录下(默认/var/lib/redis),文件名通过dbfilename配置指定(默认dump.sql) 在RDB备份过程中,fork出的子进程会将内存数据写入临时文件,临时文件默认命名规则为temp-< pid >.rdb,其中< pid >是子进程的进程ID。当子进程完成RDB文件写入后,Redis会用原子性的rename操作将临时文件重命名为正式RDB文件并删除原文件2.4 优缺点优点恢复速度快:RDB是数据的二进制快照,恢复时直接加载到内存备份时对服务影响小:使用bgsave命令时,Redis通过fork子进程在后台保存数据,主进程可以继续处理客户端请求,几乎无阻塞存储高效:RDB 文件使用二进制格式并支持LZF压缩缺点非实时一致性:RDB保存的是某个瞬间的快照,如果保存过程中有大量写入,快照可能不反映完全一致的业务状态可能丢失更多数据:如果Redis意外宕机,从上一次RDB保存到宕机之间的所有数据修改都会丢失3.AOFAOF持久化通过将Redis服务器接收到的每个写命令追加到文件末尾来实现# 开启AOFappendonly yesAI写代码bash3.1 工作流程命令追加:当Redis执行写命令时,该命令会以Redis协议格式追加到内存中的AOF缓冲区(aof_buf)。缓冲区会根据配置策略决定何时将内容同步到磁盘文件写入与同步:AOF缓冲区内容会被写入到AOF文件,具体同步到磁盘的时机由appendfsync参数控制:always:每次写命令后同步,数据安全性最高但性能影响较大everysec:每秒同步一次,平衡性能与安全性(默认配置)no:由操作系统决定同步时机,性能最好但可能丢失较多数据3.2 重写机制作用:解决AOF文件不断增长导致的存储空间占用和恢复效率问题。通过重写,可以生成一个更紧凑的AOF文件,仅包含重建当前数据集所需的最小命令集合(例如,对同一个键多次修改会记录多条命令,而重写机制会合并这些操作,仅保留最终状态的命令) 父进程通过fork创建一个子进程来完成AOF文件的重写,确保主进程继续处理客户端请求。子进程会读取当前数据库的快照数据,并将其转换为一系列Redis命令写入新的临时AOF文件 在重写过程中,主进程会将新接收到的写命令同时写入现有的AOF 缓冲区aof_buf(保证原有 AOF 文件正常更新)和AOF重写缓冲区aof_rewrite_buf(保证新命令不会丢失) 当子进程完成重写后,会通知主进程。主进程会将 AOF 重写缓冲区中的命令追加到新生成的临时 AOF 文件中,最后原子性地替换旧文件 在Redis7.0.15版本,AOF文件保存在dir + appenddirname配置指定的目录下(默认/var/lib/redis/appendonlydir)。文件前缀名通过appendfilename配置指定(默认appendonly)appendonly.aof.1.base.rdb:作为Redis AOF(Append-Only File)持久化机制的基准文件,存储某一时刻数据库的完整快照。格式为RDB,体积较小且加载速度快,用于重建数据的基础状态appendonly.aof.1.incr.aof和appendonly.aof.2.incr.aof:记录基准文件生成后的增量写操作命令,以文本形式追加存储。多个增量文件按操作顺序编号(如.1.incr.aof、.2.incr.aof),Redis 重启时会按顺序重放这些命令以恢复最新数据appendonly.aof.manifest:描述AOF文件的组成和顺序的清单文件4.混合持久化 Redis 混合持久化结合了 RDB(快照)和 AOF(日志)两种持久化方式的优势,在保证数据安全性的同时兼顾性能# 开启混合持久化aof-use-rdb-preamble yesAI写代码bash基础RDB文件优先加载:appendonly.aof.1.base.rdb作为全量快照数据文件,会优先被加载。该文件包含某一时间点的完整数据快照,恢复时作为基准数据集增量AOF文件后续应用:appendonly.aof.1.incr.aof作为增量操作日志,在基础RDB加载完成后被重放。该文件记录自 RDB 快照生成后的所有写操作,用于恢复最新数据状态原文链接:https://blog.csdn.net/2401_89167985/article/details/147985399
-
【问题来源】 内部测试环境功能测试 【问题简要】麻烦咨询下,客户呼入后最外层ivr语音中按1转智能语音交互,按2走按键交互,这个按1或者按2目前在icd库下有哪张表记录吗?目前话单表tbilllog表中未找到该数据信息。或者应该如何获取电话呼入客户的最外层按键值呢?谢谢!【问题类别】 话单数据【AICC解决方案版本】 AICC 版本:AICC 25_300.0【期望解决时间】 尽快 【日志或错误截图】无
-
1. 引入依赖首先,需要确认项目中是否直接或者间接引入过spring-web依赖,如果没有引入过,需要在pom.xml中添加以下代码引入依赖:<dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.3.24.RELEASE</version></dependency>2. 发送GET请求使用RestTemplate发送GET请求主要有getForObject()和getForEntity()2个方法,每个方法分别提供了3种不同的重载。2.1 使用getForObject发送GET请求(无参数)使用getForObject()实现:RestTemplate restTemplate = new RestTemplate();String url = "https://www.example.com/getCurrentEnv";String response = restTemplate.getForObject(url, String.class);System.out.println(response);假设以上接口返回的报文为:{ "serverAddress": "www.example.dev.com", "env": "dev"}也可以直接解析为自定义的类型:import lombok.Getter;import lombok.Setter;@Getter@Setterpublic class EnvInfo { private String serverAddress; private String env;}RestTemplate restTemplate = new RestTemplate();String url = "https://www.example.com/getCurrentEnv";EnvInfo response = restTemplate.getForObject(url, EnvInfo.class);System.out.println(JSON.toJSONString(response));2.2 使用getForEntity发送GET请求(无参数)也可以使用getForEntity()实现和2.1同样的功能,代码如下所示:RestTemplate restTemplate = new RestTemplate();String url = "https://www.example.com/getCurrentEnv";ResponseEntity<EnvInfo> responseEntity = restTemplate.getForEntity(url, EnvInfo.class);if (responseEntity.getStatusCode() == HttpStatus.OK) { EnvInfo response = responseEntity.getBody(); System.out.println(JSON.toJSONString(response));}2.3 使用getForObject发送GET请求(带参数)第一种方法是直接在url上拼接上参数,如下所示:RestTemplate restTemplate = new RestTemplate();String url = "https://www.example.com/getDataList?pageIndex=1&pageSize=20";EnvInfo response = restTemplate.getForObject(url, EnvInfo.class);System.out.println(JSON.toJSONString(response));第二种方法是使用占位符添加参数,如下所示:RestTemplate restTemplate = new RestTemplate();String url = "https://www.example.com/getDataList?pageIndex={1}&pageSize={2}";EnvInfo response = restTemplate.getForObject(url, EnvInfo.class, 1, 20);System.out.println(JSON.toJSONString(response));以上代码也可以替换为:RestTemplate restTemplate = new RestTemplate();String url = "https://www.example.com/getDataList?pageIndex={pageIndex}&pageSize={pageSize}";Map<String, String> uriVariables = new HashMap<>();uriVariables.put("pageIndex", "1");uriVariables.put("pageSize", "20");EnvInfo response = restTemplate.getForObject(url, EnvInfo.class, uriVariables);System.out.println(JSON.toJSONString(response));注意事项:uriVariables中的key必须和url中的占位符名称一致,否则会抛出异常:java.lang.IllegalArgumentException: Map has no value for 'pageIndex'第三种方法是使用UriComponentsBuilder添加参数,该种方法相比于前两种方法更加灵活,可以实现动态添加参数,代码如下所示:RestTemplate restTemplate = new RestTemplate();String httpUrl = "https://www.example.com/getDataList";UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(httpUrl);uriComponentsBuilder.queryParam("pageIndex", 1);uriComponentsBuilder.queryParam("pageSize", 20);String url = uriComponentsBuilder.toUriString();EnvInfo response = restTemplate.getForObject(url, EnvInfo.class);System.out.println(JSON.toJSONString(response));2.4 使用getForEntity发送GET请求(带参数)也可以使用getForEntity()实现和2.3同样的功能,代码如下所示:RestTemplate restTemplate = new RestTemplate();String httpUrl = "https://www.example.com/getDataList";UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(httpUrl);uriComponentsBuilder.queryParam("pageIndex", 1);uriComponentsBuilder.queryParam("pageSize", 20);String url = uriComponentsBuilder.toUriString();ResponseEntity<EnvInfo> responseEntity = restTemplate.getForEntity(url, EnvInfo.class);if (responseEntity.getStatusCode() == HttpStatus.OK) { EnvInfo response = responseEntity.getBody(); System.out.println(JSON.toJSONString(response));}2.5 getForObject与getForEntity的区别getForEntity()与getForObject()相比,返回值用了ResponseEntity进行封装,可以多获取到以下2种信息:HTTP状态码Response Headers代码示例:RestTemplate restTemplate = new RestTemplate();String httpUrl = "https://www.example.com/getDataList";UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(httpUrl);uriComponentsBuilder.queryParam("pageIndex", 1);uriComponentsBuilder.queryParam("pageSize", 20);String url = uriComponentsBuilder.toUriString();ResponseEntity<EnvInfo> responseEntity = restTemplate.getForEntity(url, EnvInfo.class);System.out.println("statusCode: " + responseEntity.getStatusCode().toString());System.out.println("statusCodeValue: " + responseEntity.getStatusCodeValue());responseEntity.getHeaders().forEach((key, values) -> { System.out.println(key + ": " + values);});输出结果:statusCode: 200statusCodeValue: 200Server: [openresty]Date: [Thu, 10 Apr 2025 05:39:02 GMT]Content-Type: [application/json]Transfer-Encoding: [chunked]Connection: [keep-alive]其中Response Headers输出部分和Chrome浏览器Network中的Response Headers是一致的:2.6 发送GET请求(带参数及请求头)一般情况下,请求第三方接口都需要签名、时间戳等请求头,但getForObject()和getForEntity()都不支持,此时需要使用exchange()方法,代码如下所示:RestTemplate restTemplate = new RestTemplate();String httpUrl = "https://www.example.com/getDataList";UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(httpUrl);uriComponentsBuilder.queryParam("pageIndex", 1);uriComponentsBuilder.queryParam("pageSize", 20);HttpHeaders headers = new HttpHeaders();headers.set("signature", "3045022100875efcef9eb54626bb0168a6baa7c61265d0001d49243f");headers.set("timestamp", String.valueOf(System.currentTimeMillis()));String url = uriComponentsBuilder.toUriString();ResponseEntity<EnvInfo> responseEntity = restTemplate.exchange(url, HttpMethod.GET, new HttpEntity<>(headers), EnvInfo.class);if (responseEntity.getStatusCode() == HttpStatus.OK) { EnvInfo response = responseEntity.getBody(); System.out.println(JSON.toJSONString(response));}3. 发送POST请求使用RestTemplate发送POST请求主要有postForObject()和postForEntity()2个方法,每个方法分别提供了3种不同的重载。3.1 发送POST请求(带参数、json方式)使用postForObject()实现:RestTemplate restTemplate = new RestTemplate();HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON_UTF8);LoginParams loginParams = new LoginParams();loginParams.setUsername("zhangsan");loginParams.setPassword("123456");HttpEntity<LoginParams> request = new HttpEntity<>(loginParams, headers);String url = "https://www.example.com/login";String response = restTemplate.postForObject(url, request, String.class);System.out.println(response);LoginParams的定义如下所示:import lombok.Getter;import lombok.Setter;@Getter@Setterpublic class LoginParams { private String username; private String password;}假设以上接口返回的报文为:{ "code": 200, "expire": "2025-04-11 14:42:22", "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE3NDQzNTM3NDIsImlkZW50aXR5"}也可以直接解析为自定义的类型:import lombok.Getter;import lombok.Setter;@Getter@Setterpublic class LoginResponse { private Integer code; private String expire; private String token;}RestTemplate restTemplate = new RestTemplate();HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON_UTF8);LoginParams loginParams = new LoginParams();loginParams.setUsername("zhangsan");loginParams.setPassword("123456");HttpEntity<LoginParams> request = new HttpEntity<>(loginParams, headers);String url = "https://www.example.com/login";LoginResponse response = restTemplate.postForObject(url, request, LoginResponse.class);System.out.println(JSON.toJSONString(response));也可以使用postForEntity()实现同样的功能,代码如下所示:ResponseEntity<LoginResponse> responseEntity = restTemplate.postForEntity(url, request, LoginResponse.class);if (responseEntity.getStatusCode() == HttpStatus.OK) { LoginResponse response = responseEntity.getBody(); System.out.println(JSON.toJSONString(response));}3.2 发送POST请求(带参数、form表单方式)使用postForObject()实现:RestTemplate restTemplate = new RestTemplate();HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);MultiValueMap<String, String> map = new LinkedMultiValueMap<>();map.add("username", "zhangsan");map.add("password", "123456");HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);String url = "https://www.example.com/login";LoginResponse response = restTemplate.postForObject(url, request, LoginResponse.class);System.out.println(JSON.toJSONString(response));也可以使用postForEntity()实现同样的功能,代码如下所示:ResponseEntity<LoginResponse> responseEntity = restTemplate.postForEntity(url, request, LoginResponse.class);if (responseEntity.getStatusCode() == HttpStatus.OK) { LoginResponse response = responseEntity.getBody(); System.out.println(JSON.toJSONString(response));}3.3 postForObject与postForEntity的区别postForObject()与postForEntity()的区别,与getForEntity()与getForObject()的区别一样,返回值用了ResponseEntity进行封装。4. 超时时间设置如果需要自定义HTTP请求的连接超时时间和数据传输超时时间,代码如下所示:import org.springframework.beans.factory.annotation.Value;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.http.client.SimpleClientHttpRequestFactory;import org.springframework.web.client.RestTemplate;@Configurationpublic class RestTemplateConfig { @Value("${restTemplate.connectTimeout:5000}") private int connectTimeout; @Value("${restTemplate.readTimeout:10000}") private int readTimeout; @Bean public RestTemplate restTemplate() { SimpleClientHttpRequestFactory simpleClientHttpRequestFactory = new SimpleClientHttpRequestFactory(); simpleClientHttpRequestFactory.setConnectTimeout(connectTimeout); simpleClientHttpRequestFactory.setReadTimeout(readTimeout); return new RestTemplate(simpleClientHttpRequestFactory); }}转载自https://www.cnblogs.com/zwwhnly/p/18820159
-
1. 前言最近在自测接口时,发现一个问题:字段类型定义的是Date,但接口返回值里却是时间戳(1744959978674),而不是预期的2025-04-18 15:06:18。private Date useTime;{ "code": "200", "message": "", "result": [ { "id": 93817601, "useTime": 1744959978674 } ]}这种返回值,无法快速的知道是哪个时间,如果想知道时间对不对,还得找一个时间戳转换工具做下转换才能确定,非常不方便。因此想让接口直接返回预期的2025-04-18 15:06:18格式。刚开始,在字段上添加了@JsonFormat注解,发现没生效,返回的还是时间戳:import com.fasterxml.jackson.annotation.JsonFormat;@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss")private Date useTime;然后,改成了@JSONField注解,发现生效了,达到了预期的结果:import com.alibaba.fastjson.annotation.JSONField;@JSONField(format = "yyyy-MM-dd HH:mm:ss")private Date useTime;{ "code": "200", "message": "", "result": [ { "id": 93817601, "useTime": "2025-04-18 15:06:18" } ]}那么问题来了,为啥@JSONField生效,@JsonFormat不生效?2. 原因分析默认情况下,Spring Boot使用的JSON消息转换器是Jackson的MappingJackson2HttpMessageConverter,核心依赖为:<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.8.11.3</version></dependency>现在使用Jackson的@JsonFormat注解不生效,说明Spring Boot没有使用默认的MappingJackson2HttpMessageConverter。使用fastjson的@JSONField注解生效了,说明Spring Boot使用的是fastjson下的JSON消息转换器,也就是FastJsonHttpMessageConverter,依赖为:<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.83</version></dependency>那么怎么找到代码在哪配置的呢?第一步,先在项目中全局搜索FastJsonHttpMessageConverter(Windows快捷键:Ctrl+Shift+F),不过大概率是搜索不到,因为公司里的项目一般都继承自公司公共的xxx-spring-boot-starter。第二步,连按2次Shift键搜索FastJsonHttpMessageConverter,然后查找该类的引用或者子类(子类很可能是公司底层框架中写的)。然后,很可能会找到类似下面的代码:@Configurationpublic class FastJsonMessageConverterConfig { @Bean public HttpMessageConverters customConverters() { FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter(); return new HttpMessageConverters(new HttpMessageConverter[]{fastJsonHttpMessageConverter}); }}以上代码显式注册了一个FastJsonHttpMessageConverter,并通过HttpMessageConverters覆盖了默认的HTTP 消息转换器(Jackson的MappingJackson2HttpMessageConverter),所以Spring MVC将只使用fastjson处理JSON序列化/反序列化。这也是@JSONField生效,@JsonFormat不生效的根本原因。3. 默认行为及全局配置fastjson 1.2.36及以上版本,默认将日期序列化为时间戳(如1744959978674),如果要默认将日期序列化为yyyy-MM-dd HH:mm:ss(如2025-04-18 15:06:18),需要启用WriteDateUseDateFormat特性:@Configurationpublic class FastJsonMessageConverterConfig { @Bean public HttpMessageConverters customConverters() { FastJsonConfig fastJsonConfig = new FastJsonConfig(); // 启用日期格式化特性(禁用时间戳) fastJsonConfig.setSerializerFeatures(SerializerFeature.WriteDateUseDateFormat); // 设置日期格式(不指定时,默认为yyyy-MM-dd HH:mm:ss,但即使与默认值一致,也建议明确指定) fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); FastJsonHttpMessageConverter fastJsonHttpMessageConverter = new FastJsonHttpMessageConverter(); fastJsonHttpMessageConverter.setFastJsonConfig(fastJsonConfig); return new HttpMessageConverters(new HttpMessageConverter[]{fastJsonHttpMessageConverter}); }}如果某个日期字段有特殊序列化要求,可以使用@JSONField注解灵活配置(该注解会覆盖全局配置):import com.alibaba.fastjson.annotation.JSONField;@JSONField(format = "yyyy-MM-dd")private Date anotherUseTime;注意事项:修改全局配置需慎重,如果一个老项目,原来日期类型返回的都是时间戳,突然全部改为返回字符串,可能会造成调用方报错。转载自https://www.cnblogs.com/zwwhnly/p/18838062
-
1. 踩坑经历假设有这样一个业务场景,需要对各个城市的订单量排序,排序规则为:先根据订单量倒序排列,再根据城市名称正序排列。示例代码:import lombok.Getter;import lombok.Setter;import lombok.ToString;@Getter@Setter@ToStringpublic class OrderStatisticsInfo { private String cityName; private Integer orderCount; public OrderStatisticsInfo(String cityName, Integer orderCount) { this.cityName = cityName; this.orderCount = orderCount; }}public static void main(String[] args) { List<OrderStatisticsInfo> orderStatisticsInfoList = Arrays.asList( new OrderStatisticsInfo("上海", 1000), new OrderStatisticsInfo("北京", 1000), new OrderStatisticsInfo("成都", 700), new OrderStatisticsInfo("常州", 700), new OrderStatisticsInfo("广州", 900), new OrderStatisticsInfo("深圳", 800) ); orderStatisticsInfoList.sort(Comparator.comparing(OrderStatisticsInfo::getOrderCount, Comparator.reverseOrder()) .thenComparing(OrderStatisticsInfo::getCityName)); orderStatisticsInfoList.forEach(System.out::println);}预期结果:北京 1000上海 1000广州 900深圳 800常州 700成都 700实际结果:OrderStatisticsInfo(cityName=上海, orderCount=1000)OrderStatisticsInfo(cityName=北京, orderCount=1000)OrderStatisticsInfo(cityName=广州, orderCount=900)OrderStatisticsInfo(cityName=深圳, orderCount=800)OrderStatisticsInfo(cityName=常州, orderCount=700)OrderStatisticsInfo(cityName=成都, orderCount=700)从以上结果可以看出,根据订单量倒序排列没啥问题,但根据城市名称正序排列不符合预期:上海竟然排到了北京的前面,但常州与成都的顺序又是对的。2. 原因分析Comparator.comparing对字符串类型进行排序时,默认使用的是字符串的自然排序,即String的compareTo方法,该方法是基于Unicode编码值进行比较的,未考虑语言特定的字符顺序(如中文拼音)。先看下String的compareTo方法的源码:public int compareTo(String anotherString) { int len1 = value.length; int len2 = anotherString.value.length; int lim = Math.min(len1, len2); char v1[] = value; char v2[] = anotherString.value; int k = 0; while (k < lim) { char c1 = v1[k]; char c2 = v2[k]; if (c1 != c2) { return c1 - c2; } k++; } return len1 - len2;}以上海与北京的比较为例,先比较第一个字符,也就是字符上和字符北,字符上对应的Unicode编码值是19978,因此c1 = 19978,字符北对应的Unicode编码值是21271,因此c2 = 21271,因为c1 != c2,所以返回值为-1293,也就是说上海小于北京(要排在北京的前面),不符合预期。以常州与成都的比较为例,先比较第一个字符,也就是字符常和字符成,字符常对应的Unicode编码值是24120,因此c1 = 24120,字符成对应的Unicode编码值是25104,因此c2 = 25104,因为c1 != c2,所以返回值为-984,也就是说常州小于成都(要排在成都的前面),符合预期。可以通过Character.codePointAt方法获取字符的Unicode编码值:// 输出:19978System.out.println(Character.codePointAt("上海", 0));// 输出:21271System.out.println(Character.codePointAt("北京", 0));// 输出:24120System.out.println(Character.codePointAt("常州", 0));// 输出:25104System.out.println(Character.codePointAt("成都", 0));3. 解决方案Java提供了本地化的排序规则,可以按特定语言规则排序(如中文拼音),代码如下所示:orderStatisticsInfoList.sort(Comparator.comparing(OrderStatisticsInfo::getOrderCount, Comparator.reverseOrder()) .thenComparing(OrderStatisticsInfo::getCityName, Collator.getInstance(Locale.CHINA)));orderStatisticsInfoList.forEach(System.out::println);此时的输出结果为:OrderStatisticsInfo(cityName=北京, orderCount=1000)OrderStatisticsInfo(cityName=上海, orderCount=1000)OrderStatisticsInfo(cityName=广州, orderCount=900)OrderStatisticsInfo(cityName=深圳, orderCount=800)OrderStatisticsInfo(cityName=常州, orderCount=700)OrderStatisticsInfo(cityName=成都, orderCount=700)可以看到,北京排到了上海的前面,符合预期。上述代码指定了Collator.getInstance(Locale.CHINA),在排序比较时不再执行String的compareTo方法,而是执行Collator的compare方法,实际上是RuleBasedCollator的compare方法。可以执行以下代码单独看下上海与北京的比较结果:Collator collator = Collator.getInstance(Locale.CHINA);// 输出:1,代表上海大于北京,也就是要排在北京的后面System.out.println(collator.compare("上海", "北京"));转载自https://www.cnblogs.com/zwwhnly/p/18846441
-
1. 前言在我接触过的大部分Java项目中,经常看到使用@Autowired注解进行字段注入:import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class OrderService { @Autowired private PaymentService paymentService; @Autowired private InventoryService inventoryService;}在IDEA中,以上代码@Autowired注解下会显示波浪线,鼠标悬停后提示:Field injection is not recommended,翻译过来就是不建议使用字段注入。关于该提示问题,有直接修改IDEA设置关闭该提示的,有替换为使用@Resource注解的,但这都不是该问题的本质。该问题的本质是Spring官方推荐使用构造器注入,IDEA作为一款智能化的IDE,针对该项进行了检测并给以提示。所以该提示背后的本质问题是:为什么Spring官方推荐构造器注入而不是字段注入?2. 推荐构造器注入的理由相比字段注入,构造器注入有以下几个优点:支持不可变性依赖明确单元测试友好循环依赖检测前置,提前暴露问题2.1 支持不可变性构造器注入允许将依赖字段声明为final,确保对象一旦创建,其依赖关系不再被修改。字段注入无法使用final,依赖可能在对象生命周期中被意外修改,破坏状态一致性。构造器注入示例:import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class OrderService { private final PaymentService paymentService; private final InventoryService inventoryService; @Autowired public OrderService(PaymentService paymentService, InventoryService inventoryService) { this.paymentService = paymentService; this.inventoryService = inventoryService; }}说明:如果Spring版本是4.3或者更高版本且只有一个构造器,构造器上的@Autowired注解可以省略。2.2 依赖明确构造器注入通过在类的构造函数中显式声明依赖,并且强制要求在创建对象时必须提供所有必须的依赖项,通过构造函数参数,使用者对该类的依赖一目了然。字段注入通过在类的字段上直接使用@Autowired注解注入依赖,依赖关系隐藏在类的内部,使用者无法直接看到该类的依赖。2.3 单元测试友好构造器注入允许直接通过new创建对象,无需依赖Spring容器或反射,降低了测试复杂度。字段注入需要依赖Spring容器或反射,增加了测试复杂度。2.4 循环依赖检测前置,提前暴露问题构造器注入在应用启动时直接暴露循环依赖,强制开发者通过设计解决问题。字段注入在应用启动时不会暴露循环依赖,直到实际调用时才可能暴露问题,增加调试难度。示例:假设项目中有以下两个Service存在循环依赖:import org.springframework.stereotype.Service;@Servicepublic class OrderService { private final PaymentService paymentService; public OrderService(PaymentService paymentService) { this.paymentService = paymentService; }}import org.springframework.stereotype.Service;@Servicepublic class PaymentService { private final OrderService orderService; public PaymentService(OrderService orderService) { this.orderService = orderService; }}此时启动项目会报错,抛出org.springframework.beans.factory.BeanCurrentlyInCreationException异常,大致的异常信息如下所示:Caused by: org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'orderService': Requested bean is currently in creation: Is there an unresolvable circular reference?将以上两个Service修改为字段注入:import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class OrderService { @Autowired private PaymentService paymentService;}import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Servicepublic class PaymentService { @Autowired private OrderService orderService;}此时启动项目不会报错,可以启动成功。3. @RequiredArgsConstructor注解的使用及原理为了避免样板化代码或者为了简化代码,有的项目中可能会使用@RequiredArgsConstructor注解来代替显式的构造方法:import lombok.RequiredArgsConstructor;import org.springframework.stereotype.Service;@RequiredArgsConstructor@Servicepublic class OrderService { private final PaymentService paymentService; private final InventoryService inventoryService;}接下来简单讲解下@RequiredArgsConstructor注解的原理。@RequiredArgsConstructor注解用于在编译时自动生成包含特定字段的构造方法。字段筛选逻辑如下所示:被final修饰的未显式初始化的非静态字段被@NonNull注解标记的未显式初始化的非静态字段示例:import lombok.NonNull;import lombok.RequiredArgsConstructor;@RequiredArgsConstructorpublic class User { private final String name; @NonNull private Integer age; private final String address = ""; private String email; private static String city; @NonNull private String sex = "男";}以上代码在编译时自动生成的构造方法如下所示:public User(String name, @NonNull Integer age) { if (age == null) { throw new NullPointerException("age is marked non-null but is null"); } else { this.name = name; this.age = age; }}从生成的构造方法可以看出:1)如果字段被lombok.NonNull注解标记,在生成的构造方法内会做null值检查。2)address字段虽然被final修饰,但因为已初始化,所以未包含在构造方法中。3)email字段既没被final修饰,也没被lombok.NonNull注解标记,所以未包含在构造方法中。4)city字段是静态字段,所以未包含在构造方法中。5)sex字段虽然被lombok.NonNull注解标记,但因为已初始化,所以未包含在构造方法中。4. 总结@Autowired注解在IDEA中提示:Field injection is not recommended,其背后的本质问题是:Spring官方推荐构造器注入而不是字段注入。而Spring官方推荐构造器注入,是因为相比字段注入,构造器注入有以下几个优点:支持不可变性依赖明确单元测试友好循环依赖检测前置,提前暴露问题使用构造器注入时,为了避免样板化代码或者为了简化代码,可以使用@RequiredArgsConstructor注解来代替显式的构造方法,因为@RequiredArgsConstructor注解可以在编译时自动生成包含特定字段的构造方法。至于项目中要不要使用构造器注入,使用显式的构造方法还是使用@RequiredArgsConstructor注解来简化代码,可以根据个人喜好及团队规范自行决定。转载自https://www.cnblogs.com/zwwhnly/p/18907966
-
在日常的开发工作中,我们经常使用到Java Stream,特别是Stream API中提供的Collectors.toList()收集器,但有些场景下,我们需要将集合转换为Map,这时候就需要使用到Stream API中提供的另一个收集器:Collectors.toMap,它可以将流中的元素映射为键值对,并收集到一个Map中。1. 三种主要的重载方法Collectors.toMap有3种重载方法,分别是:1)两个参数的重载方法(最简单的形式)public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) { return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);}2)三个参数的重载方法(包含冲突处理)public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction) { return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);}3)四个参数的重载方法(指定Map实现)public static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier) { BiConsumer<M, T> accumulator = (map, element) -> map.merge(keyMapper.apply(element), valueMapper.apply(element), mergeFunction); return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);}接下来,我们结合使用示例详细讲解。2. 使用示例2.1 将对象的某些属性转换为Map假设有一个城市列表,需要将其转换为Map,其中Key为城市ID、Value为城市名称,转换方法如下所示:@Getter@Setterpublic class City { private Integer cityId; private String cityName; public City(Integer cityId, String cityName) { this.cityId = cityId; this.cityName = cityName; }}List<City> cityList = Arrays.asList( new City(1, "北京"), new City(2, "上海"), new City(3, "广州"), new City(4, "深圳"));Map<Integer, String> cityMap = cityList.stream() .collect(Collectors.toMap(City::getCityId, City::getCityName));System.out.println(cityMap);输出结果: 2.2 将对象列表转换为Map(ID -> 对象)仍然使用上面的城市列表,需要将其转换为Map,其中Key为城市ID、Value为城市对象,转换方法如下所示:List<City> cityList = Arrays.asList( new City(1, "北京"), new City(2, "上海"), new City(3, "广州"), new City(4, "深圳"));Map<Integer, City> cityMap = cityList.stream() .collect(Collectors.toMap(City::getCityId, city -> city));City city = cityMap.get(1);System.out.println("城市ID: " + city.getCityId());System.out.println("城市名称: " + city.getCityName());输出结果如下所示:城市ID: 1城市名称: 北京上面的写法等价于:Map<Integer, City> cityMap = cityList.stream() .collect(Collectors.toMap(City::getCityId, Function.identity()));因为Function.identity()内部实现是下面这样的:static <T> Function<T, T> identity() { return t -> t;}2.3 键冲突处理假设上面的城市列表中有一个ID重复的城市:List<City> cityList = Arrays.asList( new City(1, "北京"), new City(2, "上海"), new City(3, "广州"), new City(4, "深圳"), new City(4, "天津"));Map<Integer, String> cityMap = cityList.stream() .collect(Collectors.toMap(City::getCityId, City::getCityName));System.out.println("城市ID: 4, 城市名称: " + cityMap.get(4));此时运行代码,会抛出java.lang.IllegalStateException异常,如下图所示:有3种常见的键冲突处理方式,分别是保留旧值、使用新值和合并值,接下来一一讲解。1)方式一:保留旧值Map<Integer, String> cityMap = cityList.stream() .collect(Collectors.toMap(City::getCityId, City::getCityName, (oldValue, newValue) -> oldValue));输出结果:城市ID: 4, 城市名称: 深圳2)方式二:使用新值Map<Integer, String> cityMap = cityList.stream() .collect(Collectors.toMap(City::getCityId, City::getCityName, (oldValue, newValue) -> newValue));输出结果:城市ID: 4, 城市名称: 天津3)方式三:合并值Map<Integer, String> cityMap = cityList.stream() .collect(Collectors.toMap(City::getCityId, City::getCityName, (oldValue, newValue) -> oldValue + ", " + newValue));输出结果:城市ID: 4, 城市名称: 深圳, 天津2.4 数据分组聚合假设有一个销售记录列表,需要将其转换为Map,其中Key为销售员、Value为该销售员的总销售额,转换方法如下所示:@Getter@Setterpublic class SalesRecord { private String salesPerson; private BigDecimal amount; public SalesRecord(String salesPerson, BigDecimal amount) { this.salesPerson = salesPerson; this.amount = amount; }}List<SalesRecord> salesRecordList = Arrays.asList( new SalesRecord("张三", new BigDecimal("1000")), new SalesRecord("李四", new BigDecimal("2000")), new SalesRecord("张三", new BigDecimal("980")));Map<String, BigDecimal> salesRecordMap = salesRecordList.stream() .collect(Collectors.toMap(SalesRecord::getSalesPerson, SalesRecord::getAmount, BigDecimal::add));System.out.println(salesRecordMap);输出结果: 上面的例子是销售额累加,也可以只取最小值:Map<String, BigDecimal> salesRecordMap = salesRecordList.stream() .collect(Collectors.toMap(SalesRecord::getSalesPerson, SalesRecord::getAmount, BigDecimal::min));此时的输出结果: 或者只取最大值:Map<String, BigDecimal> salesRecordMap = salesRecordList.stream() .collect(Collectors.toMap(SalesRecord::getSalesPerson, SalesRecord::getAmount, BigDecimal::max));此时的输出结果: 2.5 指定Map实现默认情况下,Collectors.toMap是将结果收集到HashMap中,如果有需要,我们也可以指定成TreeMap或者LinkedHashMap。如果想要保持插入顺序,可以指定使用LinkedHashMap:List<City> cityList = Arrays.asList( new City(2, "上海"), new City(1, "北京"), new City(4, "深圳"), new City(3, "广州"));Map<Integer, String> cityMap = cityList.stream() .collect(Collectors.toMap(City::getCityId, City::getCityName, (existing, replacement) -> existing, LinkedHashMap::new));System.out.println(cityMap);输出结果: 如果想要按键排序,可以指定使用TreeMap:List<City> cityList = Arrays.asList( new City(2, "上海"), new City(1, "北京"), new City(4, "深圳"), new City(3, "广州"));Map<Integer, String> cityMap = cityList.stream() .collect(Collectors.toMap(City::getCityId, City::getCityName, (existing, replacement) -> existing, TreeMap::new));System.out.println(cityMap);输出结果: 3. 注意事项3.1 空异常如果valueMapper中取出的值有null值,会抛出java.lang.NullPointerException异常,如下示例:List<City> cityList = Arrays.asList( new City(1, "北京"), new City(2, "上海"), new City(3, "广州"), new City(4, "深圳"), new City(5, null));Map<Integer, String> cityMap = cityList.stream() .collect(Collectors.toMap(City::getCityId, City::getCityName));System.out.println(cityMap);运行以上代码会抛出异常,如下图所示:有两种解决方案,第一种解决方案是过滤null值:Map<Integer, String> cityMap = cityList.stream() .filter(city -> city.getCityName() != null) .collect(Collectors.toMap(City::getCityId, City::getCityName));第二种解决方案是提供默认值:Map<Integer, String> cityMap = cityList.stream() .collect(Collectors.toMap(City::getCityId, city -> Optional.ofNullable(city.getCityName()).orElse("未知")));3.2 键重复异常如果出现重复键,且没有提供mergeFunction参数,会抛出java.lang.IllegalStateException异常,如下示例:List<City> cityList = Arrays.asList( new City(1, "北京"), new City(2, "上海"), new City(3, "广州"), new City(4, "深圳"), new City(4, "天津"));Map<Integer, String> cityMap = cityList.stream() .collect(Collectors.toMap(City::getCityId, City::getCityName));System.out.println(cityMap);运行以上代码会抛出异常,如下图所示:解决方案见本篇文章2.3 键冲突处理部分。4. 总结Collectors.toMap是Stream API中提供的一个非常方便的收集器,它可以将流中的元素映射为键值对,并收集到一个Map中。它适用于一对一映射的场景,但在使用时,要注意避免java.lang.NullPointerException异常和java.lang.IllegalStateException异常。转载自https://www.cnblogs.com/zwwhnly/p/19403765
-
Java 泛型的设计有个独特之处:类型信息只存在于编译期,运行时会被彻底擦除。这种 “擦除” 机制让很多开发者困惑:为什么List<String>和List<Integer>在运行时是同一个类型?为什么不能用基本类型作为泛型参数?为什么创建泛型数组会报错?今天我们就从泛型擦除的底层原理讲起,彻底搞懂这些问题,看清泛型的 “真面目”。一、泛型擦除:Java 泛型的 “编译期幻术” 泛型是 Java 5 引入的特性,但为了兼容之前的版本(Java 5 之前没有泛型),Java 采用了类型擦除(Type Erasure) 的实现方式:编译时检查泛型类型合法性,运行时擦除所有泛型信息。也就是说,泛型只在编译期起作用,运行时 JVM 根本不知道泛型参数的存在。1. 擦除的核心过程:从泛型到原始类型泛型擦除的本质是将泛型类型替换为其原始类型(Raw Type),具体规则:若泛型参数有上限(如<T extends Number>),则擦除为该上限类型;若泛型参数无上限(如<T>),则擦除为Object;若有多个上限(如<T extends A & B>),则擦除为第一个上限类型。示例:泛型类擦除前后对比// 泛型类定义public class Box<T extends Number> { private T value; public T getValue() { return value; } public void setValue(T value) { this.value = value; }} // 擦除后(编译为字节码的实际类型)public class Box { // 去掉泛型参数<T extends Number> private Number value; // T被替换为上限Number public Number getValue() { return value; } // 返回值类型变为Number public void setValue(Number value) { this.value = value; } // 参数类型变为Number}一键获取完整项目代码java2. 为什么需要擦除?—— 兼容性妥协 Java 5 之前的代码没有泛型,大量使用原始类型(如List而非List<String>)。为了让这些旧代码能与新的泛型代码无缝交互,Java 必须保证:泛型类在运行时的类型与非泛型类兼容。例如,Java 5 之前的List和 Java 5 之后的List<String>,在运行时必须是同一个类型(都是List.class),否则旧代码无法操作新的泛型集合。擦除机制正是为了实现这种兼容性。3. 擦除后的 “类型安全” 如何保证? 擦除会移除泛型信息,那运行时的类型安全怎么保证?答案是:编译器在擦除的同时,自动添加类型检查和转型代码。// 泛型代码List<String> list = new ArrayList<>();list.add("hello");String str = list.get(0); // 擦除后(编译器生成的实际代码)List list = new ArrayList();list.add("hello"); // 编译时检查:确保添加的是StringString str = (String) list.get(0); // 自动添加转型代码一键获取完整项目代码java编译期:检查add("hello")是否符合List<String>的类型约束,若添加123会直接报错;运行期:通过自动生成的(String)转型代码,保证取出的元素类型正确(若因特殊操作导致类型不匹配,仍会抛ClassCastException)。泛型擦除原理图解二、泛型擦除带来的限制:这些操作为什么不允许? 擦除机制虽然保证了兼容性,但也给泛型带来了诸多限制。理解这些限制的根源,才能避免开发中的 “坑”。限制 1:不能用基本类型作为泛型参数 你可能注意到,List<int>会编译报错,必须用List<Integer>。这是因为:泛型擦除后会替换为 Object 或上限类型,而基本类型(int、double 等)不是 Object 的子类,无法转型。若声明List<int>,擦除后应为List<Object>,但int是基本类型,不能直接存储在Object数组中(需要装箱为 Integer);编译器为了避免这种矛盾,直接禁止基本类型作为泛型参数,强制使用包装类(Integer、Double 等)。反例(编译报错):// 错误:基本类型不能作为泛型参数List<int> intList = new ArrayList<>(); // 编译报错Map<double, boolean> map = new HashMap<>(); // 编译报错 // 正确:使用包装类List<Integer> intList = new ArrayList<>();Map<Double, Boolean> map = new HashMap<>();一键获取完整项目代码java限制 2:不能实例化泛型类型(new T()) 无法在泛型类中直接创建泛型参数的实例(new T()),因为擦除后T会被替换为Object或上限类型,编译器无法确定具体类型。反例(编译报错):public class Box<T> { public Box() { // 错误:不能实例化泛型类型T T value = new T(); // 编译报错 }}一键获取完整项目代码java原因:擦除后T变为Object,new T()会被视为new Object(),这显然不符合预期(我们想要的是T的实例,而非 Object)。解决方案:通过反射创建实例(需传入 Class 对象):public class Box<T> { private T value; // 传入Class对象,通过反射创建实例 public Box(Class<T> clazz) throws InstantiationException, IllegalAccessException { value = clazz.newInstance(); // 合法 }} // 使用Box<String> box = new Box<>(String.class); // 需显式传入Class对象一键获取完整项目代码java限制 3:不能创建泛型数组(new T[]) 无法直接创建泛型数组(new T[10]),因为擦除后数组的实际类型是Object[],会导致类型安全问题。反例(编译报错):public class ArrayBox<T> { public void createArray() { // 错误:不能创建泛型数组 T[] array = new T[10]; // 编译报错 }}一键获取完整项目代码java原因:擦除后T[]变为Object[],若将其赋值给具体类型的数组(如String[]),再存入其他类型元素,会在运行时引发隐藏的ClassCastException:// 假设允许创建T[],擦除后实际为Object[]Object[] array = new Object[10];String[] strArray = (String[]) array; // 编译不报错(危险!)strArray[0] = 123; // 运行时抛ArrayStoreException(int不能存到String数组)一键获取完整项目代码java编译器为了避免这种隐藏的风险,直接禁止创建泛型数组。解决方案:用ArrayList<T>代替泛型数组(推荐,无需处理类型问题);创建Object[]数组,使用时手动转型(需谨慎,可能引发异常):public class ArrayBox<T> { private Object[] array; public ArrayBox(int size) { array = new Object[size]; // 创建Object数组 } public T get(int index) { return (T) array[index]; // 取出时转型 } public void set(int index, T value) { array[index] = value; // 存入时自动装箱 }}一键获取完整项目代码java限制 4:不能用instanceof判断泛型类型 instanceof是运行时类型检查,而泛型类型在运行时已被擦除,因此无法用instanceof判断泛型参数。反例(编译报错):List<String> list = new ArrayList<>();// 错误:不能用instanceof判断泛型类型if (list instanceof List<String>) { // 编译报错 // ...}一键获取完整项目代码java原因:运行时List<String>和List<Integer>都是List类型,instanceof无法区分。替代方案:若需判断集合元素类型,可通过泛型类的Class参数(需手动传入):public class GenericChecker<T> { private Class<T> clazz; public GenericChecker(Class<T> clazz) { this.clazz = clazz; } // 检查集合元素是否为T类型 public boolean check(List<?> list) { for (Object obj : list) { if (!clazz.isInstance(obj)) { return false; } } return true; }} // 使用GenericChecker<String> checker = new GenericChecker<>(String.class);List<Object> list = Arrays.asList("a", "b", 123);System.out.println(checker.check(list)); // false(包含Integer)一键获取完整项目代码java限制 5:静态变量 / 方法不能引用泛型类的类型参数 泛型类的类型参数是实例级别的(每个实例可以有不同的类型参数),而静态成员是类级别的(所有实例共享),因此静态变量 / 方法不能使用泛型类的类型参数。反例(编译报错):原因:擦除后泛型类的类型参数消失,静态成员无法关联到具体的类型参数(不同实例的T可能不同)。注意:静态泛型方法是允许的,因为它有自己的泛型参数(独立于类的类型参数):public class StaticBox<T> { // 正确:静态泛型方法有自己的类型参数S public static <S> S create(S obj) { return obj; }}一键获取完整项目代码java泛型限制图解三、泛型擦除的 “后遗症”:桥接方法(Bridge Method) 擦除会导致一个隐藏问题:泛型类的方法重写可能在擦除后变得不兼容。为了解决这个问题,编译器会自动生成桥接方法(Bridge Method)。桥接方法的产生场景假设有泛型父类和子类:// 泛型父类class Parent<T> { public void setValue(T value) {}} // 子类指定泛型参数为Stringclass Child extends Parent<String> { @Override public void setValue(String value) {} // 重写父类方法}一键获取完整项目代码java 擦除后,父类的setValue(T)变为setValue(Object),而子类的setValue(String)与父类的setValue(Object)参数类型不同(不满足重写条件)。这会导致多态失效:Parent<String> parent = new Child();parent.setValue("hello"); // 期望调用子类的setValue(String)一键获取完整项目代码java为了保证多态正确,编译器会为子类自动生成桥接方法:class Child extends Parent { // 编译器生成的桥接方法(重写父类的setValue(Object)) public void setValue(Object value) { setValue((String) value); // 调用子类实际的setValue(String) } // 子类自己的方法 public void setValue(String value) {}}一键获取完整项目代码java 桥接方法的作用是:在擦除后仍保持方法重写的多态性,确保父类引用调用方法时能正确指向子类实现。桥接方法验证通过反射可以看到编译器生成的桥接方法:import java.lang.reflect.Method; public class BridgeDemo { public static void main(String[] args) { for (Method method : Child.class.getMethods()) { if (method.getName().equals("setValue")) { System.out.println("方法:" + method); System.out.println("是否桥接方法:" + method.isBridge()); } } }} // 输出结果:// 方法:public void Child.setValue(java.lang.String)// 是否桥接方法:false// 方法:public void Child.setValue(java.lang.Object)// 是否桥接方法:true一键获取完整项目代码java 可以清晰看到,子类有两个setValue方法,其中setValue(Object)是桥接方法(isBridge()返回 true)。四、总结:理解擦除,用好泛型 泛型擦除是 Java 为了兼容性做出的妥协,它既带来了便利(兼容旧代码),也带来了限制(类型信息丢失)。核心要点:擦除原理:编译时检查泛型类型,运行时将泛型参数替换为上限或 Object,同时自动添加类型检查和转型代码。核心限制:不能用基本类型作为泛型参数(擦除后无法兼容 Object);不能实例化泛型类型(new T())和创建泛型数组(new T[]);不能用instanceof判断泛型类型(运行时无类型信息);静态成员不能引用泛型类的类型参数(静态与实例的级别冲突)。桥接方法:编译器自动生成,用于解决擦除后方法重写的多态性问题。 理解泛型擦除,不仅能避免开发中的常见错误,更能让你明白 Java 泛型的设计哲学 —— 在兼容性和类型安全之间寻找平衡。虽然泛型有诸多限制,但合理使用(结合通配符、反射等)仍能写出灵活且安全的代码。记住:泛型是编译期的 “语法糖”,运行时它的 “真面目” 是原始类型。————————————————原文链接:https://blog.csdn.net/qq_40303030/article/details/152211655
-
在 JavaScript 中,将时间戳(通常为毫秒或秒的 Unix 时间戳)转换为日期格式是一个常见需求。可以通过原生 Date 对象、日期格式化方法或第三方库(如 Moment.js 或 date-fns)实现。以下是详细的中文讲解,介绍多种转换方法,包含代码示例、使用场景和注意事项。1. 时间戳简介定义:时间戳是以 1970-01-01 00:00:00 UTC(Unix 纪元)为起点的秒数或毫秒数。毫秒时间戳:如 1630454400000(毫秒,JavaScript 默认)。秒时间戳:如 1630454400(需乘以 1000 转为毫秒)。目标:将时间戳转换为可读格式,如 YYYY-MM-DD HH:mm:ss 或 2021-09-01 08:00:00。2. 转换方法方法 1:使用原生 Date 对象描述:通过 new Date(timestamp) 创建日期对象,再提取年月日等部分。适用场景:简单转换,适合不需要复杂格式化的场景。代码示例:// 毫秒时间戳const timestamp = 1630454400000; // 2021-09-01 00:00:00 UTCconst date = new Date(timestamp);// 提取日期部分const year = date.getFullYear();const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从 0 开始,需 +1const day = String(date.getDate()).padStart(2, '0');const hours = String(date.getHours()).padStart(2, '0');const minutes = String(date.getMinutes()).padStart(2, '0');const seconds = String(date.getSeconds()).padStart(2, '0');// 格式化const formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;console.log(formattedDate); // 输出: 2021-09-01 00:00:00运行项目并下载源码javascript运行说明:padStart(2, '0') 确保月份、日期等为两位数。使用 UTC 方法(如 getUTCFullYear)可避免时区影响:const utcDate = `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')}`;console.log(utcDate); // 输出: 2021-09-01运行项目并下载源码javascript运行方法 2:使用 toLocaleString()描述:Date 对象的 toLocaleString() 方法提供本地化格式,支持自定义选项。适用场景:需要本地化日期格式或简单格式化。代码示例:const timestamp = 1630454400000;const date = new Date(timestamp);// 默认本地化格式console.log(date.toLocaleString('zh-CN')); // 输出: 2021/9/1 08:00:00(中国时区)// 自定义格式const options = { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false};console.log(date.toLocaleString('zh-CN', options)); // 输出: 2021-09-01 08:00:00运行项目并下载源码javascript运行说明:toLocaleString 根据地区(zh-CN、en-US)调整格式。options 参数支持灵活定制。注意:格式因浏览器和地区不同而异。方法 3:自定义格式化函数描述:编写函数根据指定格式(如 YYYY-MM-DD)转换时间戳。适用场景:需要统一、可控的日期格式。代码示例:function formatDate(timestamp, format = 'YYYY-MM-DD HH:mm:ss') { const date = new Date(timestamp); const map = { 'YYYY': date.getFullYear(), 'MM': String(date.getMonth() + 1).padStart(2, '0'), 'DD': String(date.getDate()).padStart(2, '0'), 'HH': String(date.getHours()).padStart(2, '0'), 'mm': String(date.getMinutes()).padStart(2, '0'), 'ss': String(date.getSeconds()).padStart(2, '0') }; return format.replace(/YYYY|MM|DD|HH|mm|ss/g, match => map[match]);}const timestamp = 1630454400000;console.log(formatDate(timestamp)); // 输出: 2021-09-01 00:00:00console.log(formatDate(timestamp, 'YYYY/MM/DD')); // 输出: 2021/09/01运行项目并下载源码javascript运行说明:支持自定义格式,灵活性高。可扩展支持更多格式(如 YYYY年MM月DD日)。方法 4:使用 Moment.js 库描述:Moment.js 是一个强大的日期处理库,支持丰富的格式化选项。适用场景:复杂日期操作或需要兼容旧项目。代码示例:// HTML: <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script>const timestamp = 1630454400000;const formatted = moment(timestamp).format('YYYY-MM-DD HH:mm:ss');console.log(formatted); // 输出: 2021-09-01 00:00:00// 本地化moment.locale('zh-cn');console.log(moment(timestamp).format('LLL')); // 输出: 2021年9月1日 08:00运行项目并下载源码javascript运行说明:需要引入 Moment.js(CDN 或 NPM:npm install moment)。支持多种格式和本地化,但库体积较大(约 70KB 压缩版)。注意:Moment.js 已进入维护模式,推荐新项目使用 date-fns。方法 5:使用 date-fns 库描述:date-fns 是现代、轻量的日期处理库,模块化设计。适用场景:新项目,需轻量且现代化的日期处理。代码示例:// NPM: npm install date-fnsimport { format } from 'date-fns';const timestamp = 1630454400000;const formatted = format(new Date(timestamp), 'yyyy-MM-dd HH:mm:ss');console.log(formatted); // 输出: 2021-09-01 00:00:00运行项目并下载源码javascript运行说明:按需导入,体积小(仅导入所需函数)。支持丰富的格式化选项,类似 Moment.js 但更轻量。方法 6:使用 jQuery(结合 DOM)描述:结合 jQuery 从 DOM 获取时间戳并转换。适用场景:项目已使用 jQuery,需处理用户输入的时间戳。代码示例:// HTML: <input type="text" id="timestamp" value="1630454400000"><button onclick="format()">转换</button>$(document).ready(function() { window.format = function() { const timestamp = $('#timestamp').val(); const date = new Date(Number(timestamp)); const formatted = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; console.log(formatted); // 输出: 2021-09-01 };});运行项目并下载源码javascript运行说明:需引入 jQuery:<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>运行项目并下载源码html13. 综合示例以下是一个完整示例,展示多种转换方法:<!DOCTYPE html><html><head> <title>时间戳转换</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/date-fns@2.30.0/dist/date-fns.min.js"></script> <style> body { font-family: Arial; padding: 20px; } input, button { margin: 10px; padding: 8px; } </style></head><body> <input type="text" id="timestamp" value="1630454400000" placeholder="输入时间戳"> <button onclick="convert()">转换</button> <div id="output"></div> <script> function formatDate(timestamp, format = 'YYYY-MM-DD HH:mm:ss') { const date = new Date(timestamp); const map = { 'YYYY': date.getFullYear(), 'MM': String(date.getMonth() + 1).padStart(2, '0'), 'DD': String(date.getDate()).padStart(2, '0'), 'HH': String(date.getHours()).padStart(2, '0'), 'mm': String(date.getMinutes()).padStart(2, '0'), 'ss': String(date.getSeconds()).padStart(2, '0') }; return format.replace(/YYYY|MM|DD|HH|mm|ss/g, match => map[match]); } function convert() { const timestamp = Number(document.getElementById('timestamp').value); if (isNaN(timestamp)) { alert('请输入有效时间戳!'); return; } const date = new Date(timestamp); const output = ` <p>原生 Date: ${formatDate(timestamp)}</p> <p>toLocaleString: ${date.toLocaleString('zh-CN')}</p> <p>Moment.js: ${moment(timestamp).format('YYYY-MM-DD HH:mm:ss')}</p> <p>date-fns: ${format(new Date(timestamp), 'yyyy-MM-dd HH:mm:ss')}</p> `; document.getElementById('output').innerHTML = output; } </script></body></html>运行项目并下载源码html4. 方法对比方法 依赖 优点 缺点原生 Date 无 无依赖,简单实现 需手动格式化,代码稍长toLocaleString() 无 本地化支持,灵活选项 格式因浏览器/地区不同自定义格式化 无 完全控制格式,灵活 需编写额外代码Moment.js Moment.js 功能强大,易用,支持本地化 体积大,维护模式date-fns date-fns 轻量,模块化,现代 需额外引入库jQuery jQuery 适合 jQuery 项目,简洁 DOM 操作 需引入 jQuery,增加依赖5. 注意事项时间戳单位:JavaScript Date 接受毫秒时间戳,秒时间戳需乘以 1000:const seconds = 1630454400;const date = new Date(seconds * 1000);运行项目并下载源码javascript运行12时区处理:默认使用本地时区,需用 UTC 方法(如 getUTCFullYear)处理 UTC 时间。库如 Moment.js/date-fns 支持时区插件。输入验证:检查时间戳是否有效:if (isNaN(timestamp) || timestamp < 0) { throw new Error('无效时间戳');}运行项目并下载源码javascript运行性能:原生方法最轻量,适合简单场景。Moment.js 体积大,date-fns 更适合新项目。浏览器兼容性:Date 和 toLocaleString 广泛支持。padStart 是 ES2017,IE 不支持,需 polyfill。安全性:用户输入时间戳需验证,防止异常值或恶意输入。6. 总结首选方法:原生 Date + 自定义格式化,简单无依赖。复杂场景:使用 date-fns(轻量现代)或 Moment.js(功能全面)。jQuery 项目:结合 jQuery 处理 DOM 输入。选择依据:无依赖:原生 Date 或 toLocaleString。复杂格式:自定义函数或 date-fns。本地化:toLocaleString 或 Moment.js。测试:验证不同时间戳(毫秒/秒)、时区和格式————————————————原文链接:https://blog.csdn.net/m0_57545130/article/details/152307313
-
在 JavaScript 中,将时间戳(通常为毫秒或秒的 Unix 时间戳)转换为日期格式是一个常见需求。可以通过原生 Date 对象、日期格式化方法或第三方库(如 Moment.js 或 date-fns)实现。以下是详细的中文讲解,介绍多种转换方法,包含代码示例、使用场景和注意事项。1. 时间戳简介定义:时间戳是以 1970-01-01 00:00:00 UTC(Unix 纪元)为起点的秒数或毫秒数。毫秒时间戳:如 1630454400000(毫秒,JavaScript 默认)。秒时间戳:如 1630454400(需乘以 1000 转为毫秒)。目标:将时间戳转换为可读格式,如 YYYY-MM-DD HH:mm:ss 或 2021-09-01 08:00:00。2. 转换方法方法 1:使用原生 Date 对象描述:通过 new Date(timestamp) 创建日期对象,再提取年月日等部分。适用场景:简单转换,适合不需要复杂格式化的场景。代码示例:// 毫秒时间戳const timestamp = 1630454400000; // 2021-09-01 00:00:00 UTCconst date = new Date(timestamp);// 提取日期部分const year = date.getFullYear();const month = String(date.getMonth() + 1).padStart(2, '0'); // 月份从 0 开始,需 +1const day = String(date.getDate()).padStart(2, '0');const hours = String(date.getHours()).padStart(2, '0');const minutes = String(date.getMinutes()).padStart(2, '0');const seconds = String(date.getSeconds()).padStart(2, '0');// 格式化const formattedDate = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;console.log(formattedDate); // 输出: 2021-09-01 00:00:00运行项目并下载源码javascript运行说明:padStart(2, '0') 确保月份、日期等为两位数。使用 UTC 方法(如 getUTCFullYear)可避免时区影响:const utcDate = `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')}`;console.log(utcDate); // 输出: 2021-09-01运行项目并下载源码javascript运行方法 2:使用 toLocaleString()描述:Date 对象的 toLocaleString() 方法提供本地化格式,支持自定义选项。适用场景:需要本地化日期格式或简单格式化。代码示例:const timestamp = 1630454400000;const date = new Date(timestamp);// 默认本地化格式console.log(date.toLocaleString('zh-CN')); // 输出: 2021/9/1 08:00:00(中国时区)// 自定义格式const options = { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false};console.log(date.toLocaleString('zh-CN', options)); // 输出: 2021-09-01 08:00:00运行项目并下载源码javascript运行说明:toLocaleString 根据地区(zh-CN、en-US)调整格式。options 参数支持灵活定制。注意:格式因浏览器和地区不同而异。方法 3:自定义格式化函数描述:编写函数根据指定格式(如 YYYY-MM-DD)转换时间戳。适用场景:需要统一、可控的日期格式。代码示例:function formatDate(timestamp, format = 'YYYY-MM-DD HH:mm:ss') { const date = new Date(timestamp); const map = { 'YYYY': date.getFullYear(), 'MM': String(date.getMonth() + 1).padStart(2, '0'), 'DD': String(date.getDate()).padStart(2, '0'), 'HH': String(date.getHours()).padStart(2, '0'), 'mm': String(date.getMinutes()).padStart(2, '0'), 'ss': String(date.getSeconds()).padStart(2, '0') }; return format.replace(/YYYY|MM|DD|HH|mm|ss/g, match => map[match]);}const timestamp = 1630454400000;console.log(formatDate(timestamp)); // 输出: 2021-09-01 00:00:00console.log(formatDate(timestamp, 'YYYY/MM/DD')); // 输出: 2021/09/01运行项目并下载源码javascript运行说明:支持自定义格式,灵活性高。可扩展支持更多格式(如 YYYY年MM月DD日)。方法 4:使用 Moment.js 库描述:Moment.js 是一个强大的日期处理库,支持丰富的格式化选项。适用场景:复杂日期操作或需要兼容旧项目。代码示例:// HTML: <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script>const timestamp = 1630454400000;const formatted = moment(timestamp).format('YYYY-MM-DD HH:mm:ss');console.log(formatted); // 输出: 2021-09-01 00:00:00// 本地化moment.locale('zh-cn');console.log(moment(timestamp).format('LLL')); // 输出: 2021年9月1日 08:00运行项目并下载源码javascript运行说明:需要引入 Moment.js(CDN 或 NPM:npm install moment)。支持多种格式和本地化,但库体积较大(约 70KB 压缩版)。注意:Moment.js 已进入维护模式,推荐新项目使用 date-fns。方法 5:使用 date-fns 库描述:date-fns 是现代、轻量的日期处理库,模块化设计。适用场景:新项目,需轻量且现代化的日期处理。代码示例:// NPM: npm install date-fnsimport { format } from 'date-fns';const timestamp = 1630454400000;const formatted = format(new Date(timestamp), 'yyyy-MM-dd HH:mm:ss');console.log(formatted); // 输出: 2021-09-01 00:00:00运行项目并下载源码javascript运行说明:按需导入,体积小(仅导入所需函数)。支持丰富的格式化选项,类似 Moment.js 但更轻量。方法 6:使用 jQuery(结合 DOM)描述:结合 jQuery 从 DOM 获取时间戳并转换。适用场景:项目已使用 jQuery,需处理用户输入的时间戳。代码示例:// HTML: <input type="text" id="timestamp" value="1630454400000"><button onclick="format()">转换</button>$(document).ready(function() { window.format = function() { const timestamp = $('#timestamp').val(); const date = new Date(Number(timestamp)); const formatted = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; console.log(formatted); // 输出: 2021-09-01 };});运行项目并下载源码javascript运行说明:需引入 jQuery:<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>运行项目并下载源码html13. 综合示例以下是一个完整示例,展示多种转换方法:<!DOCTYPE html><html><head> <title>时间戳转换</title> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.4/moment.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/date-fns@2.30.0/dist/date-fns.min.js"></script> <style> body { font-family: Arial; padding: 20px; } input, button { margin: 10px; padding: 8px; } </style></head><body> <input type="text" id="timestamp" value="1630454400000" placeholder="输入时间戳"> <button onclick="convert()">转换</button> <div id="output"></div> <script> function formatDate(timestamp, format = 'YYYY-MM-DD HH:mm:ss') { const date = new Date(timestamp); const map = { 'YYYY': date.getFullYear(), 'MM': String(date.getMonth() + 1).padStart(2, '0'), 'DD': String(date.getDate()).padStart(2, '0'), 'HH': String(date.getHours()).padStart(2, '0'), 'mm': String(date.getMinutes()).padStart(2, '0'), 'ss': String(date.getSeconds()).padStart(2, '0') }; return format.replace(/YYYY|MM|DD|HH|mm|ss/g, match => map[match]); } function convert() { const timestamp = Number(document.getElementById('timestamp').value); if (isNaN(timestamp)) { alert('请输入有效时间戳!'); return; } const date = new Date(timestamp); const output = ` <p>原生 Date: ${formatDate(timestamp)}</p> <p>toLocaleString: ${date.toLocaleString('zh-CN')}</p> <p>Moment.js: ${moment(timestamp).format('YYYY-MM-DD HH:mm:ss')}</p> <p>date-fns: ${format(new Date(timestamp), 'yyyy-MM-dd HH:mm:ss')}</p> `; document.getElementById('output').innerHTML = output; } </script></body></html>运行项目并下载源码html4. 方法对比方法 依赖 优点 缺点原生 Date 无 无依赖,简单实现 需手动格式化,代码稍长toLocaleString() 无 本地化支持,灵活选项 格式因浏览器/地区不同自定义格式化 无 完全控制格式,灵活 需编写额外代码Moment.js Moment.js 功能强大,易用,支持本地化 体积大,维护模式date-fns date-fns 轻量,模块化,现代 需额外引入库jQuery jQuery 适合 jQuery 项目,简洁 DOM 操作 需引入 jQuery,增加依赖5. 注意事项时间戳单位:JavaScript Date 接受毫秒时间戳,秒时间戳需乘以 1000:const seconds = 1630454400;const date = new Date(seconds * 1000);运行项目并下载源码javascript运行12时区处理:默认使用本地时区,需用 UTC 方法(如 getUTCFullYear)处理 UTC 时间。库如 Moment.js/date-fns 支持时区插件。输入验证:检查时间戳是否有效:if (isNaN(timestamp) || timestamp < 0) { throw new Error('无效时间戳');}运行项目并下载源码javascript运行性能:原生方法最轻量,适合简单场景。Moment.js 体积大,date-fns 更适合新项目。浏览器兼容性:Date 和 toLocaleString 广泛支持。padStart 是 ES2017,IE 不支持,需 polyfill。安全性:用户输入时间戳需验证,防止异常值或恶意输入。6. 总结首选方法:原生 Date + 自定义格式化,简单无依赖。复杂场景:使用 date-fns(轻量现代)或 Moment.js(功能全面)。jQuery 项目:结合 jQuery 处理 DOM 输入。选择依据:无依赖:原生 Date 或 toLocaleString。复杂格式:自定义函数或 date-fns。本地化:toLocaleString 或 Moment.js。测试:验证不同时间戳(毫秒/秒)、时区和格式————————————————原文链接:https://blog.csdn.net/m0_57545130/article/details/152307313
上滑加载中
推荐直播
-
华为云码道-AI时代应用开发利器2026/03/18 周三 19:00-20:00
童得力,华为云开发者生态运营总监/姚圣伟,华为云HCDE开发者专家
本次直播由华为专家带你实战应用开发,看华为云码道(CodeArts)代码智能体如何在AI时代让你的创意应用快速落地。更有华为云HCDE开发者专家带你用码道玩转JiuwenClaw,让小艺成为你的AI助理。
回顾中 -
Skill 构建 × 智能创作:基于华为云码道的 AI 内容生产提效方案2026/03/25 周三 19:00-20:00
余伟,华为云软件研发工程师/万邵业(万少),华为云HCDE开发者专家
本次直播带来两大实战:华为云码道 Skill-Creator 手把手搭建专属知识库 Skill;如何用码道提效 OpenClaw 小说文本,打造从大纲到成稿的 AI 原创小说全链路。技术干货 + OPC创作思路,一次讲透!
回顾中 -
码道新技能,AI 新生产力——从自动视频生成到开源项目解析2026/04/08 周三 19:00-21:00
童得力-华为云开发者生态运营总监/何文强-无人机企业AI提效负责人
本次华为云码道 Skill 实战活动,聚焦两大 AI 开发场景:通过实战教学,带你打造 AI 编程自动生成视频 Skill,并实现对 GitHub 热门开源项目的智能知识抽取,手把手掌握 Skill 开发全流程,用 AI 提升研发效率与内容生产力。
回顾中
热门标签