Java 编程问题:一、字符串、数字和数学4

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: Java 编程问题:一、字符串、数字和数学

19 声明多行字符串(文本块)


在写这本书的时候,JDK12 有一个关于添加多行字符串的建议,称为 JEP326:原始字符串字面值。但这是在最后一刻掉的


从 JDK13 开始,重新考虑了这个想法,与被拒绝的原始字符串字面值不同,文本块被三个双引号包围,""",如下所示:

String text = """My high school,
the Illinois Mathematics and Science Academy,
showed me that anything is possible
and that you're never too young to think big.""";


文本块对于编写多行 SQL 语句、使用 polyglot 语言等非常有用。更多详情见这个页面。

尽管如此,在 JDK13 之前有几种替代解决方案可以使用。这些解决方案有一个共同点,即使用行分隔符:

private static final String LS = System.lineSeparator();


从 JDK8 开始,解决方案可以依赖于String.join(),如下所示:

String text = String.join(LS,
  "My high school, ",
  "the Illinois Mathematics and Science Academy,",
  "showed me that anything is possible ",
  "and that you're never too young to think big.");


在 JDK8 之前,一个优雅的解决方案可能依赖于StringBuilder。本书附带的代码中提供了此解决方案。


虽然前面的解决方案适合于相对大量的字符串,但如果我们只有几个字符串,下面的两个就可以了。第一个使用+运算符:

String text = "My high school, " + LS +
  "the Illinois Mathematics and Science Academy," + LS +
  "showed me that anything is possible " + LS +
  "and that you're never too young to think big.";


第二个使用String.format()

String text = String.format("%s" + LS + "%s" + LS + "%s" + LS + "%s",
  "My high school, ",
  "the Illinois Mathematics and Science Academy,",
  "showed me that anything is possible ",
  "and that you're never too young to think big.");


