さあ、文字列について勉強しても良いことは何もありません。
そんなことは言わないで、「The Ordinary World」を見たことがありますか? 普通の文字列にも特別な物語がありえます。プレビュー:
(1) C言語で文字列を計算するstrlenの時間計算量はどれくらいですか? PHPではどうなるでしょうか?
(2) PHP でマルチバイト文字列を扱うにはどうすればよいですか? PHP の Unicode サポートはどうなっていますか?
これも文字列ですが、C 言語と C++/PHP/Java はなぜ違うのですか?
データ構造がアルゴリズムを決定する、この文はまさに真実です。
今日は、PHP の文字列構造と、関連する文字列関数の実装について見ていきます。
1. 文字列の基本
文字列は、PHP で最も頻繁に使用されるデータ構造の 1 つであると言えます (もう 1 つのより一般的に使用されるデータ構造は配列です。「変数 (4) - PHP カーネル探索における配列操作」を参照してください)。 PHP 言語の特性とアプリケーション シナリオにより、私たちの日常タスクの多くは実際に文字列を処理します。このため、PHP は開発者に豊富な文字列操作関数を提供しています (予備統計によると、その数は約 100 で、これはかなりの数です)。では、PHP では文字列はどのように実装されるのでしょうか? C言語との違いは何ですか?
1. PHP での文字列の表現
PHP では文字列を使用する一般的な形式が 4 つあります。
(1) 二重引用符
次の形式がより一般的です: $str="これは
(2) シングルクォーテーションマーク
一重引用符で囲まれた文字は未処理とみなされ、一重引用符で囲まれた変数、制御文字などは解析されません。
$string = "テスト";
$str = 'これは $string です、ああ、';
echo $str;
(3) ヒアドキュメント
ヒアドキュメントは、より長い文字列表現に適しており、複数行の文字列表現にはより柔軟で多用途です。二重引用符表現と同様に、ヒアドキュメントに変数を含めることもできます。一般的な形式は次のとおりです:
$string ="テスト文字列";
$str = <<
私の文字列は $string です
ストラ;
echo $str;
(4) nowdoc (5.3 以降をサポート)
nowdoc と heredoc は非常に似ているため、兄弟のように考えることができます。 nowdoc の開始識別子は一重引用符で囲まれています。一重引用符と同様に、変数やフォーマット制御文字などは解析されません。
$s = <<<'EOT'
これは $str
これはテストです;
EOT;
echo $s;
2. PHP の文字列の構造
前述したように、PHP の変数は Zval (PHP カーネル探索変数 (1) Zval) などの構造体を使用して格納されます。 Zval の構造は次のとおりです:
struct _zval_struct {
zvalue_value 値; /* 値 */
zend_uint refcount__gc; /* 変数の参照カウント */
zend_uchar タイプ; /* アクティブなタイプ */
zend_uchar is_ref__gc; /* ref 変数の場合 */
};
そして、変数の値は zvalue_value のような共用体です:
typedef Union _zvalue_value {
長い lval;
double dval;
struct { /* 文字列 */
char *val;
int len;
} str;
ハッシュテーブル *ht;
zend_object_value obj;
} zvalue_value;
そこから文字列の構造を抽出します:
構造体 {
char *val;
int len;
} str;
PHP の文字列は実際には、文字列へのポインタと文字列の長さを含む最下位レベルの構造体であることが明らかになりました。
では、なぜこれを行うのですか?言い換えれば、これを行うことでどのようなメリットがあるのでしょうか?次に、PHP 文字列と C 言語文字列を比較して、このような構造を使用して文字列を格納する利点を説明します。
3. C言語の文字列との比較
C 言語では、文字列を 2 つの一般的な形式で格納できることがわかっています。1 つはポインターを使用し、もう 1 つは文字配列を使用します。次の手順では、C 言語の文字配列を使用して文字列を格納します。
(1) PHP 文字列はバイナリ セーフですが、C 文字列はバイナリ セーフではありません。
「バイナリ セキュリティ」という用語がよく出てきますが、バイナリ セキュリティとは正確には何を意味するのでしょうか?
ウィキペディアでの Binary Safe の定義は次のとおりです:
バイナリセーフは、主に文字列操作関数に関連して使用されるコンピュータープログラミング用語です。
バイナリセーフ関数は、本質的に、入力を特定の形式を持たない生のデータ ストリームとして扱う関数です。
したがって、文字が取り得る 256 個のすべての値で動作するはずです (8 ビット文字を想定)。
翻訳:
バイナリ セキュリティはコンピュータ プログラミング用語で、主に文字列操作関数に使用されます。バイナリ セーフ関数とは、基本的に、入力を特別なフォーマットを行わずに生のデータ ストリームとして扱うことを意味します。
では、なぜ C 文字列はバイナリセーフではないのでしょうか? C 言語では、文字配列で表される文字列は常に次で終わることが知られています。
(2) 効率の比較。
C文字列で使用されるので
構造体{
char *val;
int len;
} str;
は次のような構造で表現されるため、文字列の長さの取得は定数時間で完了できます。
#define Z_STRLEN(zval) (zval).value.str.len
もちろん、strlen 関数のパフォーマンスだけでは、「PHP の文字列は C 文字列よりも効率的である」という結論を裏付けることはできません (明らかな理由は、PHP が C 言語をベースに構築された高級言語であるためです)。これは、時間の計算量の点で、PHP 文字列の方が C 文字列よりも効率的であることを意味します。
(3) 多くの C 文字列関数にはバッファ オーバーフローの脆弱性があります
バッファ オーバーフローは C 言語の一般的な脆弱性であり、このセキュリティ リスクは多くの場合致命的です。バッファ オーバーフローの典型的な例は次のとおりです:
void str2Buf(char *str) {
文字バッファ[16];
strcpy(buffer,str);
}
この関数は str の内容をバッファ配列にコピーします。バッファ配列のサイズは 16 であるため、str の長さが 16 より大きい場合、バッファ オーバーフローの問題が発生します。
strcpy に加えて、gets、strcat、fprintf などの文字列関数にもバッファ オーバーフローの問題があります。
PHP には strcpy や strcat などの関数はありません。実際、PHP 言語は単純であるため、strcpy や strcat などの関数を提供する必要はありません。たとえば、文字列をコピーしたい場合は、 =:
を使用するだけです。
$str = "これは文字列です";
$str_copy = $str;
PHP の変数共有 zval の特性により、スペースの無駄がなく、簡単に文字列接続を実現できます。
$str = "これは";
$str .= "テスト文字列";
echo $str;
文字列連結プロセス中のメモリの割り当てと管理については、zend エンジン部分の実装を確認できますが、ここでは今のところ無視しています。
2. 文字列操作関連関数(一部)
文字列を学習する目的は、その構造や特性を知ることだけではなく、より良く使用することであることに疑いの余地はありません。私たちの日常業務では、日付文字列の処理、パスワードの暗号化、ユーザー情報の取得、正規表現の一致と置換、文字列の置換、文字列の書式設定など、ほとんどの作業に文字列の処理が含まれます。 PHP 開発では、(呼吸から逃れられないのと同じように) 文字列との直接的または間接的な接触を避けることはできないと言えます。このため、PHP は開発者に、文字列操作の 90% 以上に適した豊富な文字列操作関数 (http://cn2.php.net/manual/en/ref.strings.php) を多数提供しています。 , 基本的には十分です。
文字列関数はたくさんあるので、一つ一つ説明するのは不可能です。ここでは、簡単に説明するために、いくつかの典型的な文字列操作関数のみを選択します (PHPer の 80% 以上は文字列操作関数をよく理解していると思います)。
説明を始める前に、文字列関数を使用する原則を強調する必要があります。文字列関数を効率的かつ熟練して使用するには、これらの原則を理解して習得することが非常に重要です。
(1) 操作で正規表現または文字列を使用できる場合。次に、文字列操作に優先順位を付けます。
正規表現は、テキストを処理するための優れたツールであり、特にパターン検索やパターン置換などのアプリケーションでは無敵であると言えます。このため、正規表現は多くの状況で誤用されます。文字列関数または正規表現を使用して文字列操作を完了できる場合は、正規表現は特定の状況でパフォーマンスに重大な問題を引き起こす可能性があるため、文字列操作関数を優先してください。
(2) false と 0 に注意してください
PHP は弱い変数型です。最初は多くの PHP 使用者がそれに苦しんだと思います
var_dump( 0 == false);//bool(true)
var_dump( 0 === false);//bool(false)
待って、これは文字列操作関数と何の関係があるのでしょうか?
PHP には、検索に使用される関数 (strpos、stripos など) があり、検索が成功すると、この種類の検索関数は strpos など、元の文字列内の部分文字列のインデックスを返します。 🎜>
var_dump(strpos("これは abc", "abc"));
検索が失敗した場合は false が返されます:
var_dump(strpos("this is abc", "angle"));//false
ここには落とし穴があります。文字列のインデックスも 0 から始まります。部分文字列がたまたまソース文字列の先頭に現れた場合、単純な == 比較では strpos が成功したかどうかを区別できません。
var_dump(strpos("これは abc", "これ"));
したがって、=== を使用して比較する必要があります:
if((strpos("これは abc", "これ")) === false){
// 見つかりません
}
(3) 車輪の再発明を避けるために、マニュアルをもっと読んでください。
多くの PHPer インタビューで次の質問に遭遇したと思います: 文字列を反転するにはどうすればよいですか?質問には「方法」のみが記載されているため、「PHP 組み込み関数を使用しない」という制限はありません。したがって、この質問の場合、最も簡単な方法は当然 strrev 関数を使用することです。車輪の再発明をすべきではないことを示すもう 1 つの関数は、levenshtein 関数です。その名前が示すように、この関数は 2 つの文字列間の編集距離を返します。編集は動的計画法(DP)の代表的な事例の一つとして、多くの人に馴染みがあると思います。このような問題が発生した場合でも、DP を開始する準備はできていますか? 1 つの関数がそれを実行します:
$str1 = "これはテストです";
$str2 = "彼はテスです";
エコーレーベンシュタイン($str1, $str2);
私たちは皆、特定の状況ではできるだけ「怠け者」であるべきですよね?
以下は文字列操作関数の抜粋です (最も一般的な操作については、マニュアルを直接参照してください)
1.strlen
このタイトルが出た瞬間、ほとんどの人がこんな表情をしたのではないでしょうか。
または次のようにします:
私が言いたいのは関数そのものではなく、この関数の戻り値です。
int strlen ( string $string )
指定された文字列の長さを返します。
マニュアルには「strlen 関数は指定された文字列の長さを返す」と明確に記載されていますが、長さの単位が「文字数」または「文字列のバイト数」を指すのかについてはまったく説明されていません。文字」。私たちがしなければならないのは推測ではなく、テストすることです:
GBK エンコード形式の場合:
echo strlen("これは中国語です") //8
strlen 関数が文字列のバイト数を返すという説明。次に、utf-8 エンコードの場合、中国語は utf8 エンコードで 3 バイトを使用するため、期待される結果は 12:
になります。
echo strlen("これは中国語です");//12
これは、strlen によって計算される文字列の長さは現在のエンコード形式に依存し、その値は一意ではないことを意味します。場合によっては、これでは当然満足できない場合もあります。現時点では、マルチバイト拡張 mbstring には再生する余地があります:
echo mb_strlen("これは中国語です", "GB2312");//4
この点については、マルチバイト処理で対応する説明があるので、ここでは省略します。
2. str_word_count
str_word_count も強力ですが、見落とされがちな文字列関数です。
mixed str_word_count ( string $string [, int $format = 0 [, string $charlist ]] )
$format の値が異なると、str_word_count 関数の動作が異なる場合があります。 今、このテキストが手元にあります:
私が落ち込んでいて、ああ、心がとても疲れているとき
トラブルが起こり、心に負担がかかるとき
それでは、私はここで静かに待っています
あなたが来て私と一緒にしばらく座るまで
あなたが私を育ててくれたので、私は山の上に立つことができます
あなたは嵐の海を歩けるように私を育ててくれます
あなたの肩に乗ると、私は強くなります
あなたは私を育ててくれます…私ができる以上のものに
あなたが私を育ててくれたので、私は山の上に立つことができます
あなたは嵐の海を歩けるように私を育ててくれます
あなたの肩に乗ると、私は強くなります
あなたは私を力以上に育ててくれます。
その後:
(1)$format = 0
$format=0、$format はテキスト内の単語数を返します:
echo str_word_count(file_get_contents(“word”)) //112
(2)$format = 1
$format=1 の場合、テキスト内のすべての単語の配列が返されます:
print_r(file_get_contents(“word”),1 );
配列
(
[0] => いつ
[1] => 私
[2] => 午前
[3] => 下
[4] => と
[5] => ああ
[6] => 私の
[7] => 魂
[8] => それで
[9] => 疲れた
[10] => いつ
[11] => トラブル
......
)
この機能は何をするものですか?たとえば、英語の分詞。 「単語数」の問題を覚えていますか? str_word_count を使用すると、TopK 単語統計問題を簡単に完了できます:
$s = file_get_contents("./word");
$a = array_count_values(str_word_count($s, 1)) ;
arsort( $a );
print_r( $a );
/*
配列
(
[私] => 10
[私] => 7
[レイズ] => 6
[上] => 6
[あなた] => 6
[午前] => 6
[オン] => 6
[できる] => 4
[そして] => 4
[である] => 3
[だから] => 3
…
);*/
(3)$format = 2
$format=2 の場合、連想配列が返されます:
$a = str_word_count($s, 2);
print_r($a);
/*
配列
(
[0] => いつ
[5] => 私
[7] => 午前
[10] => 下
[15] => と
[20] => ああ
[23] => 私の
[26] => 魂
[32] => それで
[35] => 疲れた
[41] => いつ
[46] => トラブル
[55] => 来てください
...
)*/
他の配列関数を使用すると、より多様な関数を実現できます。たとえば、array_flip を使用すると、単語の最後の出現位置を計算できます。
$t = array_flip(str_word_count($s, 2));
print_r($t);
array_unique と array_flip を組み合わせると、単語が最初に出現する位置を計算できます。
$t = array_flip( array_unique(str_word_count($s, 2)) );
print_r($t);
配列
(
[いつ] => 0
[私] => 5
[午前] => 7
[ダウン] => 10
[そして] => 15
[ああ] => 20
[私] => 23
[魂] => 26
[だから] => 32
[疲れた] => 35
[トラブル] => 46
[来る] => 55
[ハート] => 67
...
)
3. 類似テキスト
これは、2 つの文字列の類似性を計算する、levenshtein() 関数以外の別の関数です。
int like_text ( string $first , string $second [, float &$percent ] )
$t1 = "あなたが私を育ててくれたので、私は山の上に立つことができます";
$t2 = "嵐の海を歩けるように、あなたは私を育ててくれます";
$パーセント = 0;
echo minimum_text($t1, $t2, $percent).PHP_EOL;//26
echo $percent;// 62.650602409639
具体的な使用法とは別に、文字列の根本的な類似性がどのように定義されているかに興味があります。
Similar_text 関数の実装は ext/standard/string.c にあり、そのキー コードを抜粋します:
PHP_FUNCTION(similar_text){
char *t1, *t2;
zval **パーセント = NULL;
int ac = ZEND_NUM_ARGS();
int sim;
int t1_len, t2_len;
/* パラメータ分析 */
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ssZ", &t1, &t1_len, &t2, &t2_len, &percent) == FAILURE) {
戻る;
}
/* パーセントを double 型に設定します */
if (ac > 2) {
Convert_to_double_ex(パーセント);
}
/* t1_len == 0 && t2_len == 0 */
if (t1_len + t2_len == 0) {
if (ac > 2) {
Z_DVAL_PP(パーセント) = 0;
}
RETURN_LONG(0);
}
/* 同一の文字列の数をカウントします */
sim = php_similar_char(t1, t1_len, t2, t2_len);
/* 類似率 */
if (ac > 2) {
Z_DVAL_PP(パーセント) = sim * 200.0 / (t1_len + t2_len);
}
RETURN_LONG(sim);
}
類似する文字列の数は php_similar_char 関数を通じて取得され、類似性のパーセンテージは次の式によって取得されることがわかります。
パーセント = sim * 200 / (t1 文字列長 + t2 文字列長)
を定義します。
php_similar_char の具体的な実装:
static int php_similar_char(const char *txt1, int len1, const char *txt2, int len2)
{
int sum;
int pos1 = 0、pos2 = 0、最大;
php_similar_str(txt1, len1, txt2, len2, &pos1, &pos2, &max);
if ((sum = max)) {
if (pos1 && pos2) {
合計 += php_similar_char(txt1, pos1,txt2, pos2);
}
if ((pos1 + max sum += php_similar_char(txt1 + pos1 + max, len1 - pos1 - max,txt2 + pos2 + max, len2 - pos2 - max); } } 合計を返します; } この関数は、php_similar_str を呼び出して、類似する文字列の数の統計を完了します。php_similar_str は、文字列 s1 と文字列 s2 の間で同一の最長の文字列長を返します。 static void php_similar_str(const char *txt1, int len1, const char *txt2, int len2, int *pos1, int *pos2, int *max) { char *p, *q; char *end1 = (char *) txt1 + len1; char *end2 = (char *) txt2 + len2; int l; *最大 = 0; /* 最長の文字列を検索します */ for (p = (char *) txt1; p for (q = (char *) txt2; q for (l = 0; (p + l if (l > *max) { *max = l; *pos1 = p - txt1; *pos2 = q - txt2; } } } } php_similar_str のマッチングが完了すると、元の文字列は 3 つの部分に分割されます。 最初の部分は最長の文字列の左側の部分です。この部分には同様の文字列が含まれていますが、最長ではありません。
/* 最長の文字列の左側にある類似の文字列 */ if (pos1 && pos2) { 合計 += php_similar_char(txt1, pos1,txt2, pos2); } /* 右半分に同様の文字列 */ if ((pos1 + max sum += php_similar_char(txt1 + pos1 + max, len1 - pos1 - max, txt2 + pos2 + max, len2 - pos2 - max); } マッチングプロセスは次の図に示されています: 文字列関数の詳細については、PHP オンライン マニュアルを参照してください。ここでは 1 つずつ説明しません。 3. マルチバイト文字列 これまで説明してきた文字列および関連する演算関数はすべてシングルバイトです。しかし、赤いスイカや黄色いスイカがあるように、世界はとてもカラフルで、ストリングスも例外ではありません。たとえば、一般的に使用される漢字を GBK でエンコードする場合、実際には 2 バイトを使用してエンコードされます。マルチバイト文字列には中国語の文字に限定されず、日本語、韓国語、その他の国の文字も含まれます。このため、マルチバイト文字列の処理は非常に重要です。 文字と文字セットは、プログラミング プロセスで必然的に遭遇する用語です。このセクションの内容が特によくわからないお子様がいる場合は、「エンコーディング メジャー 1 文字エンコーディングの基本 - 文字と文字セット」に進むことをお勧めします。 私たちは日常生活で中国語を使用することが多いため、中国語の文字列インターセプトを例として取り上げ、中国語の文字列の問題に焦点を当てます。 中国語文字列のインターセプト 中国語文字列のインターセプトは常に比較的厄介な問題です。その理由は次のとおりです。 (1) PHP のネイティブ substr 関数は、シングルバイト文字列のインターセプトのみをサポートしており、マルチバイト文字列に対してはわずかに機能しません (2) PHP 拡張子 mbstring にはサーバーのサポートが必要です。実際、多くの開発環境では mbstring 拡張子が有効になっていません。これは、mbstring 拡張子の使用に慣れている子供にとっては残念です。 (3) さらに複雑な問題は、UTF-8 エンコードの場合、中国語は 3 バイトですが、中国語の一部の特殊文字 (キャレット · など) は実際にはダブルワードのセクション コード化されることです。これにより、間違いなく中国語の文字列を傍受することがより困難になります (結局のところ、中国語の文字列に特殊文字がまったく含まれないことは不可能です)。 頭の痛い問題とは別に、中国語の文字列インターセプト ライブラリを構築する必要があります。この文字列インターセプト関数には、 substr と同様の関数パラメータ リストがあり、中国語の GBK エンコーディングと UTF-8 エンコーディングでのインターセプトをサポートする必要があります。効率を高めるため、サーバーが mbstring 拡張機能を有効にしている場合は、mbstring の文字列インターセプトを直接使用する必要があります。 API: String cnSubstr(string $str, int $start, int $len, [$encode='GBK']);//パラメータ内の $start と $len は文字数ではなく文字数であることに注意してください。バイト。 UTF8 エンコーディングでの中国語のインターセプトのアイデアを説明するために、UTF-8 エンコーディングを例に挙げます。 (1) コーディング範囲: UTF-8 エンコード範囲 (utf-8 は文字のエンコードに 1 ~ 6 バイトを使用しますが、実際には 1 ~ 4 バイトのみを使用します): 1バイト: 00——7F 2バイト: C080——DFBF 3 文字: E08080——EFBFBF 4 文字: F0808080——F7BFBFBF これによれば、文字が占めるバイト数は最初のバイトの範囲に基づいて決定できます。 $ord = ord($str{$i}); $ord
192
224
中国語には 4 バイト文字はありません (2) $start が負の場合 if( $start
$start += cnStrlen_utf8( $str ); if( $start
$start = 0; } } インターネット上のほとんどの文字列インターセプト バージョンは、$start
ここで、cnStrlen_utf8 は、utf8 エンコーディングでの文字列の文字数を取得するために使用されます。 関数 cnStrlen_utf8( $str ){ $len = 0; $i = 0; $slen = strlen( $str ); while( $i < $slen ){ $ord = ord( $str{$i} ); if( $ord
$i ++; }else if( $ord
$i += 2; }その他{ $i += 3; } $len ++; } $len を返す; } UTF-8 のインターセプトアルゴリズムは次のとおりです: 関数 cnSubstr_utf8( $str, $start, $len ){ if( $start
$start += cnStrlen_utf8( $str ); if( $start
$start = 0; } } $slen = strlen( $str ); if( $len
$len += $slen - $start; if($len
$len = 0; } } $i = 0; $count = 0; /* 開始位置を取得します */ while( $i < $slen && $count < $start){ $ord = ord( $str{$i} ); if( $ord
$i ++; }else if( $ord
$i += 2; }その他{ $i += 3; } $count ++; } $count = 0; $substr = '';
を返す
$slice = iconv_substr($str,$start,$length,$charset); if(false === $slice) { $slice = ''; } }その他{ $re['utf-8'] = "/[x01-x7f][xc2-xdf][x80-xbf][xe0-xef][x80-xbf]{2}[xf0-xff][x80] -xbf]{3}/"; $re['gb2312'] = "/[x01-x7f][xb0-xf7][xa0-xfe]/"; $re['gbk'] = "/[x01-x7f][x81-xfe][x40-xfe]/"; $re['big5'] = "/[x01-x7f][x81-xfe]([x40-x7e]xa1-xfe])/"; preg_match_all($re[$charset], $str, $match); $slice = join("",array_slice($match[0], $start, $length)); } 戻り $slice.'...' : $slice; } 記事が長くなるので、さらに質問がありますが、ここでは詳しく説明しません。改めて、ご質問等ございましたら、お気軽にご指摘ください。