相關(guān)學(xué)習(xí)推薦:java基礎(chǔ)教程
初學(xué)Java時(shí)我們已經(jīng)知道Java中可以分為兩大數(shù)據(jù)類型,分別為基本數(shù)據(jù)類型和引用數(shù)據(jù)類型。而在這兩大數(shù)據(jù)類型中有一個(gè)特殊的數(shù)據(jù)類型String,String屬于引用數(shù)據(jù)類型,但又有區(qū)別于其它的引用數(shù)據(jù)類型。可以說它是數(shù)據(jù)類型中的一朵奇葩。那么,本篇文章我們就來深入的認(rèn)識一下Java中的String字符串。
一、從String字符串的內(nèi)存分配說起
上一篇文章《溫故知新--你不知道的JVM內(nèi)存分配》詳細(xì)的分析了JVM的內(nèi)存模型。在常量池部分我們了解了三種常量池,分別為:字符串常量池、Class文件常量池以及運(yùn)行時(shí)常量池。而字符串的內(nèi)存分配則和字符串常量池有著莫大的關(guān)系。
我們知道,實(shí)例化一個(gè)字符串可以通過兩種方法來實(shí)現(xiàn),第一種最常用的是通過字面量賦值的方式,另一種是通過構(gòu)造方法傳參的方式。代碼如下:
String str1="abc"; String str2=new String("abc");復(fù)制代碼
這兩種方式在內(nèi)存分配上有什么不同呢? 相信大家在初學(xué)Java的時(shí)候老師都有給我們講解過:
1.通過字面量賦值的方式創(chuàng)建String,只會(huì)在字符串常量池中生成一個(gè)String對象。 2.通過構(gòu)造方法傳入String參數(shù)的方式會(huì)在堆內(nèi)存和字符串常量池中各生成一個(gè)String對象,并將堆內(nèi)存上String的引用放入棧。
這樣的回答正確嗎?至少在現(xiàn)在看來并不完全正確,因?yàn)樗耆Q于使用的Java版本。上一篇文章《溫故知新--你不知道的JVM內(nèi)存分配》談到HotSpot虛擬機(jī)在不同的JDK上對于字符串常量池的實(shí)現(xiàn)是不同的,摘錄如下:
在JDK7以前,字符串常量池在方法區(qū)(永久代)中,此時(shí)常量池中存放的是字符串對象。而在JDK7中,字符串常量池從方法區(qū)遷移到了堆內(nèi)存,同時(shí)將字符串對象存到了Java堆,字符串常量池中只是存入了字符串對象的引用。
這句話應(yīng)該怎么理解呢?我們以String str1=new String("abc")為例來分析:
1.JDK6中的內(nèi)存分配
先來分析一下JDK6的內(nèi)存分配情況,如下圖所示:

當(dāng)調(diào)用new String("abc")后,會(huì)在Java堆與常量池中各生成一個(gè)“abc”對象。同時(shí),將str1指向堆中的“abc”對象。
2.JDK7中的內(nèi)存分配
而在JDK7及以后版本中,由于字符串常量池被移到了堆內(nèi)存,所以內(nèi)存分配方式也有所不同,如下圖所示:

