给定的 Java 字符串面试问题 范围从字符串方法、字符串不可变性和内存泄漏问题到简单的示例和用例。我将尝试涵盖 Java 面试中最常被问到的问题。
String
是Java中的关键字吗?不是。String
不是 Java 保留关键字。它是派生数据类型,即它是一个类。
public class StringExample
{
public static void main(String[] args)
{
Integer String = 10;
System.out.println(String); //Prints 10
}
}
我们都知道 Java 中的字符串是不可变的。如果您想知道什么是不变性以及它是如何实现的?关注这篇文章:How to使 java 类不可变?
这里的问题是为什么?为什么不可变?让我们分析一下。
看到这样的过度使用,他们想象字符串的不当使用会有多危险。于是他们想出了字符串池的概念。字符串池只不过是一些大多数唯一的字符串的集合。字符串池背后的基本思想是重用创建后的字符串。这样一来,如果某个特定字符串在代码中创建了 20 次,应用程序最终将只有一个实例。
字符串池是常规堆内存中的一个特定内存区域,其中存储了这些字符串常量。这些对象在应用程序的生命周期中被引用为字符串变量。
在 Java 中,我们可以通过多种方式创建字符串。例如,使用 string literals 和使用 new 关键字。
String str1 = "abc";
String str2 = new String("abc");
使用 string literal 会导致 JVM 验证是否已经存在字符串“abc”(相同的字符序列)。如果存在这样的字符串,JVM 将现有对象的引用分配给变量 str;否则,将创建一个新对象“abc”,并将其引用分配给变量 str1
。
使用 new 关键字时,Java 最终会在内存中创建两个对象。字符串池中的一个对象具有字符序列“abc”,第二个对象位于常规堆内存中,由变量 str2 引用并具有与“abc
”相同的字符序列。
为了获得更好的内存利用率和整体性能,建议使用字符串文字来创建字符串。 除非需要原始的显式副本,否则不需要使用构造函数,因为字符串是不可变的。
调用 String intern()
方法时,如果字符串池中已经包含等于 String
对象的字符串,由 equals(Object) 方法,然后返回池中的字符串。否则,会将此
String
对象添加到池中,并返回对此 String
对象的引用。
简单来说,string interning 是将 String 对象从常规堆内存移动到 String 常量并使用池中的对象引用的过程。
String str = new String("abc");
str.intern();
对于intern()方法,对于任意两个字符串s1
和s2
,s1.intern() == t2.intern( )
为 true
当且仅当 s1.equals(s2)
为 true
时。
这意味着如果 s1 和 s2 是不同的字符串对象并且具有相同的字符序列,那么对两者调用 intern() 将导致两个变量都引用一个字符串池文字。
默认情况下,请记住所有文字字符串和字符串值常量表达式都是驻留的。
我们可以使用Pattern 和Matcher 进行正则表达式匹配。 String 类提供了自己的方法 matches()
。我们应该直接使用 matches()。此方法还在函数定义中使用 Pattern.matches()。
String str = new String("Welcome to howtodoinjava.com");
System.out.println(str.matches("(.*)java(.*)")); //Prints true
System.out.println(Str.matches("(.*)python(.*)")); //Print false
采访中另一个最喜欢的领域。比较对象一般有两种方式
==
运算符equals()
方法==
运算符比较对象引用。因此,如果两个字符串对象引用字符串池中的相同文字或堆中的相同字符串对象,则 's == t
' 将返回 true,否则返回 false 。
equals()
方法在 String 类中被覆盖,并验证 String 对象持有的字符序列。换句话说,equals() 方法比较字符串对象的值。如果它们存储相同的字符序列,则 ;s.equals(t)
;将返回 true,否则返回 false。
到现在为止,我们已经完成了基本的工作。现在是严肃的事情。您是否尝试过从字符串对象创建子字符串?我敢打赌,是的。你知道 Java 中 substring 的内部结构吗?它们是如何造成内存泄漏的?
Java 中的子字符串是使用方法 substring(int beginIndex)
和该方法的一些其他重载形式创建的。所有这些方法都会创建一个新的 String 对象并更新我们在本文开头看到的 offset 和 count 变量。
原始值[]没有改变。因此,如果您创建一个包含 10000 个字符的字符串并创建 100 个子字符串,每个子字符串包含 5-10 个字符,则所有 101 个对象将具有大小为 10000 个字符的相同字符数组。毫无疑问,这是内存浪费。
让我们用一个程序看看:
import java.lang.reflect.Field;
import java.util.Arrays;
public class SubStringTest {
public static void main(String[] args) throws Exception
{
//Our main String
String mainString = "i_love_java";
//Substring holds value 'java'
String subString = mainString.substring(7);
System.out.println(mainString);
System.out.println(subString);
//Lets see what's inside mainString
Field innerCharArray = String.class.getDeclaredField("value");
innerCharArray.setAccessible(true);
char[] chars = (char[]) innerCharArray.get(mainString);
System.out.println(Arrays.toString(chars));
//Now peek inside subString
chars = (char[]) innerCharArray.get(subString);
System.out.println(Arrays.toString(chars));
}
}
i_love_java
java
[i, _, l, o, v, e, _, j, a, v, a]
[i, _, l, o, v, e, _, j, a, v, a]
显然,两个对象都存储了相同的 char 数组,而子字符串只需要四个字符。
让我们使用自己的代码来解决这个问题:
import java.lang.reflect.Field;
import java.util.Arrays;
public class SubStringTest
{
public static void main(String[] args) throws Exception
{
//Our main String
String mainString = "i_love_java";
//Substring holds value 'java'
String subString = fancySubstring(7, mainString);
System.out.println(mainString);
System.out.println(subString);
//Lets see what's inside mainString
Field innerCharArray = String.class.getDeclaredField("value");
innerCharArray.setAccessible(true);
char[] chars = (char[]) innerCharArray.get(mainString);
System.out.println(Arrays.toString(chars));
//Now peek inside subString
chars = (char[]) innerCharArray.get(subString);
System.out.println(Arrays.toString(chars));
}
//Our new method prevents memory leakage
public static String fancySubstring(int beginIndex, String original)
{
return new String(original.substring(beginIndex));
}
}
i_love_java
java
[i, _, l, o, v, e, _, j, a, v, a]
[j, a, v, a]
现在子字符串只有它需要的字符,用于创建正确子字符串的中间字符串可以被垃圾收集,因此不会留下内存足迹。
与任何其他编程语言一样,Java 中的字符串是字符序列。这更像是一个处理该字符序列的实用程序类。此 char 序列保存在以下 char array 类型的变量中:
/** The value is used for character storage. */
private final char value[];
各种字符串方法在不同的场景下对这个数组进行操作,使用以下变量来维护数组中的位置:
/** The offset is the first index of the storage that is used. */
private final int offset;
/** The count is the number of characters in the String. */
private final int count;
如果一个字符串在反转时其值相同,则它是回文串。 要检查回文,将字符串反转并与原始字符串进行比较。
如果原始字符串和反转字符串相同,则给定字符串是回文。
String originalString = "abcdcba";
StringBuilder strBuilder = new StringBuilder(originalString);
String reverseString = strBuilder.reverse().toString();
boolean isPalindrame = originalString.equals(reverseString);
System.out.println(isPalindrame); //true
要替换或删除字符,请使用 String.replace()
或 String.replaceAll()
。
两种方法都有两个参数。第一个参数是要替换的字符,第二个参数是放置在字符串中的新字符。
如果您想删除字符,请在第二个参数中传递空白字符。
String originalString = "howtodoinjava";
//Replace one character
System.out.println( originalString.replace("h", "H") ); //Howtodoinjava
//Replace all matching characters
System.out.println( originalString.replaceAll("o", "O") ); //hOwtOdOinjava
//Remove one character
System.out.println( originalString.replace("h", "") ); //owtodoinjava
//Remove all matching characters
System.out.println( originalString.replace("o", "") ); //hwtdinjava
使用 String.toLowerCase()
和 String.toUpperCase()
方法将字符串转换为小写或大写。
String blogName = "HowToDoInJava.com";
System.out.println(blogName.toLowerCase()); //howtodoinjava.com
System.out.println(blogName.toUpperCase()); //HOWTODOINJAVA.COM
是的,我们可以在 switch
String
类code> statements since Java 7. 在 Java 7 之前,这是不可能的,我们必须使用 if-else
语句来实现类似的行为。
String number = "1";
switch (number)
{
case "1":
System.out.println("One"); //Prints '1'
break;
case "2":
System.out.println("Two");
break;
default:
System.out.println("Other");
}
排列是字符元素的重新排列,因此每个排列相对于其他排列都是唯一的。例如,下面是字符串“ABC”的排列 – ABC ACB BAC BCA CBA CAB。
长度为
N
的字符串有N! (N 阶乘)
排列。
import java.util.HashSet;
import java.util.Set;
public class StringExample
{
public static void main(String[] args)
{
System.out.println(getPermutations("ABC"));
//Prints
//[ACB, BCA, ABC, CBA, BAC, CAB]
}
public static Set<String> getPermutations(String string)
{
//All permutations
Set<String> permutationsSet = new HashSet<String>();
// invalid strings
if (string == null || string.length() == 0)
{
permutationsSet.add("");
}
else
{
//First character in String
char initial = string.charAt(0);
//Full string without first character
String rem = string.substring(1);
//Recursive call
Set<String> wordSet = getPermutations(rem);
for (String word : wordSet) {
for (int i = 0; i <= word.length(); i++) {
permutationsSet.add(charInsertAt(word, initial, i));
}
}
}
return permutationsSet;
}
public static String charInsertAt(String str, char c, int position)
{
String begin = str.substring(0, position);
String end = str.substring(position);
return begin + c + end;
}
}
要分别反转每个单词,将字符串标记化并将所有单词分隔在一个数组中。然后,对每个单词应用反向单词逻辑,最后,连接所有单词。
String blogName = "how to do in java dot com";
//spilt on white space
String[] tokens = blogName.split(" ");
//It will store reversed words
StringBuffer finalString = new StringBuffer();
//Loop all words and reverse them
for (String token : tokens) {
String reversed = new StringBuffer(token).reverse().toString();
finalString.append(reversed);
finalString.append(" ");
}
//Check final string
System.out.println(finalString.toString()); //woh ot od ni avaj tod moc
使用 String.split()< /code> 在给定正则表达式的匹配项周围断开给定字符串的方法。它也称为获取基于定界符的字符串标记。
split() 方法返回字符串数组。数组中的每个字符串都是一个单独的标记。
String numbers = "1,2,3,4,5,6,7";
String[] numArray = numbers.split(",");
System.out.println(Arrays.toString(numArray)); //[1, 2, 3, 4, 5, 6, 7]
我们知道在Java中字符串是保存在常量池中的。一旦在字符串池中创建了一个字符串,它就会留在池中,除非被垃圾回收。此时,任何恶意程序都可以访问物理内存中的内存区域,也可以访问字符串数据。
如果我们将密码存储为字符串,它将保存在 spring 池中,并且在内存中可用的时间比所需的时间更长,因为垃圾收集周期是不可预测的。这使得敏感的密码字符串容易受到黑客攻击和数据窃取。
是否可以争论一个选项在使用后将 String 设为空白?不,我们不可以。我们知道,一旦创建了 String,我们就无法对其进行操作,例如你不能改变它的内容。 字符串是最终的和不可变的。
但是字符数组是可变的,我们可以在使用后覆盖它们的内容。所以我们的应用程序应该使用char[]
来存储密码文本,并在使用密码后,将数组内容替换为空白。
String password = "123456"; //Do not use it
char[] passwordChars = new char[4]; //Get password from some system such as database
//use password
for(char c : passwordChars) {
c = ' ';
}
是的,字符串是线程安全的 因为它们是不可变的。
请记住,所有不可变实例在设计上都是线程安全的。
在 Java 中,Map 键应该是不可变的,并且应该遵守 equals()
和 hashCode()
方法之间的约定。 String
类满足这两个条件。
此外,String 类提供了许多有用的方法来比较、排序、标记化或大写大小写。在 Map
上执行 CRUD 操作时可以使用这些方法。
所有这些使 String 成为一个非常有用的类,可以在 Map
中使用,而不是创建我们自己的类。
char[]
并仅操作此数组中的内容。 当我们需要在执行所有操作后获得完整的连接字符串时,它会创建一个新的 String 存储的 字符数组.
StringBuilder
类。唯一的区别是它是线程安全的。所有方法都是同步
。根据您是否需要线程安全,使用 StringBuffer
或 StringBuilder
类。在这两个类中使用 append()
方法连接字符串。
StringBuffer buffer = new StringBuffer();
buffer.append("how")
.append("to")
.append("do")
.append("in")
.append("java")
.append(".")
.append("com");
String blogName = buffer.toString();
System.out.println(blogName); //howtodoinjava.com
String s1 = "howtodoinjava.com";
String s2 = "howtodoinjava.com";
String s3 = new String("howtodoinjava.com");
s2
将引用与 s1
相同的字符串常量。为了找到给定字符串中每个字符的出现次数,我们使用了 HashMap
并将该字符作为 key 并将其出现次数作为 value。
每次新出现一个字符,我们都会增加该字符的计数器值。
String blogName = "howtodoinjava.com";
HashMap<Character, Integer> occurancesMap = new HashMap<Character, Integer>();
char[] strArray = blogName.toCharArray();
for (char c : strArray)
{
if(occurancesMap.containsKey(c))
{
occurancesMap.put(c, occurancesMap.get(c)+1);
}
else
{
occurancesMap.put(c, 1);
}
}
System.out.println(occurancesMap);
//{a=2, c=1, d=1, h=1, i=1, j=1, m=1, n=1, .=1, o=4, t=1, v=1, w=1}
反转字符串的最佳方法是 StringBuffer.reverse() 和 StringBuilder.reverse() 方法。不过,面试官可能会要求您编写自己的程序,以检查您的技能水平。
使用下面给出的基于递归的示例来反转字符串。
该程序从字符串中取出第一个字符并将其放在字符串的最后一个位置。它对字符串中的所有字符使用此替换,直到整个字符串被反转。
public class StringExample
{
public static void main(String[] args)
{
String blogName = "howtodoinjava.com";
String reverseString = recursiveSwap(blogName);
System.out.println(reverseString);
}
static String recursiveSwap(String str)
{
if ((null == str) || (str.length() <= 1))
{
return str;
}
return recursiveSwap(str.substring(1)) + str.charAt(0);
}
}
我认为这些String 面试常见问题 会对您的下一次面试有所帮助。
地址:https://www.cundage.com/article/string-questions.html