戴老师的博客 - Java 基础
type: channel
link: https://tushu.info/tag/Java-%E5%9F%BA%E7%A1%80/
items: IBM Rational 助您轻松完成自动化功能测试
items: Java的ClassLoader与Package机制
items: Java8 Stream
items: Java深度历险（二）——Java类的加载、链接和初始化
items: Java编码易疏忽的十个问题
items: java程序员必知的 8大排序
items: Java编程：常见问题汇总
items: 探索Java性能优化：技巧与实例全解析-深入篇
items: Java深度历险（一）——Java字节代码的操纵
items: 在Java中使用6种方法进行http请求

IBM Rational 助您轻松完成自动化功能测试
type: item
date: 2026-07-07T08:36:56+00:00
description: IBM Rational 助您轻松完成自动化功能测试1. 解析自动化测试的困惑在软件工程领域，如果说有一种工作让人在痛苦中感受它的价值、在无休止的加班中体会它的苦涩、在技术的进步中体验它的快乐，那它一定是软件测试。计算机技术发展到今天，自动化测试工具的广泛应用使人们重新认识到测试的源动力：最优的质量成本。软件开发过程中的测试及各种质量保证活动，无疑是在追求软件质量成本和收益间的最佳平衡点。谈到自动化测试，首先我们要明确什么情况下需要自动化。自动化测试的目的是通过自动执行测试脚本，使测试人员在更短的时间内完成更多的测试任务，并提供更高频率的执行能力，从而有效降低测试成本、提高测试效率。从软件测试的成本来看，使用测试工具进行软件自动化测试的成本可以用以下公式表示：自动化测试的成本 ＝ 测试工具成本 ＋ 测试脚本的创建成本 ＋ 测试脚本的维护成本既然自动化测试的目的在于降低测试成本、提高测试效率，因此，测试团队在选择自动化测试工具时，必须在提高测试效率的同时，尽量做到自动化测试的总体成本小于手工测试成本。因此，自动化测试工具的脚本自动化创建能力和可维护性，就成了衡量自动化测试工具的重要因素。在实际的测试工作中，一般说来，我们选择自动化的功能测试工具无外乎要解决以下三个问题：自动化的功能回归测试大批量数据驱动的软件测试整个软件测试生命周期的管理在选择自动化测试工具解决这些问题的过程中，人们主要关心的问题是：使用自动化测试工具创建测试脚本的能力、工具的易用性、测试脚本的编程和扩展能力、测试脚本的参数化技术，以及作为软件开发重要环节的测试工作和其它软件生命周期管理工具的集成能力。因此，摆脱自动化测试困惑的根本途径，就是理解自动化测试的本质，明确自己的自动化测试需求，选择合适的自动化测试工具，帮助测试团队提高效率、降低成本，最终实现软件开发过程的全过程质量保证。2. IBM 自动化功能测试解决方案：Rational Functional TesterIBM Rational Functional Tester（简称 RFT）是一款先进的、自动化的功能和回归测试工具，它适用于测试人员和 GUI 开发人员。使用它，测试新手可以简化复杂的测试任务，很快上手；测试专家能够通过选择工业标准化的脚本语言，实现各种高级定制功能。通过 IBM 的专利技术，例如基于 Wizard 的智能数据驱动软件测试技术、提高测试脚本重用的 ScriptAssurance 技术等等，大大提高了脚本的易用性和可维护能力。同时，它第一次为 Java 和 Web 测试人员提供了和开发人员同样的操作平台（Eclipse），并通过提供与 IBM Rational 整个测试生命周期软件的完美集成，真正实现了一个平台统一整个软件开发团队的能力。3. 使用 IBM RFT 轻松完成自动化功能测试3.1 基于与开发人员同一开发平台的功能测试IBM RFT 的最大特色就是基于开发人员的同一开发平台（Eclipse），为 Java 和 Web 测试人员提供了自动化测试能力。如图 1 所示，使用 RFT 进行软件测试时，我们只要在开发人员工作的 Eclipse 环境中打开 Functional Test 透视图，就会马上拥有专业的自动化功能测试工具所拥有的全部功能。图 1：IBM Rational Functional Test 工作界面在 RFT 中实现测试脚本的过程和大部分的自动化测试工具一样，是基于录制的脚本生成技术。当我们完成测试用例后，只要在功能测试工具条上选择测试脚本录制按钮，就会启动测试用例的脚本实现过程。如图 2 所示，在脚本录制的“选择脚本资产”对话框中，用户可以选择预定义好的公用测试对象图和公用测试数据池，也可以选择在脚本录制过程中生成私有测试对象图和数据池。测试对象图是 IBM 用来解决测试脚本在不同被测版本间成功回放的关键技术，它为测试脚本的重用提供了重要保证；而测试数据池是 IBM 用来实现数据驱动的自动化功能测试的重要手段，使用智能化的数据驱动测试向导，测试脚本的参数化几乎变得易如反掌。图 2：“选择脚本资产”对话框如图 3 所示，在功能测试的录制监视窗口，测试员可以根据提示启动被测应用系统，执行测试用例中规定的测试步骤，实现测试脚本的录制。在测试脚本录制过程中，测试员可以根据需要插入验证点和数据驱动的测试脚本。验证点是在指令中比较实际结果和预期结果的测试点，自动化功能测试工具正是通过它实现对被测系统功能需求的验证。图 3：测试脚本录制窗口完成脚本录制过程以后，RFT 会自动生成用工业标准语言 Java 描述的测试脚本，如下所示：import resources.ThirdwithDatapoolHelper; import com.rational.test.ft.…