當(dāng)調(diào)用了new String("abc")后,會(huì)在堆內(nèi)存中創(chuàng)建兩個(gè)“abc"對象,str1指向其中一個(gè)”abc"對象,而常量池中則會(huì)生成一個(gè)“abc"對象的引用,并指向另一個(gè)”abc"對象。
至于Java中為什么要這么設(shè)計(jì),我們在上篇文章中也已經(jīng)解釋了: 因?yàn)镾tring是Java中使用最頻繁的一種數(shù)據(jù)類型,為了節(jié)省程序內(nèi)存提高程序性能,Java的設(shè)計(jì)者們開辟了一塊字符串常量池區(qū)域,這塊區(qū)域是是所有類共享的,每個(gè)虛擬機(jī)只有一個(gè)字符串常量池。因此,在使用字面量方式賦值的時(shí)候,如果字符串常量池中已經(jīng)有了該字符串,則不會(huì)在堆內(nèi)存中重新創(chuàng)建對象,而是直接將其指向了字符串常量池中的對象。
二、String的intern()方法
在了解了String的內(nèi)存分配之后,我們需要再來認(rèn)識一下String中一個(gè)很重要的方法:String.intern()。
很多讀者可能對于這一方法并不是太了解,但并不代表他不重要。我們先來看一下intern()方法的源碼:
/** * Returns a canonical representation for the string object. * <p> * A pool of strings, initially empty, is maintained privately by the * class {@code 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. 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}. * <p> * All literal strings and string-valued constant expressions are * interned. String literals are defined in section 3.10.5 of the * <cite>The Java™ Language Specification</cite>. * * @return a string that has the same contents as this string, but is * guaranteed to be from a pool of unique strings. */ public native String intern();復(fù)制代碼
emmmmm....居然是一個(gè)native方法,不過沒關(guān)系,即使看不到源碼我們也能從其注釋中得到一些信息:當(dāng)調(diào)用intern方法的時(shí)候,如果字符串常量池中已經(jīng)包含了一個(gè)等于該String對象的字符串,則直接返回字符串常量池中該字符串的引用。否則,會(huì)將該字符串對象包含的字符串添加到常量池,并返回此對象的引用。
1.一個(gè)關(guān)于intern()的簡單例子
了解了intern方法的用途之后,來看一個(gè)簡單的列子:
public class Test { public static void main(String[] args) { String str1 = "hello world"; String str2 = new String("hello world"); String str3=str2.intern(); System.out.println("str1 == str2:"+(str1 == str2)); System.out.println("str1 == str3:"+(str1 == str3)); } }復(fù)制代碼
上面的一段代碼會(huì)輸出什么?編譯運(yùn)行之后如下:

如果理解了intern方法就很容易解釋這個(gè)結(jié)果了,從上面截圖中可以看到,我們的運(yùn)行環(huán)境是JDK8。
String str1 = "hello world"; 這行代碼會(huì)首先在Java堆中創(chuàng)建一個(gè)對象,并將該對象的引用放入字符串常量池中,str1指向常量池中的引用。
String str2 = new String("hello world");這行代碼會(huì)通過new來實(shí)例化一個(gè)String對象,并將該對象的引用賦值給str2,然后檢測字符串常量池中是否已經(jīng)有了與“hello world”相等的對象,如果沒有,則會(huì)在堆內(nèi)存中再生成一個(gè)值為"hello world"的對象,并將其引用放入到字符串常量池中,否則,不會(huì)再去創(chuàng)建。這里,第一行代碼其實(shí)已經(jīng)在字符串常量池中保存了“hello world”字符串對象的引用,因此,第二行代碼就不會(huì)再次向常量池中添加“hello world"的引用。
String str3=str2.intern(); 這行代碼會(huì)首先去檢測字符串常量池中是否已經(jīng)包含了”hello world"的String對象,如果有則直接返回其引用。而在這里,str2.intern()其實(shí)剛好返回了第一行代碼中生成的“hello world"對象。
因此【System.out.println("str1 == str3:"+(str1 == str3));】這行代碼會(huì)輸出true.
如果切到JDK6,其打印結(jié)果與上一致,至于原因讀者可以自行分析。

2.改造例子,再看intern
上一節(jié)中我們通過一個(gè)例子認(rèn)識了intern()方法的作用,接下來,我們對上述例子做一些修改:
public class Test { public static void main(String[] args) { String str1=new String("he")+new String("llo"); String str2=str1.intern(); String str3="hello"; System.out.println("str1 == str2:"+(str1 == str2)); System.out.println("str2 == str3:"+(str2 == str3)); } }復(fù)制代碼
先別急著看下方答案,思考一下在JDK7(或JDK7之后)及JDK6上會(huì)輸出什么結(jié)果?
1).JDK8的運(yùn)行結(jié)果分析
我們先來看下我們先來看下JDK8的運(yùn)行結(jié)果:

通過運(yùn)行程序發(fā)現(xiàn)輸出的兩個(gè)結(jié)果都是true,這是為什么呢?我們通過一個(gè)圖來分析:

String str1=new String("he")+new String("llo"); 這行代碼中new String("he")和new String("llo")會(huì)在堆上生成四個(gè)對象,因?yàn)榕c本例無關(guān),所以圖上沒有畫出,new String("he")+new String("llo")通過”+“號拼接后最終會(huì)生成一個(gè)"hello"對象并賦值給str1。
String str2=str1.intern(); 這行代碼會(huì)首先檢測字符串常量池,發(fā)現(xiàn)此時(shí)還沒有存在與”hello"相等的字符串對象的引用,而在檢測堆內(nèi)存時(shí)發(fā)現(xiàn)堆中已經(jīng)有了“hello"對象,遂將堆中的”hello"對象的應(yīng)用放入字符串常量池中。
String str3="hello"; 這行代碼發(fā)現(xiàn)字符串常量池中已經(jīng)存在了“hello"對象的引用,因此將str3指向了字符串常量池中的引用。
此時(shí),我們發(fā)現(xiàn)str1、str2、str3指向了堆中的同一個(gè)”hello"對象,因此,就有了上邊兩個(gè)均為true的輸出結(jié)果。
2).JDK6的運(yùn)行結(jié)果分析
我們將運(yùn)行環(huán)境切換到JDK6,來看下其輸出結(jié)果:

