序文
現実の世界では、人々の生活は常に電気と切り離せません。人間活動や機械操作などの最も基本的なものとして、それらは全世界の操作において主要な役割を果たしています。Javaの世界では、独自の電気もあり、ほとんどの場所で見ることができます。Stringクラスです。使用頻度が高く、コンテンツポイントが多いため、Stringクラスは紹介のために2つの部分に分けられます。最初の部分はソースコードの分解であり、2番目の部分は一般的な困難な点の分析です。
データ構造
データ構造には「文字列」と呼ばれるデータ構造があり、その最下層は配列で構成されています。この構造で操作、検索などを行うことができます。Stringクラスは、Java言語での「string」のデータ構造の実現です。文字列のデータ操作アルゴリズムをシミュレートし、文字列の追加、削除、変更を実現できます。この記事では、Stringクラスとそのソースコードの実装の分析に焦点を当てます。このクラスをマスターすると、最も基本的なツールをマスターできます。将来の開発キャリアでStringクラスを使用できるようになると、非常に熟練し、善意を持ったものになります。
概要図
このカテゴリには多くの方法がありますが、分析してその動作を分類し、優先度順に並べ替えることができます。最後に、共通の重要な部分に焦点を当てます。
この章の分析シーケンス
上記のAPIはよく見えますが、実際には、モジュールで分割された後、いくつかの側面に大まかに分割されます。図に示すように:
この章では、Apiで一般的に使用されるメソッド、主要なメソッド、および特別なメソッドの3つの部分に焦点を当てます。すべての方法は、機能によって[追加] [削除] [チェック] [変更] [セグメント] [比較] [変換]と[その他]に分けられています。
これまで、プログラマーになるのは8か月目です。ビジネスの洗練、プロジェクトのトレーニング、自己理解により、追加、削除、チェック、変更はできないと感じています。理由を調べてみると、これらの操作は実際にはデータ構造の基本的な操作であることがわかりました。線形テーブル、ツリー構造、グラフ構造のいずれであっても、これらの一般的な操作があります。Stringクラスは、線形構造の文字列のJAVA実装であり、その基本的な操作も、追加、削除、および変更と切り離せません。これに基づいて、さまざまなデータ型、さまざまな戻り値、およびさまざまな入力パラメーター値のAPIが拡張されます。以下の分類は、個人的な理解のみを表しています。おそらく、一部のAPiについては全員が異なる理解を持っており、区分は同じです。
文字列クラス
(1)増加
concat(String str)
(2)削除
trim()
(3)チェック
长度 : length() isEmpty()
字符 : indexOf(int ch) indexOf(int ch, int fromIndex)
indexOf(String str) indexOf(String str, int fromIndex)
lastIndexOf(int ch) lastIndexOf(int ch, int fromIndex)
lastIndexOf(String str) lastIndexOf(String str, int fromIndex)
charAt(int index)
hash值: hashCode()
(4)変更
replace(char oldChar, char newChar)
replace(CharSequence target, CharSequence replacement)
replaceFirst(String regex, String replacement)
replaceAll(String regex, String replacement)
(5)分割
割为数组: split(String regex) split(String regex, int limit)
割成子串: substring(int beginIndex) subSequence(int beginIndex, int endIndex)
substring(int beginIndex, int endIndex)
(6)変換
转为字节: getBytes() getBytes(Charset charset)
getBytes(int srcBegin, int srcEnd, byte dst[], int dstBegin)
转为字符: toCharArray()
转为字符串: toString()
大小写转换: toUpperCase() toUpperCase(Locale locale)
toLowerCase() toLowerCase(Locale locale)
(7)比較
整串比较: equals(Object anObject) contentEquals(CharSequence cs)
compareTo(String anotherString)
内部类比较器: CaseInsensitiveComparator类
整或子串比较: startsWith(String prefix) startsWith(String prefix, int toffset)
endsWith(String suffix)
contains(CharSequence s)
compareToIgnoreCase(String str)
equalsIgnoreCase(String anotherString)
(8)その他
static join(CharSequence delimiter, CharSequence... elements)
native String intern() // 这个native方法和常量池有关
static valueOf(Object o) // 这个静态valueOf方法对不同的数据类型有同样的操作。
開始前
主要なAPIはおそらくこれらであり、使用率は比較的高いです。見た目はよく見えますが、多くのメソッドが相互に実装されており、非常に単純なメソッドがいくつかあります。したがって、分析する方法は多くありません。もちろん、Apiに加えて、これらのApiをサポートするコンストラクターと内部プライベートメソッドもあります。この部分は、対応するApiメソッドで結合および分析されます。
Stringの内部プロパティは非常に単純です。char配列とハッシュ値は内部で維持されます。デフォルトでは0です。
/** The value is used for character storage. */
private final char value[];
/** Cache the hash code for the string */
private int hash; // Default to 0
他のものは、この配列の拡張に基づいて非常に用途が広いです。
ソースコードを段階的に分析してみましょう。
1.増やす
concat(String str)
ソースコードは次のように導入されています。
* Concatenates the specified string to the end of this string.
* 翻译: 将指定字符串追加在末尾
ソースコードの実装:
// 整个思路还是很简单的。
public String concat(String str) {
int otherLen = str.length(); // 获取指定字符串长度
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen); // 新建一个长度总和的数组
str.getChars(buf, len); // 把指定字符串的字符,赋值到新数组的后面。这个新数组就已装下了两者之和。
return new String(buf, true); // 把新数组构建成字符串
}
2.削除
trim()
trim()メソッドはtrimメソッドとも呼ばれます。ユーザーの視点からは、このような質問がよくありますが、入力データは正しいのですが、見つからない、プロンプトが間違っているのはなぜですか?ユーザーの観点からは、スペースはインターフェイス上に表示されないため、前後にスペースがあるかどうかは気にしません。この操作に基づいて、trim()メソッドは内部でトリミングメソッドを実装します。
ソースコードの実装:
public String trim() {
int len = value.length; // 尾指针 (从尾向头查找)
int st = 0; // 头指针 (从头向尾查找)
char[] val = value; /* avoid getfield opcode */
// 判断用的是ASCII码对比,空格或者空格以下的字符将会被清理,顺序从头到尾,获取第一个有效索引。
while ((st < len) && (val[st] <= ' ')) {
st++;
}
// 这里也一样,顺序从尾到头。获取最后一个有效字符的索引。
while ((st < len) && (val[len - 1] <= ' ')) {
len--;
}
// 根据头指针 和 尾指针的最终位置,对原字符串进行截取,获取的字符串。
return ((st > 0) || (len < value.length)) ? substring(st, len) : this;
}
三、チェック
长度 : length() isEmpty()
字符 : indexOf(int ch) indexOf(int ch, int fromIndex)
indexOf(String str) indexOf(String str, int fromIndex)
lastIndexOf(int ch) lastIndexOf(int ch, int fromIndex)
lastIndexOf(String str) lastIndexOf(String str, int fromIndex)
charAt(int index)
この部分はクエリに関連しています。length()メソッドとisEmput()メソッドは非常に単純なので、ここでは紹介しません。この段落では、文字列内の文字を取得することに焦点を当てています。
キャラクター獲得には9つの関連する方法があり、それらの間の関係と機能はここではグラフ構造で表されます。
この図から、Stringクラスが文字列に対して2方向のインデックスを提供していることがはっきりとわかります。上記のIndexOf()メソッドは、最初から最後まで順番にトラバースします。lastIndexOf()は、最後から最初まで順番にトラバースします。それぞれの実装には低レベルのメソッドがありますが、原則は似ています。したがって、ここでは1つのindexOf()メソッドグループのみが分析され、反対側のlastIndexOf()メソッドグループも同様です。
IndexOfには、intパラメータータイプとStringパラメータータイプがあります。違いは、IntegerのparseInt()メソッドに似ています。これらの2つのパラメーターから派生した2セットのメソッドと基礎となる実装を分析してみましょう。indexOf()メソッドとlastIndexOf()メソッドはどちらも、検索文字のインデックスを返します。
charAt(int index)は、添え字に従って対応する文字を検索することであり、上記の逆です。
(1)indexOf(int ch)
indexOf(int ch)ソースコード:
// 内部默认从下标 0 开始。返回的是一个出现ch的第一个下标。ch是什么呢?
// 源码中有解释,ch是一个Unicode码点,每一个字符在Unicode中都对应一个码点。所以ch其实是一个字符
* @param ch a character (Unicode code point).
*
public int indexOf(int ch) {
return indexOf(ch, 0);
}
indexOf(int ch、int fromIndex)ソースコード:
// 参数多了一个fromIndex
* @param fromIndex the index to start the search from.
* 翻译: 指定从第几个下标开始
*
public int indexOf(int ch, int fromIndex) {
final int max = value.length;
if (fromIndex < 0) {
fromIndex = 0; //也就是说起点允许为负数,不会报错
} else if (fromIndex >= max) {
// Note: fromIndex might be near -1>>>1. -1>>>1 指的是最大值2^31
return -1;
}
//下面以 Character.MIN_SUPPLEMENTARY_CODE_POINT 为界,即补充码点最小值,暂时可以理解为一个临界点。
if (ch < Character.MIN_SUPPLEMENTARY_CODE_POINT) {
// handle most cases here (ch is a BMP code point or a
// negative value (invalid code point))
final char[] value = this.value;
for (int i = fromIndex; i < max; i++) {
if (value[i] == ch) {
return i; // 这里通过遍历获得ch码点对应的字符,在串中的下标值。
}
}
return -1;
} else {
// 如果超过临界点,则调用 下面这个方法
return indexOfSupplementary(ch, fromIndex);
}
}
indexOfSupplementary(int ch、int fromIndex)ソース码:
private int indexOfSupplementary(int ch, int fromIndex) {
if (Character.isValidCodePoint(ch)) {
final char[] value = this.value;
final char hi = Character.highSurrogate(ch);
final char lo = Character.lowSurrogate(ch);
final int max = value.length - 1;
for (int i = fromIndex; i < max; i++) {
if (value[i] == hi && value[i + 1] == lo) {// 这里也是通过循环遍历 来寻找对应的码点,
return i; // 然后找到字符所在串中的下标。具体的码点相关知识这里不做补充
}
}
}
return -1;
}
(2)indexOf(String str)
indexOf(String str)ソースコード:
* @param str the substring to search for.
* // str指的是目标字符串,其源码内部是引用IndexOf(String str, Int fromIndex)
*
public int indexOf(String str) {
return indexOf(str, 0);
}
indexOf(String str、int fromIndex)ソースコード:
// 其源码内部直接调用的底层方法,因此下面详细分析底层方法。
// fromIndex 是指从哪个下标开始。
public int indexOf(String str, int fromIndex) {
return indexOf(value, 0, value.length,
str.value, 0, str.value.length, fromIndex);
}
indexOf(char [] source、int sourceOffset、int sourceCount、char [] target、int targetOffset、int targetCount、int fromIndex)ソースコード: [これは一時的に理解されていません。変数sourceCountとtargetCountは理解されていません]
/**
* Code shared by String and StringBuffer to do searches. The
* source is the character array being searched, and the target
* is the string being searched for.
* 翻译: 这是一个由String 和 StringBuffer 共享的方法
*
* @param source the characters being searched. // 源字符串
* @param sourceOffset offset of the source string. // 源串中的偏移量,意思就是从开始寻找的位置偏移后的下标,比如字符串“abcdefg”,开始遍历下标为1,但是偏移量为2,因此开始下标就要从1+2=3开始。
* @param sourceCount count of the source string. // 源串计数变量
* @param target the characters being searched for. // 目标字符串
* @param targetOffset offset of the target string. // 目标串中的偏移量
* @param targetCount count of the target string. // 目标串计数变量
* @param fromIndex the index to begin searching from. // 源串中的开始下标
*/
static int indexOf(char[] source, int sourceOffset, int sourceCount,char[] target,
int targetOffset, int targetCount, int fromIndex) {
if (fromIndex >= sourceCount) {
return (targetCount == 0 ? sourceCount : -1);
}
if (fromIndex < 0) {
fromIndex = 0;
}
if (targetCount == 0) {
return fromIndex;
}
char first = target[targetOffset];
int max = sourceOffset + (sourceCount - targetCount);
for (int i = sourceOffset + fromIndex; i <= max; i++) {
/* Look for first character. */
if (source[i] != first) {
while (++i <= max && source[i] != first);
}
/* Found first character, now look at the rest of v2 */
if (i <= max) {
int j = i + 1;
int end = j + targetCount - 1;
for (int k = targetOffset + 1; j < end && source[j]
== target[k]; j++, k++);
if (j == end) {
/* Found whole string. */
return i - sourceOffset;
}
}
}
return -1;
}
4、変更
重点分析:replace(char oldChar, char newChar)
非重点分析:replace(CharSequence target, CharSequence replacement)
replaceFirst(String regex, String replacement)
replaceAll(String regex, String replacement)
上記の4つの方法はすべて置き換えであり、さまざまなビジネスニーズに合わせて拡張されたAPIです。その中で、replace(char oldChar、char newChar)メソッドについてのみここで説明します。
(1)Replace(char oldChar、char newChar)ソースコード:
// 这是一个替换字符操作的Api,不是字符串的操作。
public String replace(char oldChar, char newChar) {
if (oldChar != newChar) {
int len = value.length;
int i = -1;
char[] val = value; /* avoid getfield opcode */
// 下面遍历,找出目标字符在字符串中的第一个下标 i
while (++i < len) {
if (val[i] == oldChar) {
break;
}
}
if (i < len) {
// 将第一个下标前的所有字符赋值到新数组中
char buf[] = new char[len];
for (int j = 0; j < i; j++) {
buf[j] = val[j];
}
// 从第一个目标的下标开始再次向后遍历,如果是目标字符,则用newChar 来替换。
while (i < len) {
char c = val[i];
buf[i] = (c == oldChar) ? newChar : c;
i++;
}
// 最后将新数组生成字符串,返回。
return new String(buf, true);
}
}
return this;
}
ソースコードはおそらくこんな感じですが、ソースコードを読んだ後はとても戸惑いました!
なぜそんなに複雑なのですか?文字列を直接トラバースし、それを判断するためにifを追加するだけです。その時間計算量と空間計算量は低くなります。パズルの下で、私は自分が表現したコードを投稿し、偉大な神にソースコードの意味を指摘し、その深遠さを指摘するように頼みました。
// StringMo 是我模拟String的一个类,内部也是维护着一个字符数组。下面贴出replace方法,我觉得可以直接如下这样:
public StringMo replace(char oldChar,char newChar){
if (oldChar == newChar){
return this;
}
char[] arr = val;
for (int i = 0; i < arr.length; i++) {
if (arr[i] == oldChar){
arr[i] = newChar;
}
}
return new StringMo(val);
}
5、セグメンテーション
割为数组: split(String regex) split(String regex, int limit)
割成子串: substring(int beginIndex) subSequence(int beginIndex, int endIndex)
substring(int beginIndex, int endIndex)
文字列のセグメンテーションには2つのタイプがあります。1つは文字列配列に分割する方法で、もう1つは部分文字列に分割する方法です。
配列への分割はsplit()メソッドであり、2つのオーバーロードされたメソッドがあります。この方法を以下で分析してみましょう。
(1)分割(文字列正規表現)
// regex 就是正则表达式,或叫做分割条件,如: “a-b-c-d”.split(”-“)得到的就是a b c d四个字符串组成的字符串数组。
public String[] split(String regex) {
return split(regex, 0); // 这个0 是指,数组里面的空字符串单元将会被去掉。详情原因请看下面的方法。
}
(2)分割(文字列正規表現、整数制限)
split(String regex)とは異なり、追加の入力パラメーター制限があり、これはソースコードで導入されています。
* @param limit
*
* <p>{@code limit}参数控制应用模式,因此会影响结果的长度阵列。
* 如果极限limit大于零,则模式最多应用limit-1次,数组的长度将不大于limit,
* 并且数组的最后一个条目将包含最后一个匹配分隔符以外的所有输入。
* 如果limit如果为非正,则该模式将被应用可能,数组可以有任意长度。
* 如果limit为零,则该模式将被尽可能多次应用,数组可以任何长度,都将丢弃尾随的空字符串。
*
素人の言葉で言えば、limitはこのメソッドのモードを制御します。正、非正、ゼロの3つのモードがあります。制限とは、分割の最大数を意味します(分割の最大数が制限を超えている場合でも)。
関連するソースコードは次のとおりです。
public String[] split(String regex, int limit) {
char ch = 0;
// 条件这一块做了分割,好看点。 整个结构是 (a1 || a2) && b
if (((regex.value.length == 1 && ".$|()[{^?*+\\".indexOf(ch = regex.charAt(0)) == -1) ||
// a1: 上面这句的意思是,如果正则表达式只有一个字符,并且不是字符串".$|()[{^?*+\\"中的一个,则为true。
(regex.length() == 2 && regex.charAt(0) == '\\' && (((ch = regex.charAt(1))-'0') | ('9'-ch)) < 0 &&
((ch-'a')|('z'-ch)) < 0 && ((ch-'A')|('Z'-ch)) < 0))
// a2: 如果长度为2,并且第一个字符为 \,并且 第二个字符 ( ch - '0' | '9' - ch ) < 0 ,才为true。中间是或运算,计算机中0 为 正,1为负。或运算小于0的条件就是,字符其中一个为负。所以这个意思就是 ch 不能是在 ‘0’ ~ ‘9’(包含)之间 并且 不在‘a’ ~ 'z' 之间,并且不在 'A'~'Z'之间。
&&
(ch < Character.MIN_HIGH_SURROGATE || ch > Character.MAX_LOW_SURROGATE))
// b: 最后要求ch 在Character.MIN_HIGH_SURROGATE 到 Character.MAX_LOW_SURROGATE 之外。
{
int off = 0;
int next = 0;
boolean limited = limit > 0; // 模式控制
ArrayList<String> list = new ArrayList<>();
// 分割字符串后用来存放的容器
while ((next = indexOf(ch, off)) != -1) {
// off初始值为0,next 为字符串中每一次 ch 出现的下标。它会随着偏移值的改变而改变。
/**
* limit 模式为 零 时 或模式为 正 时
*
if (!limited || list.size() < limit - 1) {
list.add(substring(off, next));
// 将每一个目标串分割出来 放入list中
off = next + 1;
// 计算下一次循环从哪里开始,off可以看做下一次开始遍历的起点。
// 举个例子:“a-ab-abc-d".splict(”-“), off第一次的偏移量是0,next是”-“第一次的下标。
// 所以下一次开始应该是从next+1 ,开始向后索引第二个“-”; 直到next == -1跳出循环
} else { // last one
/**
* limit 模式为正时的最后一段
*
//assert (list.size() == limit - 1);
list.add(substring(off, value.length)); // limit限值下的最后末尾一段装进list中,如果limit = 1,则就是原串。
off = value.length; // 偏移值移到最后
break;
}
}
// If no match was found, return this
// 如果没有进行分割,则返回一个包含原串的数组
if (off == 0)
return new String[]{this};
// Add remaining segment
// 如果是模式零,添加最后剩下的一段。因为索引只到达最后一个目标字符,不到达字符串的末尾。
// 如果是模式正 , 添加了最后一段。与上面的else 不同的是,上面else的情况是指可分割次数 > limit情况下。
// 这个是指 可分割次数 <= limit 的情况下。
if (!limited || list.size() < limit)
list.add(substring(off, value.length));
// Construct result
int resultSize = list.size();
// 如果模式为 零 , 则会去除里面的空字符串。
if (limit == 0) {
while (resultSize > 0 && list.get(resultSize - 1).length() == 0) {
resultSize--;
}
}
//最后构造成一个数组并返回。
String[] result = new String[resultSize];
return list.subList(0, resultSize).toArray(result);
}
// Pattern.compile这个部分不做研究
return Pattern.compile(regex).split(this, limit);
}
最後に、これら2つのメソッドの簡単な要約:
内部的にsplit(String regex、0 [int limit])と呼ばれるsplit(String regex)。0はモードを表します
。0の場合、文字列は分割の最大数に従って分割され、分割によって生成された空の文字列は削除されます。
0より大きい場合、実際の分割状況は、分割の最大数と制限の関係に基づいている必要があります。2つのタイプがあります。
(1)分割数>制限。制限+1の長さの部分文字列配列は次のようになります。得られた。
(2)分割数<=制限;最大分割数の部分文字列配列が取得されます。
(3)limit = 0;分割できる部分文字列の最大数と、空の文字列を削除した後に生成される部分文字列配列を取得します。
(3)部分文字列(int beginIndex)
subStringはインターセプトに属する必要があり、ソース文字列の一部を新しい文字列としてインターセプトします。内部ソースコードは次のとおりです。
// 代码比较简答,有一个注释特别重要,
* @param beginIndex the beginning index, inclusive.
* 翻译: 下标beginIndex 将会包含在内。
*
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
上記のsubStringは、指定された添え字から最後までインターセプトされ、指定された添え字の文字を含みます。
(4)部分文字列(int beginIndex、int endIndex)
コードは似ていますが、コメントは非常に重要です。
* @param beginIndex the beginning index, inclusive.
* @param endIndex the ending index, exclusive.
* // 翻译: 起点下标包含在内,结束下标不包含在内。也就是说这是一个左闭右开的方法。
*
public String substring(int beginIndex, int endIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
if (endIndex > value.length) {
throw new StringIndexOutOfBoundsException(endIndex);
}
int subLen = endIndex - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
return ((beginIndex == 0) && (endIndex == value.length)) ? this
: new String(value, beginIndex, subLen);
}
(5)subSequence(int beginIndex、int endIndex)
内部的に呼び出されるsubstring(beginIndex、endIndex);は、左が閉じている間隔と右が開いている間隔でもあります。
public CharSequence subSequence(int beginIndex, int endIndex) {
return this.substring(beginIndex, endIndex);
}
6、変換
转为字节: getBytes() getBytes(Charset charset) // 转换为字节 或者 按指定编码转换为字节
转为字符: toCharArray() // 转换为char 数组
转为字符串: toString()
大小写转换: toUpperCase() toUpperCase(Locale locale) // 全部转换为大写
toLowerCase() toLowerCase(Locale locale) // 全部转换为小写
この部分は比較的単純です。
セブン、比較
文字列比較部分、
整串比较: equals(Object anObject) contentEquals(CharSequence cs)
compareTo(String anotherString)
内部类比较器: CaseInsensitiveComparator类
整或子串比较: startsWith(String prefix) startsWith(String prefix, int toffset)
endsWith(String suffix)
contains(CharSequence s)
compareToIgnoreCase(String str)
equalsIgnoreCase(String anotherString)
まず、シリーズ全体の比較を見てみましょう。
(1)equals(Object anObject)
そのパラメーターはオブジェクトです。つまり、任意のオブジェクトを渡すことができます。内部比較は、3つのレベル(オブジェクトタイプ、長さ、1文字の比較)に分けられます。
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) { // 判断对象类型
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) { // 判断字符串长度
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i]) // 单个字符对比,包括字符顺序。
return false;
i++;
}
return true;
}
}
return false;
}
(2)contentEquals(CharSequence cs)
これは入力パラメーターであり、文字のシーケンスです。つまり、String、StringBuffer、StringBuilderをすべて渡すことができます。したがって、内部で型判定を行います。このメソッドは基本的にequals()と同じです。
public boolean contentEquals(CharSequence cs) {
// Argument is a StringBuffer, StringBuilder
if (cs instanceof AbstractStringBuilder) {
if (cs instanceof StringBuffer) {
synchronized(cs) {
return nonSyncContentEquals((AbstractStringBuilder)cs);
}
} else {
return nonSyncContentEquals((AbstractStringBuilder)cs);
}
}
// Argument is a String
if (cs instanceof String) {
return equals(cs);
}
// Argument is a generic CharSequence
char v1[] = value;
int n = v1.length;
if (n != cs.length()) {
return false;
}
for (int i = 0; i < n; i++) {
if (v1[i] != cs.charAt(i)) {
return false;
}
}
return true;
}
(2)compareTo(String anotherString)
このメソッドについて注意することは、その戻り値がintであり、これが違いであるということです。差はどのように計算されますか?
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; // 差值返回的是从头开始遍历,第一个不同字符的ASCII码差值。
}
k++;
}
return len1 - len2; // 否则返回两者的长度差,其实到了这里,其中一个字符串肯定为另一个字符串的子串。
}
8.その他
join(CharSequence delimiter, CharSequence... elements)
join(CharSequence delimiter, Iterable<? extends CharSequence> elements)
native String intern() // 这个native方法和常量池有关
static valueOf(Object o) // 这个静态valueOf方法对不同的数据类型有同样的操作。
(1)join()メソッド
まず、join()メソッドを見てみましょう。2つのオーバーロードがあります。パラメータタイプはCharSequenceで、可変長のパラメータリスト要素があります。
a。
CharSequenceはjava.langパッケージの下のインターフェースであり、文字列の動作を表すために使用され、そのサブクラスには通常、String、StringBuffer、およびStringBuilderの3つの実装があります。したがって、CharSequenceはこれら3つのクラスの親と見なすことができます。この親クラスをパラメーターとして使用すると、さまざまなサブクラスを渡すことができます。これは一種のJavaポリモーフィズムの実装であり、上方参照とも呼ばれます。したがって、CharSequenceをパラメーターとして使用する場合は、String、StringBuffer、またはStringBuilderを渡すことができます。
b。
可変長パラメーターリストは、Java 5.0以降の新しい定義です。パラメーターの後に、次のように3つのドットが追加されます。Object...。これは、Object配列または複数のObjectオブジェクトパラメーターを渡す必要があることを意味します。
join(CharSequence delimiter、CharSequence ...要素)メソッドでは、join( "-"、 "a"、 "b"、 "c");
を渡すこともできます。join( "-"、 ")を渡すこともできます。a "、" b ");または、join("-"、StringBuffer []);を渡すことができます。
join(CharSequence delimiter, CharSequence... elements)
join(CharSequence delimiter, Iterable<? extends CharSequence> elements)
join()メソッドの機能は、最初の入力パラメーター文字列区切り文字を使用して、後続のパラメーターリストの各文字列を接続することです。これは静的メソッドです。これはツールクラスと同等です。
(2)ValueOf()メソッドグループ
これは、パッケージングクラスの基本タイプのvalueOfに似ています。これは、入力パラメーターを使用して新しい文字列を作成します。各メソッドには新しいものがあります。入力パラメーターには、ここにリストされていない8つの基本データタイプ、オブジェクトタイプなどが含まれます。 。。
public static String valueOf(Object obj) { return (obj == null) ? "null" : obj.toString() }
public static String valueOf(char data[]) { return new String(data); }
public static String valueOf(char data[], int offset, int count) {
return new String(data, offset, count);
}
public static String valueOf(boolean b) { return b ? "true" : "false";}
...
(3)ネイティブ文字列intern()メソッド
この方法には、定数プールが含まれます。これはローカルメソッドです。そのコメントはこのように書かれています。
/**
* <p>
* A pool of strings, initially empty, is maintained privately by the
* class {@code String}.
* 翻译: String 的常量池中,最开始是空的,由String类维护着。
* <p>
* When the intern method is invoked, if the pool already contains a
* string equal to this {@code String} object as determined by
* the {@link #equals(Object)} method, then the string from the pool is
* returned.
* 翻译: 当intern()方法被调用时,如果常量池中,已经存在一个通过equal方法比较和这个字符串相等的对象了,
* 则返回常量池中的这个对象。
* Otherwise, this {@code String} object is added to the
* pool and a reference to this {@code String} object is returned.
* 翻译:否则,就把这个对象加入到常量池中,然后返回常量池中的这个对象。
* <p>
* It follows that for any two strings {@code s} and {@code t},
* {@code s.intern() == t.intern()} is {@code true}
* if and only if {@code s.equals(t)} is {@code true}.
* 翻译:任意两个字符串 s 和 t,如果要满足 s.intern() = t.intern(),则他们一定满足 s.equals(t);
* 不知道我理解得对不对。
* <p>
* All literal strings and string-valued constant expressions are
* interned.
* 翻译:所有的常量表达式都被调用了 intern() 方法。
*
デモンストレーションの例
char[] s = {'a'};
String t = new String(s);
String m = t.intern();
System.out.println(t == m );
// 结果: ture
这个例子说明了,对象 t 被创建在堆中,常量池 m 只是引用堆中的地址。
デモンストレーションの例
char[] t = {'a'};
String s = new String(t);
String a = "a";
System.out.println(a == s);
// 结果: false
这个例子说明了,两种不同创建方式,在堆里创建了两个对象。
デモンストレーションの例
char[] t = {'a'};
String s = new String(t);
s.intern();
String a = "a";
System.out.println(a == s);
// 结果:true
这个例子说明了,通过intern()方法,将s指向的对象,加入了String的常量池。变量a会先去常量池中
寻找,发现有一个a了,于是引用了常量池这个地址,而常量池这个地址就是 s 的地址。
デモンストレーションの例
String b = "a";
String a = "a";
System.out.println(a == b);
结果: true
// 这个例子说明,变量a 和变量b 都引用的同一个对象,也只生成了一个对象。
这个对象肯定不是变量a的时候生成的,所以一定是变量b的时候生成的。
变量a 没有生成对象,只是引用了变量b 指向的对象。
所以判断出这样直接赋值时,会先去常量池中寻找,如果没有,才会在堆中创建一个新的对象。
デモンストレーションの例
String b = new String("a");
b.intern();
String a = "a";
结果:false
// 这个例子反向推理,因为是false,变量a会先到常量池查找,发现有一个“a”,但是不是对象b,
所以在执行b.intern( )方法前,常量池中已经有了一个对象,这个对象从哪里来呢?
在变量b 构造对象之前,也就是new之前,需要一个入参,这个入参就是“a”字符串.所以在获得这个入参时,
程序默认执行了 类似: String xxx = "a" 的操作。只不过这个操作是系统执行的,没有xxx变量引用。