Java的ClassLoader与Package机制
type: item
date: 2026-07-07T08:36:37+00:00
description: 实验验证为了深入探究 Java 的 ClassLoader 机制，我们先进行以下实验。尝试在 java.lang 包下定义一个类，并访问 String 类的包权限构造函数：package java.lang; public class Test { public static void main(String[] args.html) { char[] c = "1234567890".toCharArray(); // 尝试访问 String 的 package-private 构造函数 String s = new String(0, 10, c.html); } }String 类拥有一个包权限（Package-private）的构造函数 String(int offset, int length, char[] array.html)。按照 Java 默认的访问控制规则，由于 Test 类也属于 java.lang 包，理论上应该可以访问该构造函数。编译可以通过！然而，执行时结果如下：Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang at java.lang.ClassLoader.defineClass(Unknown Source.html) at java.security.SecureClassLoader.defineClass(Unknown Source.html) at java.net.URLClassLoader.defineClass(Unknown Source.html) at java.net.URLClassLoader.access$100(Unknown Source.html) at java.net.URLClassLoader$1.run(Unknown Source.html) at java.security.AccessController.doPrivileged(Native Method.html) at java.net.URLClassLoader.findClass(Unknown Source.html) at java.lang.ClassLoader.loadClass(Unknown Source.html) at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source.html) at java.lang.ClassLoader.loadClass(Unknown Source.html) at java.lang.ClassLoader.loadClassInternal(Unknown Source.html)为何会出现这种 SecurityException？要弄清原因，必须深入理解 ClassLoader 的机制。ClassLoader 机制与双亲委派Java 的 ClassLoader 负责动态装载 class 文件。对于一个 class，ClassLoader 只会装载一次。JVM 中常见的 ClassLoader 主要分为以下 4 种，优先级依次从高到低：启动类装载器（Bootstrap ClassLoader）标准扩展类装载器（Extension ClassLoader）类路径装载器（App/System ClassLoader）网络类装载器（Network ClassLoader）这些 ClassLoader 之间使用所谓的“双亲委派模型”（Parent Delegation Model）。确切地说，如果一个网络类装载器被请求装载一个类（例如 java.lang.Integer），它会首先把请求发送给上一级的类路径装载器。如果上级返回已装载，则网络类装载器不会再次装载；只有当上一级返回未装载时，它才会尝试自行装载。类似的，类路径装载器收到请求后（无论是直接请求还是下级上传的请求），也会先把请求发送到上一级的标准扩展类装载器。这样一层一层上传，直到启动类装载器。由于启动类装载器优先级最高，如果它按照自己的方式找到了 java.lang.Integer，则下层的 ClassLoader 都不能再装载该类。这意味着，尽管你自己写了一个 java.lang.Integer 试图取代核心库的类，这是不可能的。因为核心类已由启动类装载器加载，自己写的类根本无法被下层的 ClassLoader 装载。Package 权限与 ClassLoader 身份再说说 Package 权限。Java 语言规定，在同一个包中的 class，如果没有修饰符，默认为 Pack…

Java8 Stream
type: item
date: 2026-07-07T08:36:32+00:00
description: Java 中的 Stream（流）可以定义为来自源的一系列元素，这些元素支持对它们的聚合操作。这里的“源”是指向流提供数据的 Collection（集合）或 Array（数组）。Stream 保持数据在源中的顺序。聚合操作或批量操作使我们能够轻松、清晰地表达对流元素的共同操作。在继续之前，了解 Java 8 Streams 的设计方式很重要：大多数流操作仅返回流本身。这有助于我们创建流操作链，称为管道化（Pipelining）。在这篇文章中，我将多次使用该术语，请牢记。1. Java 流与集合我们所有人都已经在 YouTube 或其他类似网站上观看过在线视频。当您开始观看视频时，文件的一小部分首先会加载到计算机中并开始播放，无需在下载完整视频之前等待。这称为流式传输。我将尝试将此概念与集合相关联，并区分 Streams 与 Collections。在基本级别上，集合和流之间的差异与计算时机有关：Collection 是一个内存内数据结构，它包含所有当前存在的数据结构。集合中的每个元素在添加到集合之前都必须已被计算出具体的值。Stream 是概念上的固定数据结构，其中的元素按需计算。这带来了显著的编程优势。其核心思想是，用户仅从 Stream 中提取他们需要的值，并且这些元素仅在需要时对用户无形地生成。这是生产者 - 消费者关系的一种形式。在 Java 中，java.util.Stream 表示可以在其上执行一个或多个操作的流。流操作分为中间操作或终端操作。虽然终端操作返回特定类型的结果，但中间操作返回流本身，从而可以在链中调用多个方法。流是在源上创建的，例如列表或集合（不支持映射）之类的 java.util.Collection。流操作可以顺序执行，也可以并行执行。基于以上几点，如果我们列出 Stream 的各种特征，它们将如下所示：不是数据结构专为 Lambda 表达式设计不支持索引访问可以轻松输出为数组或列表支持延迟访问（Lazy Evaluation）可并行化2. 创建流的不同方法以下是从集合构建流的最流行的几种方法。2.1. Stream.of(val1, val2, val3…..html)public class StreamBuilders { public static void main(String[] args.html) { Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9.html); stream.forEach(p -> System.out.println(p.html)); } }2.2. Stream.of(arrayOfElements.html)public class StreamBuilders { public static void main(String[] args.html) { Stream<Integer> stream = Stream.of(new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9}.html); stream.forEach(p -> System.out.println(p.html)); } }2.3. List.stream()public class StreamBuilders { public static void main(String[] args.html) { List<Integer> list = new ArrayList<Integer>(); for(int i = 1; i < 10; i++.html){ list.add(i.html); } Stream<Integer> stream = list.stream(); stream.forEach(p -> System.out.println(p.html)); } }2.4. Stream.generate() 或 Stream.iterate()public class StreamBuilders { public static void main(String[] args.html) { // 注意：generate 生成无限流，实际使用通常需配合 limit() Stream<Date> stream = Stream.generate((.html) -> { return new Date(); }) .limit(5.html); stream.forEach(p -> System.out.println(p.html)); } }2.5. String chars 或 String tokenspublic class StreamBuild…

Java深度历险（二）——Java类的加载、链接和初始化
type: item
date: 2026-07-07T08:36:10+00:00
description: 在 上一篇文章 中，我们介绍了 Java 字节码的操纵，其中提到了利用 Java 类加载器来加载修改后的字节码并在 JVM 上执行。本文接着上一篇的话题，深入讨论 Java 类的加载、链接和初始化。Java 字节码的表现形式为字节数组（byte[]），而 Java 类在 JVM 中的表现形式则是 java.lang.Class 类的对象。一个 Java 类从字节码到能够在 JVM 中被使用，需要经过加载、链接和初始化这三个步骤。在这三个步骤中，对开发人员直接可见的是 Java 类的加载，通过使用 Java 类加载器（Class Loader）可以在运行时刻动态地加载一个 Java 类；而链接和初始化则是在使用 Java 类之前会发生的动作。本文将详细介绍 Java 类的加载、链接和初始化的过程。Java 类的加载Java 类的加载是由类加载器来完成的。一般来说，类加载器分成两类：启动类加载器（Bootstrap Class Loader）和用户自定义类加载器（User-defined Class Loader）。两者的区别在于：启动类加载器是由 JVM 的原生代码实现的，而用户自定义的类加载器都继承自 Java 中的 java.lang.ClassLoader 类。在用户自定义类加载器的部分，一般 JVM 都会提供一些基本实现。应用程序的开发人员也可以根据需要编写自己的类加载器。JVM 中最常使用的是系统类加载器（System Class Loader），它用来启动 Java 应用程序的加载。通过 java.lang.ClassLoader 的 getSystemClassLoader() 方法可以获取到该类加载器对象。类加载器需要完成的最终功能是定义一个 Java 类，即把 Java 字节代码转换成 JVM 中的 java.lang.Class 类的对象。但是类加载的过程并不是这么简单。Java 类加载器有两个比较重要的特征：层次组织结构和代理模式。层次组织结构：指的是每个类加载器都有一个父类加载器，通过 getParent() 方法可以获取到。类加载器通过这种父 - 子的方式组织在一起，形成树状层次结构。代理模式：指的是一个类加载器既可以自己完成 Java 类的定义工作，也可以代理给其它的类加载器来完成。由于代理模式的存在，启动一个类的加载过程的类加载器和最终定义这个类的类加载器可能并不是一个。前者称为初始类加载器，而后者称为定义类加载器。两者的关联在于：一个 Java 类的定义类加载器是该类所导入的其它 Java 类的初始类加载器。比如类 A 通过 import 导入了类 B，那么由类 A 的定义类加载器负责启动类 B 的加载过程。一般的类加载器在尝试自己去加载某个 Java 类之前，会首先代理给其父类加载器。当父类加载器找不到的时候，才会尝试自己加载。这个逻辑是封装在 java.lang.ClassLoader 类的 loadClass() 方法中的。一般来说，父类优先的策略就足够好了。在某些情况下，可能需要采取相反的策略，即先尝试自己加载，找不到的时候再代理给父类加载器。这种做法在 Java 的 Web 容器中比较常见，也是 Servlet 规范 推荐的做法。比如，Apache Tomcat 为每个 Web 应用都提供一个独立的类加载器，使用的就是自己优先加载的策略。IBM WebSphere Application Server 则允许 Web 应用选择类加载器使用的策略。类加载器的一个重要用途是在 JVM 中为相同名称的 Java 类创建隔离空间。在 JVM 中，判断两个类是否相同，不仅是根据该类的 二进制名称，还需要根据两个类的定义类加载器。只有两者完全一样，才认为两个类的是相同的。因此，即便是同样的 Java 字节代码，被两个不同的类加载器定义之后，所得到的 Java 类也是不同的。如果试图在两个类的对象之间进行赋值操作，会抛出 java.lang.ClassCastException。这个特性为同样名称的 Java 类在 JVM 中共存创造了条件。在实际的应用中，可能会要求同一名称的 Java 类的不同版本在 JVM 中可以同时存在。通过类加载器就可以满足这种需求。这种技术在 OSGi 中得到了广泛的应用。Java 类的链接Java 类的链接指的是将 Java 类的二进制代码合并到 JVM 的运行状态之中的过程。在链接之前，这个类必须被成功加载。类的链接包括验证、准备和解析等几个步骤。验证（Verification）：用来确保 Java 类的二进制表示在结构上是完全正确的。如果验证过程出现错误的话，会抛出 java.lang.VerifyError 错误。准备（Preparation）：则是创建 Java …

Java编码易疏忽的十个问题
type: item
date: 2026-07-07T08:35:43+00:00
description: 在 Java 编码实践中，开发者容易犯一些错误，也容易疏忽某些细节。笔者对日常编码中遇到的经典情形进行了归纳整理，形成以下十个常见问题，以供共同探讨与避坑。1. 纠结的同名现象很多类的命名相同（例如：常见于异常、常量、日志等类），导致在 import 时容易张冠李戴。这种错误有时候很隐蔽，因为往往同名的类功能也类似，IDE 可能不会提示警告（Warning）。解决写完代码时，扫视一下 import 部分，检查有没有不熟悉的类。替换成正确导入后，要注意注释是否也作了相应修改。启示命名尽量避开重复名，特别要避开与 JDK 中的类重名，否则容易导入错误。同时，存在大量重名类时，查找也需要更多的辨别时间。2. 想当然的 API现象有时候调用 API 时，会想当然地通过名字直接自信满满地调用，导致出现一些令人惊讶的错误。示例一：flag 是 true 吗？boolean flag = Boolean.getBoolean("true".html);结果可能老是 false。因为 Boolean.getBoolean 读取的是系统属性（System Property），而非字符串解析。示例二：这是去年的今天吗（今年是 2012 年）？Calendar calendar = GregorianCalendar.getInstance(); calendar.roll(Calendar.DAY_OF_YEAR, -365.html);结果还是 2012 年。roll 方法只改变指定字段，不影响更大字段（如年份）。下面的才是去年：calendar.add(Calendar.DAY_OF_YEAR, -365.html); 解决办法问自己几个问题：这个方法我很熟悉吗？有没有类似的 API？区别是什么？就示例一而言，需要区别如下方法：Boolean.valueOf(b.html)Boolean.parseBoolean(b.html)Boolean.getBoolean(b.html)启示名字起得更详细点，注释写得更清楚点。不要不经了解、测试就想当然地使用一些 API。如果时间有限，用自己最为熟悉的 API。3. 有时候溢出并不难现象有时候溢出并不难发现，虽然不常复现。示例一：long x = Integer.MAX_VALUE + 1; System.out.println(x.html);x 是多少？竟然是 -2147483648。明明加上 1 之后还在 long 的范围，但因为 Integer.MAX_VALUE 是 int，运算先在 int 范围内进行，导致溢出后再赋值给 long。类似的经常出现在时间计算或多数字连乘中：数字 1 × 数字 2 × 数字 3… 示例二：在检查是否为正数的参数校验中，为了避免重载，选用参数 number，于是下面代码结果小于 0，也是因为溢出导致：Number i = Long.MAX_VALUE; System.out.println(i.intValue(.html) > 0);解决让第一个操作数是 long 型，例如加上 L 或者 l（不建议小写字母 l，因为和数字 1 太相似了）；不确定时，还是使用重载吧。即使使用 doubleValue()，当参数是 BigDecimal 时，也可能无法完全解决问题。启示对数字运用要保持敏感：涉及数字计算就要考虑溢出；涉及除法就要考虑被除数是 0；实在容纳不下了可以考虑 BigDecimal 之类。4. 日志跑哪了？现象有时候觉得 log 都打了，怎么找不到？示例一：没有 Stack Trace！} catch (Exception ex.html) { log.error(ex.html); }示例二：找不到 log！} catch (ConfigurationException e.html) { e.printStackTrace(); }解决替换成 log.error(ex.getMessage(.html), ex);，确保异常堆栈被记录；换成普通的 log4j 吧，而不是 System.out。启示API 定义应该避免让人犯错，如果多加个重载的 log.error(Exception.html) 自然没有错误发生；在产品代码中，使用的一些方法要考虑是否有效，使用 e.printStackTrace() 要想下终端（Console）在哪。5. 遗忘的 volatile现象在 DCL（Double-Checked Locking，双重检查锁定）模式中，总是忘记加一个 volatile。private static CacheImpl instance; // lose volatile public static CacheImpl getInstance(…