如何处理多行字符串的每一行?好吧,快速方法需要 JDK11,它与String.lines()方法一起提供。该方法通过行分隔符(支持\n、\r、\r\n对给定字符串进行拆分,并将其转换为Stream<String>。或者,也可以使用String.split()方法(从 JDK1.4 开始提供)。如果字符串的数量变得重要,建议将它们放入一个文件中,并逐个读取/处理它们(例如,通过getResourceAsStream()方法)。其他方法依赖于StringWriter或BufferedWriter.newLine()。


对于第三方库支持,请考虑 Apache Commons Lang、StringUtils.join()、Guava、Joiner和自定义注解@Multiline。




20 连接相同字符串 n 次

在 JDK11 之前,可以通过StringBuilder快速提供解决方案,如下:

public static String concatRepeat(String str, int n) {
  StringBuilder sb = new StringBuilder(str.length() * n);
  for (int i = 1; i <= n; i++) {
    sb.append(str);
  }
  return sb.toString();
}


从 JDK11 开始,解决方案依赖于String.repeat(int count)方法。此方法返回一个字符串,该字符串通过将此字符串count连接几次而得到。在幕后,这个方法使用了System.arraycopy(),这使得这个速度非常快:

String result = "hello".repeat(5);


其他适合不同场景的解决方案如下:


  • 以下是基于String.join()的解决方案:
String result = String.join("", Collections.nCopies(5, TEXT));



  • 以下是基于Stream.generate()的解决方案:
String result = Stream.generate(() -> TEXT)
  .limit(5)
  .collect(joining());


  • 以下是基于String.format()的解决方案:
String result = String.format("%0" + 5 + "d", 0)
  .replace("0", TEXT);


  • 以下是基于char[]的解决方案:
String result = new String(new char[5]).replace("\0", TEXT);



对于第三方库支持,请考虑 ApacheCommonsLang,StringUtils.repeat()和 Guava,Strings.repeat()

要检查字符串是否是相同子字符串的序列,请使用以下方法:

public static boolean hasOnlySubstrings(String str) {
  StringBuilder sb = new StringBuilder();
  for (int i = 0; i < str.length() / 2; i++) {
    sb.append(str.charAt(i));
    String resultStr = str.replaceAll(sb.toString(), "");
    if (resultStr.length() == 0) {
      return true;
    }
  }
  return false;
}

该解决方案循环给定字符串的一半,并通过逐字符将原始字符串追加到StringBuilder中,逐步用""(子字符串构建)替换它。如果这些替换结果是空字符串,则表示给定的字符串是相同子字符串的序列。



21 删除前导空格和尾随空格

这个问题的最快解决方法可能依赖于String.trim()方法。此方法能够删除所有前导和尾随空格,即代码点小于或等于 U+0020 或 32 的任何字符(空格字符):

String text = "\n \n\n hello \t \n \r";
String trimmed = text.trim();


前面的代码片段将按预期工作。修剪后的字符串将为hello。这只适用于所有正在使用的空格小于 U+0020 或 32(空格字符)。有 25 个字符定义为空格,trim()只覆盖其中的一部分(简而言之,trim()不知道 Unicode)。让我们考虑以下字符串:

char space = '\u2002';
String text = space + "\n \n\n hello \t \n \r" + space;


\u2002是trim()无法识别的另一种类型的空白(\u2002在\u0020之上)。这意味着,在这种情况下,trim()将无法按预期工作。从 JDK11 开始,这个问题有一个名为strip()的解决方案。此方法将trim()的功能扩展到 Unicode 领域:

String stripped = text.strip();



这一次,所有的前导和尾随空格都将被删除。

此外,JDK11 还提供了两种类型的strip(),用于仅删除前导(stripLeading())或尾部(stripTrailing())空格。trim()方法没有这些味道。




22 查找最长的公共前缀


让我们考虑以下字符串数组:

String[] texts = {"abc", "abcd", "abcde", "ab", "abcd", "abcdef"};


现在,让我们把这些线一根接一根,如下所示:

abc
abcd
abcde
ab abcd
abcdef


通过对这些字符串的简单比较可以看出,ab是最长的公共前缀。现在,让我们深入研究解决此问题的解决方案。我们在这里提出的解决方案依赖于一个简单的比较。此解决方案从数组中获取第一个字符串,并将其每个字符与其余字符串进行比较。如果发生以下任一情况,算法将停止:


   第一个字符串的长度大于任何其他字符串的长度


   第一个字符串的当前字符与任何其他字符串的当前字符不同


如果由于上述情况之一而强制停止算法,则最长的公共前缀是从 0 到第一个字符串的当前字符索引的子字符串。否则,最长的公共前缀是数组中的第一个字符串。此解决方案的代码如下:

public static String longestCommonPrefix(String[] strs) {
  if (strs.length == 1) {
    return strs[0];
  }
  int firstLen = strs[0].length();
  for (int prefixLen = 0; prefixLen < firstLen; prefixLen++) {
    char ch = strs[0].charAt(prefixLen);
    for (int i = 1; i < strs.length; i++) {
      if (prefixLen >= strs[i].length() 
         || strs[i].charAt(prefixLen) != ch) {
          return strs[i].substring(0, prefixLen);
      }
    }
  }
  return strs[0];
}

这个问题的其他解决方案使用众所周知的算法,例如二分搜索Trie。在本书附带的源代码中,还有一个基于二分搜索的解决方案。



23 应用缩进


从 JDK12 开始,我们可以通过String.indent(int n)方法缩进文本。

假设我们有以下String值:

String days = "Sunday\n" 
  + "Monday\n" 
  + "Tuesday\n" 
  + "Wednesday\n" 
  + "Thursday\n" 
  + "Friday\n" 
  + "Saturday";

用 10 个空格的缩进打印这个String值可以如下所示:

System.out.print(days.indent(10));


输出如下:


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ziozvzzr-1657077108924)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/3c34fd48-6ec4-427d-a0cd-3037407f6bc8.png)]


现在,让我们试试层叠缩进:

List<String> days = Arrays.asList("Sunday", "Monday", "Tuesday",
  "Wednesday", "Thursday", "Friday", "Saturday");
for (int i = 0; i < days.size(); i++) {
  System.out.print(days.get(i).indent(i));
}


输出如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fwmBXszC-1657077108924)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/e9d00aa3-22ae-4c73-b34d-d3f2cf66e702.png)]


现在,让我们根据String值的长度缩进:


days.stream()
  .forEachOrdered(d -> System.out.print(d.indent(d.length())));


输出如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yvtiz6zI-1657077108925)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/342a0e70-c60d-4f4d-95c1-2de21e6a0d8e.png)]

缩进一段 HTML 代码怎么样?让我们看看:


String html = "<html>";
String body = "<body>";
String h2 = "<h2>";
String text = "Hello world!";
String closeH2 = "</h2>";
String closeBody = "</body>";
String closeHtml = "</html>";
System.out.println(html.indent(0) + body.indent(4) + h2.indent(8) 
  + text.indent(12) + closeH2.indent(8) + closeBody.indent(4)
  + closeHtml.indent(0));


