java需要转go吗,【Java转Go】弄清GOPATH
編譯語言 vs 解釋語言
本文會參照Java來比較分析GO的編譯,為了解兩者區別,閱讀正文前可以先了解下這兩個概念
程序的執行,說到底就是將代碼編譯成平臺能運行的機器碼,然后執行的過程
執行方式分成了兩種:
編譯型:通過編譯器,將代碼編譯成平臺特定的機器碼。編譯與運行隔開,一次編譯,可多次運行。代表有C、C++
解釋型:通過解釋器,逐行編譯代碼成平臺的機器碼,并立即運行。即每次運行時都編譯。代表有Python、Ruby
編譯型語言效率高,但跨平臺得重新編譯程序;解釋型語言易跨平臺執行,但每次運行要編譯效率低。
Golang 是編譯型語言
Java是半編譯半解釋型語言(編譯成jvm的字節碼,即class文件,然后jvm解釋執行)
GOPATH定義
剛學go語言時,我一直都沒有弄懂這個變量到底是做什么的,先看看官方的定義:
GOPATH 環境變量指定了你的工作空間位置。它或許是你在開發Go代碼時, 唯一需要設置的環境變量。
Go代碼必須放在工作空間內。它其實就是一個目錄,其中包含三個子目錄:
src 目錄包含Go的源文件,它們被組織成包(每個目錄都對應一個包),
pkg 目錄包含包對象,
bin 目錄包含可執行命令。
go 工具用于構建源碼包,并將其生成的二進制文件安裝到 pkg 和 bin 目錄中。src 子目錄通常包會含多種版本控制的代碼倉庫(例如Git或Mercurial), 以此來跟蹤一個或多個源碼包的開發
總結一下官方的描述重點:
所有Go代碼必須放在GoPath中
src包含所有源代碼,pkg包含編譯后的包文件(go中后綴為.a,java中為.jar),bin包含編譯后的可執行文件(go中根據平臺不一樣后綴不一樣,java中所有平臺都為.jar)
疑問一:為啥Go代碼必須放在GOPATH中
從java轉過來的我表示不能理解,為啥規定所有代碼都要在GoPath目錄?萬能的java里,項目在任何目錄都是可以執行的呀
我們來實驗一下,在非GoPath創建項目是否可以運行。
在D://創建如下goProject項目,包含一個main.go,及引用到的basic.hello.go
代碼如下:
main.go:
package main
import "basic"
func main() {
basic.Hello()
}
hello.go:
package basic
import "fmt"
func Hello() {
fmt.Print("Hello! ")
}
嘗試編譯main.go文件,發現報錯:
PS D:\goProject\src> go build -n main.go
main.go:3:8: cannot find package "basic" in any of:
D:\Program Files\go\src\basic (from $GOROOT)
E:\workspace\go\src\basic (from $GOPATH)
從錯誤信息中我們了解到,編譯失敗的原因在于,尋找引用包basic時,并沒在像我們想象的自動在項目路徑src下尋找,而是分別在$GOROOT和$GOPATH進行了查找。
也就是說,代碼只有都放進$GOPATH,才能保證import的引用都能正確的被找到。如果你的代碼除了官方引用($GOROOT),沒有其他包的引用,也是可以正常編譯運行的。
擴展:為什么Java項目放在任何路徑都可以正常編譯呢?
Java中有一個類似GOPATH的參數classpath,它是Java運行時環境搜索類和其他資源文件(比如jar\zip等資源)
的路徑。
classpath默認為jdk的相關目錄(lib)和當前目錄。java程序編譯和運行時,都可以指定classpath。我們之所以感覺java項目可以任意目錄執行,是因為idea、maven這些工具幫我們指定好了運行時依賴的classpath路徑。(在文章末尾有純命令編譯運行java項目的例子,想要了解的朋友可以簡單看看)
Go中其實也可以在項目運行的環境變量中指定GOPATH,這樣的好處在于每個項目的依賴包相互隔離。
但是個人感覺GOPATH的設計理念就是基于想把所有的依賴包、代碼、二進制文件統一到一個目錄。并且GO這么設計的時候很粗暴的不支持依賴包有不同版本:
Go philosophy is that everything should be backward compatible. If you release a library, you have to ensure it stay compatible with older versions's public API. If you need to break the API, then it is a new package and should have a new import path.
Go設計的哲學思維是,所有的代碼都應該向后兼容。如果你發布一個庫,你必須保證之前版本的API仍然是可以正常使用的。如果不能,那新版本的API應是在新的包路徑中被引用
Go這樣的設計應該是沒有得到很大的認可,所以在后續的版本中,Go還是加入了依賴包的版本管理(go 1.11和1.12版本中新增了go module)
疑問二:為啥Go編譯后的可執行文件那么大?
我們上面的項目放入$GOPATH后編譯,得到如下可執行文件:
image.png
一個hello world級別的代碼,編譯出來的可執行文件居然快2M?我們將java程序打包成可執行jar包,也不會這么大呀。
我們來詳細看下main.go的編譯過程:
E:\workspace\go\src\github.com\Mrdshu\codeDemo\goDemo> go build -x main.go
WORK=C:\Users\xxx\AppData\Local\Temp\go-build130222670 ----(指定臨時編譯目錄)
mkdir -p $WORK\b002\
cat >$WORK\b002\importcfg << 'EOF' # internal
# import config
packagefile fmt=D:\Program Files\go\pkg\windows_amd64\fmt.a
EOF
cd E:\workspace\go\src\github.com\Mrdshu\codeDemo\goDemo\basic
----(compile依賴包,并將編譯好的歸檔文件pkg_.a文件復制到緩存目錄,注意并不是$GOPATH/pkg目錄)
"D:\\Program Files\\go\\pkg\\tool\\windows_amd64\\compile.exe" -o "C:\\Users\\xxx\\AppData\\Local\\Temp\\go-build130222670\\b002\\_pkg_.a" -trimpath "C:\\Users\\xxx\\AppData\\Local\\Temp\\go-build130222670\\b002" -p github.com/Mrdshu/codeDemo/goDemo/basic -complete -buildid G_nVir86m2b03lvrDAWh/G_nVir86m2b03lvrDAWh -goversion go1.11 -D "" -importcfg "C:\\Users\\xxx\\AppData\\Local\\Temp\\go-build130222670\\b002\\importcfg" -pack -c=4 "E:\\workspace\\go\\src\\github.com\\Mrdshu\\codeDemo\\goDemo\\basic\\type.go"
"D:\\Program Files\\go\\pkg\\tool\\windows_amd64\\buildid.exe" -w "C:\\Users\\xxx\\AppData\\Local\\Temp\\go-build130222670\\b002\\_pkg_.a" # internal
cp "C:\\Users\\xxx\\AppData\\Local\\Temp\\go-build130222670\\b002\\_pkg_.a" "C:\\Users\\xxx\\AppData\\Local\\go-build\\50\\5026df2f79f2ef5ea4775d8d700e1ab5086453e6fe91b37d372e005c5655a6fc-d" # internal
mkdir -p $WORK\b001\
cat >$WORK\b001\importcfg << 'EOF' # internal
# import config
packagefile github.com/Mrdshu/codeDemo/goDemo/basic=$WORK\b002\_pkg_.a ----(指定import依賴包的)
packagefile runtime=D:\Program Files\go\pkg\windows_amd64\runtime.a
EOF
cd E:\workspace\go\src\github.com\Mrdshu\codeDemo\goDemo
----(編譯main.go)
"D:\\Program Files\\go\\pkg\\tool\\windows_amd64\\compile.exe" -o "C:\\Users\\xxx\\AppData\\Local\\Temp\\go-build130222670\\b001\\_pkg_.a" -trimpath "C:\\Users\\xxx\\AppData\\Local\\Temp\\go-build130222670\\b001" -p main -complete -buildid vFLkQqJe-TIZKPTKaKqi/vFLkQqJe-TIZKPTKaKqi -goversion go1.11 -D _/E_/workspace/go/src/github.com/Mrdshu/codeDemo/goDemo -importcfg "C:\\Users\\xxx\\AppData\\Local\\Temp\\go-build130222670\\b001\\importcfg" -pack -c=4 "E:\\workspace\\go\\src\\github.com\\Mrdshu\\codeDemo\\goDemo\\main.go"
"D:\\Program Files\\go\\pkg\\tool\\windows_amd64\\buildid.exe" -w "C:\\Users\\xxx\\AppData\\Local\\Temp\\go-build130222670\\b001\\_pkg_.a" # internal
cp "C:\\Users\\xxx\\AppData\\Local\\Temp\\go-build130222670\\b001\\_pkg_.a" "C:\\Users\\xxx\\AppData\\Local\\go-build\\89\\89f263733604869aad0807de6f81086dc556c2e81ace89d9b484618fb0d5d586-d" # internal
cat >$WORK\b001\importcfg.link << 'EOF' # internal
----(以下是鏈接地址)
packagefile command-line-arguments=$WORK\b001\_pkg_.a
packagefile github.com/Mrdshu/codeDemo/goDemo/basic=$WORK\b002\_pkg_.a
packagefile runtime=D:\Program Files\go\pkg\windows_amd64\runtime.a
packagefile fmt=D:\Program Files\go\pkg\windows_amd64\fmt.a
packagefile internal/bytealg=D:\Program Files\go\pkg\windows_amd64\internal\bytealg.a
packagefile internal/cpu=D:\Program Files\go\pkg\windows_amd64\internal\cpu.a
packagefile runtime/internal/atomic=D:\Program Files\go\pkg\windows_amd64\runtime\internal\atomic.a
packagefile runtime/internal/sys=D:\Program Files\go\pkg\windows_amd64\runtime\internal\sys.a
packagefile errors=D:\Program Files\go\pkg\windows_amd64\errors.a
packagefile io=D:\Program Files\go\pkg\windows_amd64\io.a
packagefile math=D:\Program Files\go\pkg\windows_amd64\math.a
packagefile os=D:\Program Files\go\pkg\windows_amd64\os.a
packagefile reflect=D:\Program Files\go\pkg\windows_amd64\reflect.a
packagefile strconv=D:\Program Files\go\pkg\windows_amd64\strconv.a
packagefile sync=D:\Program Files\go\pkg\windows_amd64\sync.a
packagefile unicode/utf8=D:\Program Files\go\pkg\windows_amd64\unicode\utf8.a
packagefile sync/atomic=D:\Program Files\go\pkg\windows_amd64\sync\atomic.a
packagefile internal/poll=D:\Program Files\go\pkg\windows_amd64\internal\poll.a
packagefile internal/syscall/windows=D:\Program Files\go\pkg\windows_amd64\internal\syscall\windows.a
packagefile internal/testlog=D:\Program Files\go\pkg\windows_amd64\internal\testlog.a
packagefile syscall=D:\Program Files\go\pkg\windows_amd64\syscall.a
packagefile time=D:\Program Files\go\pkg\windows_amd64\time.a
packagefile unicode/utf16=D:\Program Files\go\pkg\windows_amd64\unicode\utf16.a
packagefile unicode=D:\Program Files\go\pkg\windows_amd64\unicode.a
packagefile math/bits=D:\Program Files\go\pkg\windows_amd64\math\bits.a
packagefile internal/race=D:\Program Files\go\pkg\windows_amd64\internal\race.a
packagefile internal/syscall/windows/sysdll=D:\Program Files\go\pkg\windows_amd64\internal\syscall\windows\sysdll.a
packagefile internal/syscall/windows/registry=D:\Program Files\go\pkg\windows_amd64\internal\syscall\windows\registry.a
EOF
mkdir -p $WORK\b001\exe\
cd .
----(編譯鏈接,得到可執行文件)
"D:\\Program Files\\go\\pkg\\tool\\windows_amd64\\link.exe" -o "C:\\Users\\xxx\\AppData\\Local\\Temp\\go-build130222670\\b001\\exe\\a.out.exe" -importcfg "C:\\Users\\xxx\\AppData\\Local\\Temp\\go-build130222670\\b001\\importcfg.link" -buildmode=exe -buildid=y5HJgFD5pvt8M7MF2-zO/vFLkQqJe-TIZKPTKaKqi/P1SUhx5DB4gHx03j6wd9/y5HJgFD5pvt8M7MF2-zO -extld=gcc "C:\\Users\\xxx\\AppData\\Local\\Temp\\go-build130222670\\b001\\_pkg_.a"
"D:\\Program Files\\go\\pkg\\tool\\windows_amd64\\buildid.exe" -w "C:\\Users\\xxx\\AppData\\Local\\Temp\\go-build130222670\\b001\\exe\\a.out.exe" # internal
cp $WORK\b001\exe\a.out.exe main.exe
rm -r $WORK\b001\
從如上的編譯過程,我們可以大致的知道:
go build會先編譯依賴包,并將編譯的歸檔文件最終放入一個Local\\go-build的緩存目錄
編譯命令源碼文件main.go時,除了鏈接緩存目錄下的依賴包外,還鏈接了go自身的許多庫。
java的jar包之所以小是因為只包含了真正源代碼的字節碼(class文件),等到jvm運行時才編譯鏈接成二進制文件,最終執行;
而go程序編譯時鏈接了go語言底層的代碼庫,不單單只有源代碼。
最后,引用官方文檔中的解釋來進一步理解:
Why is my trivial program such a large binary?
The linker in the gc toolchain creates statically-linked binaries by default. All Go binaries therefore include the Go runtime, along with the run-time type information necessary to support dynamic type checks, reflection, and even panic-time stack traces.
A simple C "hello, world" program compiled and linked statically using gcc on Linux is around 750 kB, including an implementation of printf. An equivalent Go program using fmt.Printf weighs a couple of megabytes, but that includes more powerful run-time support and type and debugging information.
A Go program compiled with gc can be linked with the -ldflags=-w flag to disable DWARF generation, removing debugging information from the binary but with no other loss of functionality. This can reduce the binary size substantially.
疑問三:為什么編譯后GOPATH的pkg、bin中沒有文件?
上面我們編譯命令源碼文件main.go后,得到的二進制可執行文件在當前目錄,并不是bin目錄。而編譯依賴包后生成的歸檔文件也不在pkg目錄,而是在緩存目錄。
那pkg、bin目錄下什么時候有文件呢?
答案是使用go install時。有興趣的朋友可以嘗試使用go install -x來觀察執行過程,go install過程和go build差不多,只是最后多了一行命令將生成的文件移動到bin或pkg中。
另外,當pkg目錄、緩存目錄同時存在依賴包的歸檔文件時,編譯器會使用pkg目錄下的歸檔文件。
附加:Java 項目的編譯示例
不用maven、grade等項目管理工具,我們看一個“原生態”的java項目的結構:
如圖可分為src(存放源碼)、target(存放編譯后的class文件)、lib(存放第三方引用jar包)三個目錄。
A.java
package packageA;
public class A {
private String name;
public A(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Main.java
package packageA;
import org.springframework.util.StringUtils;
public class Main {
public static void main(String[] args) {
A a = new A("aaaa");
String name = a.getName();
if (StringUtils.isEmpty(name)){
System.out.println("name is empty");
}
else{
System.out.println("name is "+name);
}
}
}
我們直接在源碼路徑編譯項目:
PS D:\project\src\packageA> javac -verbose -encoding UTF-8 -classpath "D:\project\lib\spring-core-5.0.7.RELEASE.jar" -d
D:\project\target\classes A.java Main.java
[解析開始時間 RegularFileObject[A.java]]
[解析已完成, 用時 18 毫秒]
[解析開始時間 RegularFileObject[Main.java]]
[解析已完成, 用時 2 毫秒]
[源文件的搜索路徑: D:\project\lib\spring-core-5.0.7.RELEASE.jar]
[類文件的搜索路徑: D:\Program Files\Java\jdk1.8.0\jre\lib\resources.jar,D:\Program Files\Java\jdk1.8.0\jre\lib\rt.jar,D:\Program Files\Java\jdk1.8.0\jre\lib\sunrsasign.jar,D:\Program Files\Java\jdk1.8.0\jre\lib\jsse.jar,D:\Program Files\Java\jdk1.8.0\jre\lib\jce.jar,D:\Program Files\Java\jdk1.8.0\jre\lib\charsets.jar,D:\Program Files\Java\jdk1.8.0\jre\lib\jfr.jar,D:\Program Files\Java\jdk1.8.0\jre\classes,D:\Program Files\Java\jdk1.8.0\jre\lib\ext\access-bridge-64.jar,D:\Program Files\Java\jdk1.8.0\jre\lib\ext\cldrdata.jar,D:\Program Files\Java\jdk1.8.0\jre\lib\ext\dnsns.jar,D:\Program Files\Java\jdk1.8.0\jre\lib\ext\jaccess.jar,D:\Program Files\Java\jdk1.8.0\jre\lib\ext\jfxrt.jar,D:\Program Files\Java\jdk1.8.0\jre\lib\ext\localedata.jar,D:\Program Files\Java\jdk1.8.0\jre\lib\ext\nashorn.jar,D:\Program Files\Java\jdk1.8.0\jre\lib\ext\sunec.jar,D:\Program Files\Java\jdk1.8.0\jre\lib\ext\sunjce_provider.jar,D:\Program Files\Java\jdk1.8.0\jre\lib\ext\sunmscapi.jar,D:\Program Files\Java\jdk1.8.0\jre\lib\ext\sunpkcs11.jar,D:\Program Files\Java\jdk1.8.0\jre\lib\ext\zipfs.jar,D:\project\lib\spring-core-5.0.7.RELEASE.jar]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Object.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/String.class)]]
[正在加載ZipFileIndexFileObject[D:\project\lib\spring-core-5.0.7.RELEASE.jar(org/springframework/util/StringUtils.class)]]
[正在檢查packageA.A]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/io/Serializable.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/AutoCloseable.class)]]
[已寫入RegularFileObject[D:\project\target\classes\packageA\A.class]]
[正在檢查packageA.Main]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Byte.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Character.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Short.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Long.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Float.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Integer.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Double.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Boolean.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Void.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/System.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/io/PrintStream.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Appendable.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/io/Closeable.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/io/FilterOutputStream.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/io/OutputStream.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/io/Flushable.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/Comparable.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/CharSequence.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/StringBuilder.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/AbstractStringBuilder.class)]]
[正在加載ZipFileIndexFileObject[D:\Program Files\Java\jdk1.8.0\lib\ct.sym(META-INF/sym/rt.jar/java/lang/StringBuffer.class)]]
[已寫入RegularFileObject[D:\project\target\classes\packageA\Main.class]]
[共 258 毫秒]
-verbose:輸出有關編譯器正在執行的操作的消息
-encoding:指定源文件使用的字符編碼
-classpath:指定查找用戶類文件和注釋處理程序的位置。classpath默認為.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar
編譯順序:先編譯A.java,再編譯使用到A的Main.java
通過日志我們可以清晰的看到,java程序編譯時,到指定的classpath路徑下搜索用到的源文件和類文件,然后找到依賴的class文件并引用,最終在指定的\target\classes目錄生成了對應的class文件。
在target\classes目錄我們運行程序:
運行時java的類加載器會將class文件加載進去,然后進行鏈接、初始化,最終執行
java -classpath "D:\project\lib\spring-core-5.0.7.RELEASE.jar;." -verbose packageA/Main
##output:name is aaaa
總結
以上是生活随笔為你收集整理的java需要转go吗,【Java转Go】弄清GOPATH的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java三目运算符判断boonlean,
- 下一篇: php 跨域名存储cookie,实现跨域