java程序员必知的 8大排序
type: item
date: 2026-07-07T08:35:21+00:00
description: 本文整理了八大排序算法的基本思想、实例图解及 Java 实现代码，供学习参考。8 种排序算法之间的关系如下图所示：1. 直接插入排序（Insertion Sort）1.1 基本思想在要排序的一组数中，假设前面 (n-1.html) [n>=2] 个数已经是排好顺序的，现在要把第 n 个数插到前面的有序数中，使得这 n 个数也是排好顺序的。如此反复循环，直到全部排好顺序。1.2 实例1.3 代码实现package com.jsdiff; public class insertSort { public insertSort() { int a[] = {49, 38, 65, 97, 76, 13, 27, 49, 78, 34, 12, 64, 5, 4, 62, 99, 98, 54, 56, 17, 18, 23, 34, 15, 35, 25, 53, 51}; int temp = 0; for (int i = 1; i < a.length; i++.html) { int j = i - 1; temp = a[i]; for (; j >= 0 && temp < a[j]; j--.html) { a[j + 1] = a[j]; // 将大于 temp 的值整体后移一个单位 } a[j + 1] = temp; } for (int i = 0; i < a.length; i++.html) System.out.println(a[i].html); } }2. 希尔排序（Shell Sort）2.1 基本思想算法先将要排序的一组数按某个增量 d（n/2，n 为要排序数的个数）分成若干组，每组中记录的下标相差 d。对每组中全部元素进行直接插入排序，然后再用一个较小的增量（d/2）对它进行分组，在每组中再进行直接插入排序。当增量减到 1 时，进行直接插入排序后，排序完成。2.2 实例2.3 代码实现public class shellSort { public shellSort() { int a[] = {1, 54, 6, 3, 78, 34, 12, 45, 56, 100}; double d1 = a.length; int temp = 0; while (true.html) { d1 = Math.ceil(d1 / 2.html); int d = (int.html) d1; for (int x = 0; x < d; x++.html) { for (int i = x + d; i < a.length; i += d.html) { int j = i - d; temp = a[i]; for (; j >= 0 && temp < a[j]; j -= d.html) { a[j + d] = a[j]; } a[j + d] = temp; } } if (d == 1.html) break; } for (int i = 0; i < a.length; i++.html) System.out.println(a[i].html); } }3. 简单选择排序（Selection Sort）3.1 基本思想在要排序的一组数中，选出最小的一个数与第一个位置的数交换；然后在剩下的数当中再找最小的与第二个位置的数交换，如此循环到倒数第二个数和最后一个数比较为止。3.2 实例3.3 代码实现public class selectSort { public selectSort() { int a[] = {1, 54, 6, 3, 78, 34, 12, 45}; int position = 0; for (int i = 0; i < a.length; i++.html) { int j = i + 1; position = i; int temp = a[i]; for (; j < a.length; j++.html) { if (a[j] < temp.html) { temp = a[j]; position = j; } } a[position] = a[i]; a[i] = temp; } for (int i = 0; i < a.length; i++.html) System.out.println(a[i].html); } }4. 堆排序（Heap Sort）4.1 基本思想堆排序是一种树形选择排序，是对直接选择排序的有效改进。堆的定义如下：具有 n 个元素的序列（h1,h2,...,hn)，当且仅当满足（hi>=h2i, hi>=2i+1）或（hi<=h2i, hi<=2i+1）(i=1,2,...,n/2.html) 时称之为堆。在这里…

Java编程：常见问题汇总
type: item
date: 2026-07-07T08:35:17+00:00
description: Java 编程：常见问题汇总本文收集了一些看似无害但实际上存在问题的代码模式（反模式）。初学者往往在语言语法上挣扎，不熟悉标准 JDK 类库的最佳实践。这里的示例大多源自日常初级代码，经过修改以突出潜在问题。许多问题可以通过 SonarQube 等工具轻松发现，强烈推荐使用该工具进行代码质量检查。其中一些问题可能看起来像是微优化、未经剖析的过早优化或常数因子优化。然而，在成千上万个这样的小地方浪费的性能和内存会迅速累积，使应用程序变得难以维护。这里提到的“应用程序”主要指运行在应用服务器上的服务端应用。在桌面 GUI 应用程序上，情况可能没那么严重，但客户端 Java 应用的主要平台——Android，是一个资源（尤其是内存）非常有限的嵌入式平台。在这里，即使是常数因子的优化也能很快获得回报，例如遍历数组而不是列表。如果您对如何友好地优化性能感兴趣，请查看 JDK Performance Wiki。最后，应用程序的性能很大程度上取决于代码的整体质量。切勿低估内存占用的重要性。我看到过太多应用程序因疯狂的垃圾回收（GC）开销和内存不足错误而崩溃。尽管垃圾回收很快，但大多数服务端代码的可伸缩性主要受制于每个请求/事务的内存使用量和请求/事务持续时间。将这两个参数中的任何一个优化一个常数因子，都将直接带来更高的吞吐量。如果系数是 10，这意味着支持 100 或 1000 个用户的能力差异，这对客户至关重要。比较以下情况（假设年轻代堆内存为 100MB）：场景 (Scenario.html)线程池 (thread pool.html)事务持续时间 (tx duration.html)=> 最大事务数/秒 (max tx / s.html)内存/事务 (mem / tx.html)=> 垃圾/分钟 (garbage / min.html)GC 次数/分钟 (GC / min.html)基准 (base.html)30100 ms30050 KB900 MB9较慢 (slower.html)301000 ms3050 KB90 MB0.9更多内存 (more mem.html)30100 ms300500 KB9 GB90内存过剩 (excess mem.html)30100 ms3005 MB90 GB900在较慢的情况下，事务持续时间长了 10 倍。这也立即将每秒最大事务数减少了 10 倍（受限于线程池和 CPU 资源）。在更多内存的情况下，每个事务使用了 10 倍的内存。这直接将垃圾收集的频率提高到每秒超过一次，导致不可忽略的开销。使用更多的内存，例如在内存过剩场景中，将导致每秒 15 次收集，而每个收集只剩下 66ms，这显然是不够的，系统将崩溃。同样，66ms 低于 100ms 的事务持续时间，因此许多正在运行的事务仍将保留在内存中，阻止了它们的收集，并导致内存晋升到老年代。这意味着老年代将开始增长，并且需要频繁且缓慢的 Full GC。该场景中的应用程序将无法正常执行。与仅代码运行缓慢相比，这清楚地表明了过多的内存消耗是多么糟糕。当您分配过多的内存时，所有超快速代码都无法为您提供帮助。目录字符串串联StringBuffer 的性能误区测试字符串是否相等将数字转换为字符串解析和转换数字不利用不可变对象XML 解析的简易误区用 String 操作组装 XMLXML 编码陷阱字符不是整数假设 char 代表一个字符平台相关的文件名未定义的编码无缓冲流InputStreamReader/OutputStreamWriter 上的未缓冲操作使用 PrintWriter 进行文件 I/O堆内存溢出风险无限超时风险假设便宜的计时器呼叫捕获所有异常：未知的运行时异常异常很烦人重新包装 RuntimeException没有正确传播异常愚蠢的异常消息异常捕获与日志记录不完整的异常处理永远不会发生的异常transient 字段初始化陷阱过度初始化日志实例：静态还是非静态？选择错误的类加载器使用反射不良同步过度列表类型错误HashMap 大小陷阱Hashtable、HashMap 和 HashSet 被高估了列表被高估对象数组非常灵活对象过早分解修改 Setter不必要的 Calendar依赖默认的时区时区“转换”使用 Calendar.getInstance()危险 Calendar 操作调用 Date.setTime()假设 SimpleDateFormat 是线程安全的具有全局 Configuration/Parameters/Constants 类没有注意到溢出将 == 与 float 或 double 一起使用将钱存入浮点变量在 finally 块中不释放资源滥用 finalize()不由自主地重置 Thread.interrupted来自…

探索Java性能优化：技巧与实例全解析-深入篇
type: item
date: 2026-07-07T08:35:14+00:00
description: 2. 深入优化技巧篇2.1 方法内联优化现代 Java 编译器（JIT）与 JVM 会尝试进行方法内联（Method Inlining）优化。对于一些简单且被频繁调用的小方法，编译器将其代码直接嵌入到调用处，从而减少方法调用的栈帧开销。例如：public class MethodInlineExample { private static int add(int a, int b.html) { return a + b; } public static void main(String[] args.html) { int result = add(3, 5.html); // 编译器可能会将 add 方法内联，直接计算 3 + 5 System.out.println(result.html); } }在实际开发中，建议尽量保持方法的简洁性，避免过度复杂的逻辑，以利于编译器进行内联优化。2.2 逃逸分析与栈上分配逃逸分析（Escape Analysis）是 JVM 的一项关键优化技术。通过分析对象的作用域，JVM 确定对象是否会逃逸出方法或线程。如果一个对象不会逃逸出方法，它可能会被分配在栈上（标量替换），而不是堆上。这样可以减少垃圾回收（GC）的压力，显著提高性能。例如：public class EscapeAnalysisExample { public static void main(String[] args.html) { for (int i = 0; i < 1000000; i++.html) { // 这里的 StringBuilder 对象可能会被栈上分配 StringBuilder sb = new StringBuilder(); sb.append("Hello".html); sb.append("World".html); } } }在此例中，由于 StringBuilder 对象在循环内创建且生命周期仅限于方法内部，不会逃逸，JVM 可能会将其分配在栈上或通过标量替换优化，从而提升性能。2.3 锁优化策略在多线程编程中，锁的使用不可避免地带来性能开销。我们可以采用以下锁优化策略来降低竞争成本：减小锁的粒度：例如，在一个包含多个独立操作的类中，如果多个线程只对其中部分操作有竞争，可以将这些操作分别用不同的锁保护，而不是使用一个大锁保护整个类。读写锁：当数据的读取操作远远多于写入操作时，使用读写锁（ReentrantReadWriteLock）可以提高并发性能。多个线程可以同时获取读锁进行读取操作，但写入操作时需要获取写锁，且写锁会独占资源，防止读写冲突。锁粗化：如果在一个循环内频繁地获取和释放同一把锁，可以将锁的获取和释放操作放在循环外，减少锁操作的次数。但需注意不要过度粗化锁，以免降低并发度。2.4 避免伪共享问题伪共享（False Sharing）是多线程编程中容易被忽视的性能问题。由于现代 CPU 的缓存行（Cache Line）机制，当多个线程访问不同变量但这些变量位于同一个缓存行时，可能会导致缓存行的频繁失效和重新加载，严重影响性能。我们可以通过填充字节（Padding）的方式，使得不同线程访问的变量分布在不同的缓存行。例如：public class FalseSharingExample { public static class Data { // 使用 volatile 保证可见性 volatile long value; // 填充字节，避免与其他 Data 对象的 value 变量位于同一缓存行 long padding1, padding2, padding3, padding4, padding5, padding6, padding7; } public static void main(String[] args.html) throws InterruptedException { Data[] dataArray = new Data[2]; dataArray[0] = new Data(); dataArray[1] = new Data(); Thread t1 = new Thread((.html) -> { for (int i = 0; i < 100000000; i++.html) { dataArray[0].value = i; } }); Thread t2 = new Thread((.html) -> { for (int i = 0; i < 100000000; i++.html) { dataArray[1].value = i; } }); t1.start(); t2.start(); t1.join(); t2.join…

Java深度历险（一）——Java字节代码的操纵
type: item
date: 2026-07-07T08:34:45+00:00
description: 在常规的 Java 应用开发流程中，开发人员使用 Java 的方式相对简单：打开常用的 IDE，编写 Java 源代码，再利用 IDE 提供的功能直接运行 Java 程序即可。这种开发模式背后的过程是：开发人员编写的是 Java 源代码文件（.java），IDE 会负责调用 Java 编译器把 Java 源代码编译成平台无关的字节代码（byte code），以类文件的形式保存在磁盘上（.class）。Java 虚拟机（JVM）会负责把 Java 字节代码加载并执行。Java 通过这种方式来实现其"编写一次，到处运行（Write once, run anywhere）"的目标。Java 类文件中包含的字节代码可以被不同平台上的 JVM 所使用。Java 字节代码不仅可以以文件形式存在于磁盘上，也可以通过网络方式来下载，还可以只存在于内存中。JVM 中的类加载器会负责从包含字节代码的字节数组（byte[]）中定义出 Java 类。在某些情况下，可能会需要动态地生成 Java 字节代码，或是对已有的 Java 字节代码进行修改。这个时候就需要用到本文中将要介绍的相关技术。首先介绍一下如何动态编译 Java 源文件。动态编译 Java 源文件在一般情况下，开发人员都是在程序运行之前就编写完成了全部的 Java 源代码并且成功编译。对有些应用来说，Java 源代码的内容在运行时刻才能确定。这个时候就需要动态编译源代码来生成 Java 字节代码，再由 JVM 来加载执行。典型的场景是很多算法竞赛的在线评测系统（如 PKU JudgeOnline），允许用户上传 Java 代码，由系统在后台编译、运行并进行判定。在动态编译 Java 源文件时，使用的做法是直接在程序中调用 Java 编译器。JSR 199 引入了 Java 编译器 API。如果使用 JDK 6 的话，可以通过此 API 来动态编译 Java 代码。比如下面的代码用来动态编译最简单的 Hello World 类。该 Java 类的代码是保存在一个字符串中的。public class CompilerTest { public static void main(String[] args.html) throws Exception { String source = "public class Main { public static void main(String[] args.html) {System.out.println(\"Hello World!\".html);} }"; JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null.html); StringSourceJavaObject sourceObject = new CompilerTest.StringSourceJavaObject("Main", source.html); Iterable<? extends JavaFileObject> fileObjects = Arrays.asList(sourceObject.html); CompilationTask task = compiler.getTask(null, fileManager, null, null, null, fileObjects.html); boolean result = task.call(); if (result.html) { System.out.println("编译成功。".html); } } static class StringSourceJavaObject extends SimpleJavaFileObject { private String content = null; public StringSourceJavaObject(String name, String content.html) throws URISyntaxException { super(URI.create("string:///" + name.replace('.', '/'.html) + Kind.SOURCE.extension), Kind.SOURCE); this.content = content; } public CharSequence getCharContent(boolean ignoreEncodingErrors.html) th…

在Java中使用6种方法进行http请求
type: item
date: 2026-07-07T08:34:27+00:00
description: 在 Java 中使用 6 种方法进行 HTTP 请求在 Java 开发中，发起 HTTP 请求有多种成熟方案。常见的实现方式主要包括以下六种：Java SE 自带的 HttpURLConnection 类Apache 的 HttpClient 第三方库Spring 框架的 RestTemplate 类JavaFX 的 WebEngine 类Square 公司的 OkHttp 第三方库Square 公司的 Retrofit 第三方库以上方法均可用于发出 HTTP 请求，具体选择可依据项目依赖与技术栈决定。以下将逐一介绍这六种方法的具体使用示例，更多细节也可参考各库的官方文档。1. Java SE 的 HttpURLConnection 类这是 JDK 内置的标准类，无需引入额外依赖。import java.io.*; import java.net.*; public class HttpRequestExample { public static void main(String[] args.html) throws IOException { URL url = new URL("https://www.example.com".html); HttpURLConnection con = (HttpURLConnection.html) url.openConnection(); con.setRequestMethod("GET".html); int status = con.getResponseCode(); BufferedReader in = new BufferedReader( new InputStreamReader(con.getInputStream(.html))); String inputLine; StringBuffer content = new StringBuffer(); while ((inputLine = in.readLine(.html)) != null) { content.append(inputLine.html); } in.close(); con.disconnect(); System.out.println(content.html); } }2. Apache 的 HttpClient 第三方库Apache HttpComponents 提供的经典 HTTP 客户端库。CloseableHttpClient client = HttpClients.createDefault(); HttpGet request = new HttpGet("http://www.example.com".html); CloseableHttpResponse response = client.execute(request.html);3. Spring 的 RestTemplate 类Spring 框架提供的同步 HTTP 客户端，使用简便。RestTemplate restTemplate = new RestTemplate(); String response = restTemplate.getForObject("http://www.example.com", String.class);4. JavaFX 的 WebEngine 类JavaFX 提供的引擎类，通常用于加载网页内容，也可用于发起请求。WebEngine engine = new WebEngine(); engine.load("http://www.example.com".html);5. OkHttp 第三方库高效的 HTTP 客户端库，广泛应用于 Android 及 Java 后端开发。OkHttpClient client = new OkHttpClient(); Request request = new Request.Builder() .url("http://www.example.com".html) .build(); Response response = client.newCall(request.html).execute();6. Retrofit 第三方库基于 OkHttp 的类型安全 HTTP 客户端，常用于定义 API 接口。Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://www.example.com".html) .build(); MyApi api = retrofit.create(MyApi.class); Call<ResponseBody> call…