输出如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9T42XLeQ-1657077108926)(https://github.com/apachecn/apachecn-java-zh/raw/master/docs/java-coding-prob/img/db341df1-83f8-438d-992e-8a62408fc196.png)]




24 转换字符串


假设我们有一个字符串,我们想把它转换成另一个字符串(例如,把它转换成大写)。我们可以通过应用像Function<? super String, ? extends R>这样的函数来实现这一点。


在 JDK8 中,我们可以通过map()来实现,如下两个简单的例子所示:

// hello world
String resultMap = Stream.of("hello")
  .map(s -> s + " world")
  .findFirst()
  .get();
// GOOOOOOOOOOOOOOOOL! GOOOOOOOOOOOOOOOOL!
String resultMap = Stream.of("gooool! ")
  .map(String::toUpperCase)
  .map(s -> s.repeat(2))
  .map(s -> s.replaceAll("O", "OOOO"))
  .findFirst()
  .get();

从 JDK12 开始,我们可以依赖一个名为transform(Function<? super String, ? extends R> f)的新方法。让我们通过transform()重写前面的代码片段:

// hello world
String result = "hello".transform(s -> s + " world");
// GOOOOOOOOOOOOOOOOL! GOOOOOOOOOOOOOOOOL!
String result = "gooool! ".transform(String::toUpperCase)
  .transform(s -> s.repeat(2))
  .transform(s -> s.replaceAll("O", "OOOO"));

虽然map()更一般,但transform()专用于将函数应用于字符串并返回结果字符串。



25 计算两个数的最小值和最大值


在 JDK8 之前,一个可能的解决方案是依赖于Math.min()Math.max()方法,如下所示:

int i1 = -45;
int i2 = -15;
int min = Math.min(i1, i2);
int max = Math.max(i1, i2);


Math类为每个原始数字类型(int、long、float和double提供了min()和max()方法。

从 JDK8 开始,每个原始数字类型的包装器类(Integer、Long、Float和Double都有专用的min()和max()方法,在这些方法后面,还有来自Math类的对应调用。请参见下面的示例(这是一个更具表现力的示例):

double d1 = 0.023844D;
double d2 = 0.35468856D;
double min = Double.min(d1, d2);
double max = Double.max(d1, d2);


在函数式风格的上下文中,潜在的解决方案将依赖于BinaryOperator函数式接口。此接口有两种方式,minBy()maxBy()

float f1 = 33.34F;
final float f2 = 33.213F;
float min = BinaryOperator.minBy(Float::compare).apply(f1, f2);
float max = BinaryOperator.maxBy(Float::compare).apply(f1, f2);

这两种方法能够根据指定的比较器返回两个元素的最小值(分别是最大值)。



26 两个大的int/long值求和并导致操作溢出


让我们从+操作符开始深入研究解决方案,如下例所示:

int x = 2;
int y = 7;
int z = x + y; // 9

这是一种非常简单的方法,适用于大多数涉及intlongfloatdouble的计算。


现在,让我们将此运算符应用于以下两个大数(与其自身相加为 2147483647):


int x = Integer.MAX_VALUE;
int y = Integer.MAX_VALUE;
int z = x + y; // -2


此时,z将等于 -2,这不是预期的结果,即 4294967294。仅将z类型从int更改为long将无济于事。但是,将xy的类型从int改为long将有所帮助:


long x = Integer.MAX_VALUE;
long y = Integer.MAX_VALUE;
long z = x + y; // 4294967294


但如果不是Integer.MAX_VALUE,而是Long.MAX_VALUE,问题就会再次出现:

long x = Long.MAX_VALUE;
long y = Long.MAX_VALUE;
long z = x + y; // -2


从 JDK8 开始,+操作符被一个原始类型数字类型的包装器以一种更具表现力的方式包装。因此,IntegerLongFloatDouble类具有sum()方法:

long z = Long.sum(); // -2


在幕后,sum()方法也使用+操作符,因此它们只产生相同的结果。


但同样从 JDK8 开始,Math类用两种addExact()方法进行了丰富。一个addExact()用于两个int变量的求和,一个用于两个long变量的求和。如果结果容易溢出int或long,这些方法非常有用,如前面的例子所示。在这种情况下,这些方法抛出ArithmeticException,而不是返回误导性的结果,如下例所示:

int z = Math.addExact(x, y); // throw ArithmeticException



代码将抛出一个异常,例如java.lang.ArithmeticException: integer overflow。这是很有用的,因为它允许我们避免在进一步的计算中引入误导性的结果(例如,早期,-2 可以悄悄地进入进一步的计算)。


在函数式上下文中,潜在的解决方案将依赖于BinaryOperator函数式接口,如下所示(只需定义相同类型的两个操作数的操作):


BinaryOperator<Integer> operator = Math::addExact;
int z = operator.apply(x, y);


除addExact()外,Math还有multiplyExact()、substractExact()、negateExact()。此外,众所周知的增量和减量表达式i++和i--可以通过incrementExact()和decrementExact()方法(例如Math.incrementExact(i))来控制溢出它们的域。请注意,这些方法仅适用于int和long。


在处理大量数字时,还要关注BigInteger(不可变任意精度整数)和BigDecimal(不可变任意精度带符号十进制数)类。



27 字符串按照基数转换为无符号数


对无符号算术的支持从版本 8 开始添加到 Java 中。ByteShortIntegerLong类受此影响最大。


在 Java 中,表示正数的字符串可以通过parseUnsignedInt()parseUnsignedLong()JDK8 方法解析为无符号的intlong类型。例如,我们将以下整数视为字符串:

String nri = "255500";


将其解析为以 36 为基数(最大可接受基数)的无符号int值的解决方案如下:

int result = Integer.parseUnsignedInt(nri, Character.MAX_RADIX);


第一个参数是数字,第二个参数是基数。基数应在[2, 36]或[Character.MIN_RADIX, Character.MAX_RADIX]范围内。

使用基数 10 可以很容易地完成如下操作(此方法默认应用基数 10):

int result = Integer.parseUnsignedInt(nri);


从 JDK9 开始,parseUnsignedInt()有了新的味道。除了字符串和基数之外,这个方法还接受一个范围的[beginIndex, endIndex]类型。这一次,解析是在这个范围内完成的。例如,可以按如下方式指定范围[1, 3]:

int result = Integer.parseUnsignedInt(nri, 1, 4, Character.MAX_RADIX);



parseUnsignedInt()方法可以解析表示大于Integer.MAX_VALUE的数字的字符串(尝试通过Integer.parseInt()完成此操作将引发java.lang.NumberFormatException异常):

// Integer.MAX_VALUE + 1 = 2147483647 + 1 = 2147483648
int maxValuePlus1 = Integer.parseUnsignedInt("2147483648");

对于Long类中的长数存在相同的方法集(例如,parseUnsignedLong()

相关文章
|
10天前
|
Java 开发者
Java多线程编程中的常见误区与最佳实践####
本文深入剖析了Java多线程编程中开发者常遇到的几个典型误区,如对`start()`与`run()`方法的混淆使用、忽视线程安全问题、错误处理未同步的共享变量等,并针对这些问题提出了具体的解决方案和最佳实践。通过实例代码对比,直观展示了正确与错误的实现方式,旨在帮助读者构建更加健壮、高效的多线程应用程序。 ####
|
16天前
|
JSON Java Apache
非常实用的Http应用框架,杜绝Java Http 接口对接繁琐编程
UniHttp 是一个声明式的 HTTP 接口对接框架,帮助开发者快速对接第三方 HTTP 接口。通过 @HttpApi 注解定义接口,使用 @GetHttpInterface 和 @PostHttpInterface 等注解配置请求方法和参数。支持自定义代理逻辑、全局请求参数、错误处理和连接池配置,提高代码的内聚性和可读性。
|
9天前
|
Java 开发者
Java多线程编程的艺术与实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的技术文档,本文以实战为导向,通过生动的实例和详尽的代码解析,引领读者领略多线程编程的魅力,掌握其在提升应用性能、优化资源利用方面的关键作用。无论你是Java初学者还是有一定经验的开发者,本文都将为你打开多线程编程的新视角。 ####
|
8天前
|
存储 安全 Java
Java多线程编程中的并发容器:深入解析与实战应用####
在本文中,我们将探讨Java多线程编程中的一个核心话题——并发容器。不同于传统单一线程环境下的数据结构,并发容器专为多线程场景设计,确保数据访问的线程安全性和高效性。我们将从基础概念出发,逐步深入到`java.util.concurrent`包下的核心并发容器实现,如`ConcurrentHashMap`、`CopyOnWriteArrayList`以及`BlockingQueue`等,通过实例代码演示其使用方法,并分析它们背后的设计原理与适用场景。无论你是Java并发编程的初学者还是希望深化理解的开发者,本文都将为你提供有价值的见解与实践指导。 --- ####
|
11天前
|
安全 Java 开发者
Java多线程编程中的常见问题与解决方案
本文深入探讨了Java多线程编程中常见的问题,包括线程安全问题、死锁、竞态条件等,并提供了相应的解决策略。文章首先介绍了多线程的基础知识,随后详细分析了每个问题的产生原因和典型场景,最后提出了实用的解决方案,旨在帮助开发者提高多线程程序的稳定性和性能。
|
17天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
14天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
16天前
|
存储 缓存 安全
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见
在 Java 编程中,创建临时文件用于存储临时数据或进行临时操作非常常见。本文介绍了使用 `File.createTempFile` 方法和自定义创建临时文件的两种方式,详细探讨了它们的使用场景和注意事项,包括数据缓存、文件上传下载和日志记录等。强调了清理临时文件、确保文件名唯一性和合理设置文件权限的重要性。
39 2
|
17天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
2天前
|
Java API 数据库
Java 反射机制:动态编程的 “魔法钥匙”
Java反射机制是允许程序在运行时访问类、方法和字段信息的强大工具,被誉为动态编程的“魔法钥匙”。通过反射,开发者可以创建更加灵活、可扩展的应用程序。
下一篇
无影云桌面