有點(diǎn)意思!相同的代碼在不同的JDK版本上輸出結(jié)果竟然不相等。這是怎么回事呢?我們還通過一張圖來分析:

String str1=new String("he")+new String("llo"); 這行代碼會(huì)通過new String("he")和new String("llo")會(huì)分別在Java堆與字符串常量池中各生成兩個(gè)String對象,由于與本例無關(guān),所以并沒有在圖中畫出。而new String("he")+new String("llo")通過“+”號拼接后最終會(huì)在Java堆上生成一個(gè)"hello"對象,并將其賦值給了str1。
String str2=str1.intern(); 這行代碼檢測到字符串常量池中還沒有“hello"對象,因此將堆中的”hello“對象復(fù)制到了字符串常量池,并將其賦值給str2。
String str3="hello"; 這行代碼檢測到字符串常量池中已經(jīng)有了”hello“對象,因此直接將str3指向了字符串常量池中的”hello“對象。 此時(shí)str1指向的是Java堆中的”hello“對象,而str2和str3均指向了字符串常量池中的對象。因此,有了上面的輸出結(jié)果。
通過這兩個(gè)例子,相信大家因該對String的intern()方法有了較深的認(rèn)識。那么intern()方法具體在開發(fā)中有什么用呢?推薦大家可以看下美團(tuán)技術(shù)團(tuán)隊(duì)的一篇文章《深入解析String#intern》中舉的兩個(gè)例子。限于篇幅,本文不再舉例分析。
三、String類的結(jié)構(gòu)及特性分析
前兩節(jié)我們認(rèn)識了String的內(nèi)存分配以及它的intern()方法,這兩節(jié)內(nèi)容其實(shí)都是對String內(nèi)存的分析。到目前為止,我們還并未認(rèn)識String類的結(jié)構(gòu)以及它的一些特性。那么本節(jié)內(nèi)容我們就此來分析。先通過一段代碼來大致了解一下String類的結(jié)構(gòu)(代碼取自jdk8):
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 // ...}復(fù)制代碼
可以看到String類實(shí)現(xiàn)了Serializable接口、Comparable接口以及CharSequence接口,意味著它可以被序列化,同時(shí)方便我們排序。另外,String類還被聲明為了final類型,這意味著String類是不能被繼承的。而在其內(nèi)部維護(hù)了一個(gè)char數(shù)組,說明String是通過char數(shù)組來實(shí)現(xiàn)的,同時(shí)我們注意到這個(gè)char數(shù)組也被聲明為了final,這也是我們常說的String是不可變的原因。
1.不同JDK版本之間String的差異
Java的設(shè)計(jì)團(tuán)隊(duì)一直在對String類進(jìn)行優(yōu)化,這就導(dǎo)致了不同jdk版本上String類的實(shí)現(xiàn)有些許差異,只是我們使用上并無感知。下圖列出了jdk6-jdk9中String源碼的一些變化。

可以看到在Java6之前String中維護(hù)了一個(gè)char 數(shù)組、一個(gè)偏移量 offset、一個(gè)字符數(shù)量 count以及一個(gè)哈希值 hash。 String對象是通過 offset 和 count 兩個(gè)屬性來定位 char[] 數(shù)組,獲取字符串。這么做可以高效、快速地共享數(shù)組對象,同時(shí)節(jié)省內(nèi)存空間,但這種方式很有可能會(huì)導(dǎo)致內(nèi)存泄漏。
在Java7和Java8的版本中移除了 offset 和 count 兩個(gè)變量了。這樣的好處是String對象占用的內(nèi)存稍微少了些,同時(shí) String.substring 方法也不再共享 char[],從而解決了使用該方法可能導(dǎo)致的內(nèi)存泄漏問題。
從Java9開始,String中的char數(shù)組被byte[]數(shù)組所替代。我們知道一個(gè)char類型占用兩個(gè)字節(jié),而byte占用一個(gè)字節(jié)。因此在存儲(chǔ)單字節(jié)的String時(shí),使用char數(shù)組會(huì)比byte數(shù)組少一個(gè)字節(jié),但本質(zhì)上并無任何差別。 另外,注意到在Java9的版本中多了一個(gè)coder,它是編碼格式的標(biāo)識,在計(jì)算字符串長度或者調(diào)用 indexOf() 函數(shù)時(shí),需要根據(jù)這個(gè)字段,判斷如何計(jì)算字符串長度。coder 屬性默認(rèn)有 0 和 1 兩個(gè)值, 0 代表Latin-1(單字節(jié)編碼),1 代表 UTF-16 編碼。如果 String判斷字符串只包含了 Latin-1,則 coder 屬性值為 0 ,反之則為 1。
2.String字符串的裁剪、拼接等操作分析
在本節(jié)內(nèi)容的開頭我們已經(jīng)知道了字符串的不可變性。那么為什么我們還可以使用String的substring方法進(jìn)行裁剪,甚至可以直接使用”+“連接符進(jìn)行字符串的拼接呢?
(1)String的substring實(shí)現(xiàn)
關(guān)于substring的實(shí)現(xiàn),其實(shí)我們直接深入String的源碼查看即可,源碼如下:
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); }復(fù)制代碼
從這段代碼中可以看出,其實(shí)字符串的裁剪是通過實(shí)例化了一個(gè)新的String對象來實(shí)現(xiàn)的。所以,如果在項(xiàng)目中存在大量的字符串裁剪的代碼應(yīng)盡量避免使用String,而是使用性能更好的StringBuilder或StringBuffer來處理。
(2)String的字符串拼接實(shí)現(xiàn)
1)字符串拼接方案性能對比
關(guān)于字符串的拼接有很多實(shí)現(xiàn)方法,在這里我們舉三個(gè)例子來進(jìn)行一個(gè)性能對比,分別如下:
使用”+“操作符拼接字符串
public class Test { private static final int COUNT=50000; public static void main(String[] args) { String str=""; for(int i=0;i<COUNT;i++) { str=str+"abc"; } }復(fù)制代碼
使用String的concat()方法拼接
public class Test { private static final int COUNT=50000; public static void main(String[] args) { String str=""; for(int i=0;i<COUNT;i++) { str=str+"abc"; } }復(fù)制代碼
使用StringBuilder的append方法拼接
public class Test { private static final int COUNT=50000; public static void main(String[] args) { StringBuilder str=new StringBuilder(); for(int i=0;i<COUNT;i++) { str.append("abc"); } }復(fù)制代碼
如上代碼,通過三種方法分別進(jìn)行了50000次字符串拼接,每種方法分別運(yùn)行了20次。統(tǒng)計(jì)耗時(shí),得到以下表格:
拼接方法 | 最小用時(shí)(ms) | 最大用時(shí)(ms) | 平均用時(shí)(ms) |
---|---|---|---|
"+"操作符 | 4868 | 5146 | 4924 |
String的concat方法 | 2227 | 2456 | 2296 |
StringBuilder的append方法 | 4 | 12 | 6.6 |
從以上數(shù)據(jù)中可以很直觀的看到”+“操作符的性能是最差的,平均用時(shí)達(dá)到了4924ms。其次是String的concat方法,平均用時(shí)也在2296ms。而表現(xiàn)最為優(yōu)秀的是StringBuilder的append方法,它的平均用時(shí)竟然只有6.6ms。這也是為什么在開發(fā)中不建議使用”+“操作符進(jìn)行字符串拼接的原因。
2)三種字符串拼接方案原理分析
”+“操作符的實(shí)現(xiàn)原理由于”+“操作符是由JVM來完成的,我么無法直接看到代碼實(shí)現(xiàn)。不過Java為我們提供了一個(gè)javap的工具,可以幫助我們將Class文件進(jìn)行一個(gè)反匯編,通過匯編指令,大致可以看出”+“操作符的實(shí)現(xiàn)原理。
public class Test { private static final int COUNT=50000; public static void main(String[] args) { for(int i=0;i<COUNT;i++) { str=str+"abc"; } }復(fù)制代碼
把上邊這段代碼編譯后,執(zhí)行javap,得到如下結(jié)果:

注意圖中的”11:“行指令處實(shí)例化了一個(gè)StringBuilder,在"19:"行處調(diào)用了StringBuilder的append方法,并在第”27:"行處調(diào)用了String的toString()方法??梢?,JVM在進(jìn)行”+“字符串拼接時(shí)也是用了StringBuilder來實(shí)現(xiàn)的,但為什么與直接使用StringBuilder的差距那么大呢?其實(shí),只要我們將上邊代碼轉(zhuǎn)換成虛擬機(jī)優(yōu)化后的代碼一看便知:
public class Test { private static final int COUNT=50000; public static void main(String[] args) { String str=""; for(int i=0;i<COUNT;i++) { str=new StringBuilder(str).append("abc").toString(); } }復(fù)制代碼
可見,優(yōu)化后的代碼雖然也是用的StringBuilder,但是StringBuilder卻是在循環(huán)中實(shí)例化的,這就意味著循環(huán)了50000次,創(chuàng)建了50000個(gè)StringBuilder對象,并且調(diào)用了50000次toString()方法。怪不得用了這么長時(shí)間?。?!
String的concat方法的實(shí)現(xiàn)原理關(guān)于concat方法可以直接到String內(nèi)部查看其源碼,如下:
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); }復(fù)制代碼
可以看到,在concat方法中使用Arrays的copyOf進(jìn)行了一次數(shù)組拷貝,接下來又通過getChars方法再次進(jìn)行了數(shù)組拷貝,最后通過new實(shí)例化了String對象并返回。這也意味著每調(diào)用一次concat都會(huì)生成一個(gè)String對象,但相比”+“操作符卻省去了toString方法。因此,其性能要比”+“操作符好上不少。
至于StringBuilder其實(shí)也沒必要再去分析了,畢竟”+“操作符也是基于StringBuilder實(shí)現(xiàn)的,只不過拼接過程中”+“操作符創(chuàng)建了大量的對象。而StringBuilder拼接時(shí)僅僅創(chuàng)建了一個(gè)StringBuilder對象。
四、總結(jié)
本篇文章我們深入分析了String字符串的內(nèi)存分配、intern()方法,以及String類的結(jié)構(gòu)及特性。關(guān)于這塊知識,網(wǎng)上的文章魚龍混雜,甚至眾說紛紜。筆者也是參考了大量的文章并結(jié)合自己的理解來做的分析。但是,避免不了的可能會(huì)出現(xiàn)理解偏差的問題,如果有,希望大家多多討論給予指正。 同時(shí),文章中多次提到StringBuilder,但限于文章篇幅,沒能給出關(guān)于其詳細(xì)分析。不過不用擔(dān)心,我會(huì)在下一篇文章中再做探討。 不管怎樣,相信大家看完這篇文章后一定 對String有了更加深入的認(rèn)識,尤其是了解String類的一些裁剪及拼接中可能造成的性能問題,在今后的開發(fā)中應(yīng)該盡量避免。
Atas ialah kandungan terperinci 溫故知新(1)深入認(rèn)識Java中的字符串. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Alat AI Hot

Undress AI Tool
Gambar buka pakaian secara percuma

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Clothoff.io
Penyingkiran pakaian AI

Video Face Swap
Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas

Komen tidak boleh cuai kerana mereka ingin menjelaskan sebab-sebab kewujudan kod dan bukannya fungsi, seperti keserasian dengan antara muka lama atau sekatan pihak ketiga, jika tidak, orang yang membaca kod itu hanya boleh bergantung pada meneka. Bidang yang mesti dikomentari termasuk penghakiman bersyarat kompleks, logik pengendalian kesilapan khas, dan sekatan pintasan sementara. Cara yang lebih praktikal untuk menulis komen ialah memilih komen tunggal atau menyekat komen berdasarkan tempat kejadian. Gunakan komen blok dokumen untuk menerangkan parameter dan pulangan nilai pada permulaan fungsi, kelas, dan fail, dan simpan komen dikemas kini. Untuk logik yang kompleks, anda boleh menambah garis kepada yang sebelumnya untuk meringkaskan niat keseluruhan. Pada masa yang sama, jangan gunakan komen untuk menutup kod, tetapi gunakan alat kawalan versi.

Kunci untuk menulis komen PHP adalah untuk menjelaskan tujuan dan spesifikasi. Komen harus menjelaskan "mengapa" dan bukannya "apa yang dilakukan", mengelakkan redundansi atau terlalu kesederhanaan. 1. Gunakan format bersatu, seperti docblock (/*/) untuk deskripsi kelas dan kaedah untuk meningkatkan keserasian dan keserasian alat; 2. Menekankan sebab -sebab di sebalik logik, seperti mengapa JS melompat perlu dikeluarkan secara manual; 3. Tambahkan gambaran keseluruhan gambaran sebelum kod kompleks, terangkan proses dalam langkah -langkah, dan membantu memahami idea keseluruhan; 4. Gunakan Todo dan Fixme secara rasional untuk menandakan item dan masalah untuk memudahkan penjejakan dan kerjasama berikutnya. Anotasi yang baik dapat mengurangkan kos komunikasi dan meningkatkan kecekapan penyelenggaraan kod.

Kunci untuk menulis komen yang baik adalah untuk menjelaskan "mengapa" daripada hanya "apa yang dilakukan" untuk meningkatkan kebolehbacaan kod. 1. Komen harus menjelaskan sebab -sebab logik, seperti pertimbangan di sebalik pemilihan nilai atau pemprosesan; 2. Gunakan anotasi perenggan untuk logik kompleks untuk meringkaskan idea keseluruhan fungsi atau algoritma; 3. Secara kerap mengekalkan komen untuk memastikan konsistensi dengan kod, elakkan mengelirukan, dan padamkan kandungan ketinggalan zaman jika perlu; 4. Secara serentak periksa komen semasa mengkaji semula kod, dan merekodkan logik awam melalui dokumen untuk mengurangkan beban komen kod.

Kunci untuk menulis komen PHP adalah jelas, berguna dan ringkas. 1. Komen harus menjelaskan niat di sebalik kod itu dan bukan hanya menggambarkan kod itu sendiri, seperti menjelaskan tujuan logik penghakiman bersyarat kompleks; 2. Tambahkan komen kepada senario utama seperti nilai sihir, keserasian kod lama, antara muka API, dan lain -lain untuk meningkatkan kebolehbacaan; 3. Elakkan kandungan kod pendua, simpan ringkas dan spesifik, dan gunakan format standard seperti phpDoc; 4. Komen harus dikemas kini secara serentak dengan kod untuk memastikan ketepatan. Komen yang baik harus difikirkan dari perspektif orang lain, mengurangkan kos pemahaman, dan menjadi kod pemahaman peranti navigasi.

Pembolehubah PHP bermula dengan $, dan penamaan mesti mematuhi peraturan, seperti mereka tidak boleh bermula dengan nombor dan sensitif kes; Skop pembolehubah dibahagikan kepada tempatan, global dan hyperglobal; Pembolehubah global boleh diakses menggunakan global, tetapi disyorkan untuk menyampaikannya dengan parameter; Pembolehubah dan tugasan rujukan harus digunakan dengan berhati -hati. Pembolehubah adalah asas untuk menyimpan data, dan menguasai peraturan dan mekanisme mereka dengan betul adalah penting untuk pembangunan.

Langkah pertama ialah memilih pakej persekitaran bersepadu XAMPP atau MAMP untuk membina pelayan tempatan; Langkah kedua ialah memilih versi PHP yang sesuai mengikut keperluan projek dan mengkonfigurasi pelbagai versi pertukaran; Langkah ketiga ialah memilih VSCode atau PHPStorm sebagai editor dan debug dengan XDebug; Di samping itu, anda perlu memasang komposer, php_codesniffer, phpunit dan alat lain untuk membantu dalam pembangunan.

Terdapat tiga cara biasa untuk menggunakan komen PHP: Komen line tunggal sesuai untuk menerangkan secara ringkas logik kod, seperti // atau # untuk penjelasan garis semasa; komen multi-line /*...*/ sesuai untuk penerangan terperinci mengenai fungsi atau kelas; Komen Dokumen DocBlock Mula dengan /** untuk memberikan maklumat segera untuk IDE. Apabila menggunakannya, anda harus mengelakkan karut, terus mengemas kini serentak, dan jangan gunakan komen untuk menyekat kod untuk masa yang lama.

Pengendali perbandingan PHP perlu memberi perhatian kepada jenis isu penukaran. 1. Penggunaan == Untuk membandingkan nilai sahaja, dan penukaran jenis akan dilakukan, seperti 1 == "1" adalah benar; 2. Penggunaan === Untuk memerlukan nilai yang sama seperti jenis, seperti 1 === "1" adalah palsu; 3. Perbandingan saiz boleh digunakan pada nilai dan rentetan, seperti "Apple"
