一、初步了解 Go 语言
(一)Go 语言诞生的主要问题和目标
- 多核硬件架构:随着计算机硬件的发展,多核处理器成为主流,使得并行计算变得普遍。然而,传统的编程语言在处理多核并行性时可能面临困难,因为它们缺乏合适的原生支持。Go 语言通过引入轻量级的协程(goroutine)和通道(channel)机制,使得并发编程变得更加容易。
总结了 Go 语言的基础知识,涵盖环境搭建、基本语法(变量、常量、数据类型)、控制流(条件、循环、跳转)、集合操作(数组、切片、Map)、函数定义、面向对象特性(结构体、接口)、错误处理机制以及包管理和依赖管理。重点介绍了 Go 的并发编程模型,包括 Goroutine、Channel、Mutex、WaitGroup 及 Context 的使用,并提供了相应的代码示例和最佳实践建议。

综合来看,Go 语言在诞生时确实着重解决了多核硬件架构、超大规模分布式计算集群和 Web 模式下的开发规模与速度等技术挑战。
Go 语言在当下应用开发中已经得到广泛应用,许多知名公司和项目都使用 Go 语言来构建各种类型的应用。以下是一些代表性的产品和项目,它们使用了 Go 语言作为核心开发语言:

当 Java、C++、C 等编程语言的程序员开始学习编写 Go 语言时,可能会遇到一些误区:
在 macOS 上设置 Go 语言开发环境非常简单,可以按照以下步骤进行操作:
goX.X.X.darwin-amd64.pkg,双击下载的安装包,按照指示运行安装程序。默认路径通常是 /usr/local/go。验证安装:打开终端,输入以下命令来验证 Go 是否已正确安装:
go version
如果看到了 Go 的版本号,表示安装成功。
设置环境变量:一旦安装完成,需要将 Go 语言的二进制路径添加到自己的终端配置文件中的 PATH 环境变量中。
export PATH=$PATH:/usr/local/go/bin
使配置生效,可以运行以下命令或者重启终端:
source ~/.bash_profile
使用 Homebrew 安装:如果您使用 Homebrew 包管理器,这是最方便的方法。
brew install go
推荐使用 GoLand 或其他支持 Go 的 IDE,直接官网下载即可。
创建自己的工程目录,新建 src 目录。
src 目录下创建 chapter1/hello 目录,新建 hello.go 文件,编写代码如下:
package main
import (
"fmt"
"os"
)
func main() {
if len(os.Args) > 1 {
fmt.Println("Hello World", os.Args[1])
}
}
在该目录下执行 go run hello.go ZYF,运行结果为 Hello World ZYF。
前提:chapter2 目录下创建 variables,学习总结如下:
var 关键字声明一个变量,例如:var x int。:= 操作符进行变量声明和赋值,Go 会根据右侧的值自动推断变量类型。= 给变量赋值。新建 fib_test.go,简单实用斐波那契数列进行练习:
package variables
import "testing"
func TestFibList(t *testing.T) {
a := 1
b := 1
t.Log(a)
for i := 0; i < 5; i++ {
t.Log(" ", b)
tmp := a
a = b
b = tmp + a
}
}
func TestExchange(t *testing.T) {
a := 1
b := 2
a, b = b, a
t.Log(a, b)
}
前提:chapter2 目录下创建 constant,学习总结如下:
const 关键字声明一个常量。新建 constant_test.go:
package constant
import "testing"
const (
Monday = 1 + iota
Tuesday
Wednesday
)
const (
Readable = 1 << iota
Writable
Executable
)
func TestConstant1(t *testing.T) {
t.Log(Monday, Tuesday)
}
func TestConstant2(t *testing.T) {
a := 1
t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)
}
前提:chapter2 目录下创建 type,学习总结如下:
具体代码展开分析:
package main
import "fmt"
type Person struct {
FirstName string
LastName string
Age int
}
type Shape interface {
Area() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14 * c.Radius * c.Radius
}
func add(a, b int) int {
return a + b
}
type Operation func(int, int) int
func main() {
// 演示多种数据类型
fmt.Println("整数类型")
var x int = 10
var y int64 = 100
fmt.Println(x, y)
fmt.Println("浮点数类型")
var a float32 = 3.14
var b float64 = 3.14159265359
fmt.Println(a, b)
fmt.Println("布尔类型")
var isTrue bool = true
fmt.Println(isTrue)
fmt.Println("字符串类型")
str1 := "Hello, "
str2 := "Go!"
concatenated := str1 + str2
fmt.Println(concatenated)
fmt.Println("切片类型")
numbers := []int{1, 2, 3, 4, 5}
fmt.Println(numbers)
fmt.Println("映射类型")
ages := map[string]int{
"Alice": 25,
"Bob": 30,
}
fmt.Println(ages)
fmt.Println("结构体类型")
person := Person{
FirstName: "John",
LastName: "Doe",
Age: 30,
}
fmt.Println(person)
fmt.Println("接口类型")
var shape Shape
circle := Circle{Radius: 5}
shape = circle
fmt.Println("Circle Area:", shape.Area())
fmt.Println("函数类型")
var op Operation
op = add
result := op(10, 5)
fmt.Println("Addition:", result)
fmt.Println("通道类型")
messages := make(chan string)
go func() {
messages <- "Hello, Go!"
}()
msg := <-messages
fmt.Println(msg)
fmt.Println("指针类型")
x = 10
var ptr *int
ptr = &x
fmt.Println("Value of x:", x)
fmt.Println("Value stored in pointer:", *ptr)
*ptr = 20
fmt.Println("Updated value of x:", x)
}
Go 语言支持类型转换,但需要注意一些规则和限制。例如:
package main
import "fmt"
func main() {
// 显式类型转换
var x int = 10
var y float64 = float64(x)
fmt.Println(y)
// 类型别名的转换
type Celsius float64
type Fahrenheit float64
c := Celsius(25)
f := Fahrenheit(c*9/5 + 32)
fmt.Println(f)
}
前提:chapter2 目录下创建 operator,学习总结如下:
新建 operator_test.go:
package operator
import (
"fmt"
"testing"
)
const (
Readable = 1 << iota
Writable
Executable
)
func TestOperatorBasic(t *testing.T) {
a := 10
b := 5
fmt.Println("Sum:", a+b)
fmt.Println("Difference:", a-b)
fmt.Println("Product:", a*b)
fmt.Println("Quotient:", a/b)
fmt.Println("Remainder:", a%b)
x := true
y := false
fmt.Println("AND:", x && y)
fmt.Println("OR:", x || y)
fmt.Println("NOT:", !x)
fmt.Println("Equal:", a == b)
fmt.Println("Not Equal:", a != b)
fmt.Println("Greater Than:", a > b)
}
func TestBitClear(t *testing.T) {
a := 7 // 0111
a = a &^ Readable
a = a &^ Executable
t.Log(a&Readable == Readable, a&Writable == Writable, a&Executable == Executable)
}
在 Go 语言中,&^ 是按位清除运算符(Bit Clear Operator)。它用于将某些位置上的位清零。
func TestOther(t *testing.T) {
var a uint8 = 0b11001100 // 十进制为 204
var b uint8 = 0b00110011 // 十进制为 51
result := a &^ b
fmt.Printf("a: %08b\n", a)
fmt.Printf("b: %08b\n", b)
fmt.Printf("Result: %08b\n", result) // 输出:11000000
fmt.Println("Result (Decimal):", result) // 输出:192
}
前提:chapter2 目录下创建 condition,学习总结如下:
创建 condition_test.go:
package condition
import (
"fmt"
"testing"
)
func TestConditionIf(t *testing.T) {
age := 18
if age < 18 {
fmt.Println("You are a minor.")
} else if age >= 18 && age < 60 {
fmt.Println("You are an adult.")
} else {
fmt.Println("You are a senior citizen.")
}
}
func TestConditionSwitch(t *testing.T) {
dayOfWeek := 3
switch dayOfWeek {
case 1:
fmt.Println("Monday")
case 2:
fmt.Println("Tuesday")
case 3:
fmt.Println("Wednesday")
default:
fmt.Println("Weekend")
}
}
前提:chapter2 目录下创建 loop,学习总结如下:
创建 loop_test.go:
package loop
import (
"fmt"
"testing"
)
func TestLoopFor(t *testing.T) {
for i := 1; i <= 5; i++ {
fmt.Println("Iteration:", i)
}
}
func TestLoopForRange(t *testing.T) {
numbers := []int{1, 2, 3, 4, 5}
for index, value := range numbers {
fmt.Printf("Index: %d, Value: %d\n", index, value)
}
}
func TestLoopForUnLimit(t *testing.T) {
i := 1
for {
fmt.Println("Iteration:", i)
i++
if i > 5 {
break
}
}
}
前提:chapter2 目录下创建 jump,学习总结如下:
创建 jump_test.go:
package jump
import (
"fmt"
"testing"
)
func TestJumpBreak(t *testing.T) {
for i := 1; i <= 5; i++ {
if i == 3 {
break
}
fmt.Println("Iteration:", i)
}
}
func TestJumpContinue(t *testing.T) {
for i := 1; i <= 5; i++ {
if i == 3 {
continue
}
fmt.Println("Iteration:", i)
}
}
src 目录下创建 chapter3,在 Go 语言中,集合是存储一组值的数据结构。常用的集合类型包括数组、切片、映射和通道。
前提:chapter3 目录下创建 array,学习总结如下:
创建 array_test.go:
package array
import "testing"
func TestArrayInit(t *testing.T) {
var arr [3]int
arr1 := [4]int{1, 2, 3, 4}
arr3 := [...]int{1, 3, 4, 5}
arr1[1] = 5
t.Log(arr[1], arr[2])
t.Log(arr1, arr3)
}
func TestArrayTravel(t *testing.T) {
arr3 := [...]int{1, 3, 4, 5}
for _, e := range arr3 {
t.Log(e)
}
}
前提:chapter3 目录下创建 slice,学习总结如下:
创建 slice_test.go:
package slice
import (
"fmt"
"testing"
)
func TestSlice(t *testing.T) {
numbers := []int{1, 2, 3, 4, 5}
fmt.Println("Original Slice:", numbers)
slice := make([]int, 3)
fmt.Println("Initial Make Slice:", slice)
slice = append(slice, 10)
slice = append(slice, 20, 30)
fmt.Println("After Append:", slice)
copySlice := make([]int, len(slice))
copy(copySlice, slice)
fmt.Println("Copied Slice:", copySlice)
subSlice := numbers[1:3]
fmt.Println("Subslice:", subSlice)
subSlice[0] = 100
fmt.Println("Modified Subslice:", subSlice)
fmt.Println("Original Slice:", numbers)
}
前提:chapter3 目录下创建 map,学习总结如下:
创建 map_test.go:
package my_map
import (
"fmt"
"testing"
)
func TestBasic(t *testing.T) {
ages := map[string]int{
"Alice": 25,
"Bob": 30,
"Eve": 28,
}
fmt.Println("Original Map:", ages)
ages["Charlie"] = 35
fmt.Println("After Adding:", ages)
delete(ages, "Eve")
fmt.Println("After Deletion:", ages)
age, exists := ages["Alice"]
if exists {
fmt.Println("Alice's Age:", age)
}
}
前提:chapter3 目录下创建 set,学习总结如下:
前提:chapter3 目录下创建 string,学习总结如下:
创建 string_test.go:
package string
import (
"strconv"
"strings"
"testing"
)
func TestString(t *testing.T) {
var s string
t.Log(s)
s = "hello"
t.Log(len(s))
s = "中"
t.Log(len(s))
c := []rune(s)
t.Logf("中 unicode %x", c[0])
}
func TestStringFn(t *testing.T) {
s := "A,B,C"
parts := strings.Split(s, ",")
for _, part := range parts {
t.Log(part)
}
t.Log(strings.Join(parts, "-"))
}
src 目录下创建 chapter4,在 Go 语言中,函数是一种用于执行特定任务的代码块。
func functionName(parameters) returnType {
// 函数体
return returnValue
}
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func divideNamed(a, b int) (result int, err error) {
if b == 0 {
err = errors.New("division by zero")
return
}
result = a / b
return
}
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
func applyFunction(fn func(int, int) int, a, b int) int {
return fn(a, b)
}
func main() {
x := 5
fn := func() {
fmt.Println(x)
}
fn()
}
func main() {
defer fmt.Println("World")
fmt.Println("Hello")
}
在 chapter4 下新建 basic,在创建 func_basic_test.go:
package basic
import (
"errors"
"fmt"
"testing"
)
func greet(name string) {
fmt.Printf("Hello, %s!\n", name)
}
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
func TestBasic(t *testing.T) {
greet("Alice")
q, err := divide(10, 2)
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Quotient:", q)
}
total := sum(1, 2, 3, 4, 5)
fmt.Println("Sum:", total)
}
在 chapter4 下新建 biz,在创建 func_biz_test.go:
package biz
import (
"fmt"
"testing"
)
type Product struct {
Name string
Price float64
}
func calculateTotal(products []Product) float64 {
total := 0.0
for _, p := range products {
total += p.Price
}
return total
}
func applyDiscount(amount, discount float64) float64 {
return amount * (1 - discount)
}
func TestBiz(t *testing.T) {
products := []Product{
{Name: "Product A", Price: 10.0},
{Name: "Product B", Price: 20.0},
{Name: "Product C", Price: 30.0},
}
total := calculateTotal(products)
fmt.Printf("Total before discount: $%.2f\n", total)
discountedTotal := applyDiscount(total, 0.1)
fmt.Printf("Total after 10%% discount: $%.2f\n", discountedTotal)
}
src 目录下创建 chapter5,Go 语言支持面向对象编程(OOP),尽管与一些传统的面向对象编程语言相比,Go 的实现方式略有不同。在 Go 语言中,没有类的概念,但可以通过结构体和方法来实现面向对象的特性。
创建 struct 目录,编写 struct_test.go:
package _struct
import (
"fmt"
"testing"
)
type Person struct {
FirstName string
LastName string
Age int
}
func TestStruct(t *testing.T) {
person1 := Person{
FirstName: "Alice",
LastName: "Smith",
Age: 25,
}
fmt.Println("First Name:", person1.FirstName)
fmt.Println("Last Name:", person1.LastName)
fmt.Println("Age:", person1.Age)
}
{}。创建 method 目录:
package method
import (
"fmt"
"testing"
)
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
func TestMethodDef(t *testing.T) {
c := Circle{Radius: 5}
area := c.Area()
fmt.Printf("Circle area: %.2f\n", area)
}
package method
import (
"fmt"
"testing"
)
type Counter struct {
Count int
}
func (c *Counter) Increment() {
c.Count++
}
func TestMethonRec(t *testing.T) {
counter := Counter{Count: 0}
counter.Increment()
fmt.Println("Count:", counter.Count)
}
创建 interface 目录:
package interface_test
import (
"fmt"
"testing"
)
type Shape interface {
Area() float64
}
type Circle struct {
Radius float64
}
func (c Circle) Area() float64 {
return 3.14159 * c.Radius * c.Radius
}
type Rectangle struct {
Width float64
Height float64
}
func (r Rectangle) Area() float64 {
return r.Width * r.Height
}
func TestInterface(t *testing.T) {
shapes := []Shape{
Circle{Radius: 2},
Rectangle{Width: 3, Height: 4},
}
for _, shape := range shapes {
fmt.Printf("Area of %T: %.2f\n", shape, shape.Area())
}
}
interface{},可以表示任何类型的值。value.(Type),用于恢复具体类型。package emptyassert
import (
"fmt"
"testing"
)
func DoSomething(p interface{}) {
switch v := p.(type) {
case int:
fmt.Println("Integer", v)
case string:
fmt.Println("String", v)
default:
fmt.Println("Unknow Type")
}
}
func TestEmptyAssert(t *testing.T) {
var x interface{} = "hello"
str, ok := x.(string)
if ok {
fmt.Println("String:", str)
} else {
fmt.Println("Not a string")
}
}
src 目录下创建 chapter6,Go 语言中的错误处理机制是通过返回错误值来实现的。
package basic
import (
"errors"
"fmt"
"testing"
)
var LessThanTwoError = errors.New("n should be not less than 2")
func GetFibonacci(n int) ([]int, error) {
if n < 2 {
return nil, LessThanTwoError
}
fibList := []int{1, 1}
for i := 2; i < n; i++ {
fibList = append(fibList, fibList[i-2]+fibList[i-1])
}
return fibList, nil
}
func TestGetFibonacci(t *testing.T) {
if v, err := GetFibonacci(1); err != nil {
t.Error(err)
} else {
t.Log(v)
}
}
package chain
import (
"errors"
"fmt"
"testing"
)
type FileError struct {
Op string
Path string
Err error
}
func (e *FileError) Error() string {
return fmt.Sprintf("%s %s: %v", e.Op, e.Path, e.Err)
}
func ReadFile(path string) ([]byte, error) {
return nil, &FileError{Op: "read", Path: path, Err: errors.New("file not found")}
}
func TestChain(t *testing.T) {
filePath := "/path/to/nonexistent/file.txt"
_, err := ReadFile(filePath)
if err != nil {
fmt.Println("Error:", err)
if fileErr, ok := err.(*FileError); ok {
fmt.Printf("Operation: %s\n", fileErr.Op)
fmt.Printf("File Path: %s\n", fileErr.Path)
}
}
}
package recover
import (
"fmt"
"testing"
)
func cleanup() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}
func TestRecover(t *testing.T) {
defer cleanup()
panic("Something went wrong")
fmt.Println("This line will not be executed")
}
package define
import (
"fmt"
"testing"
"time"
)
type TimeoutError struct {
Operation string
Timeout time.Time
}
func (e TimeoutError) Error() string {
return fmt.Sprintf("Timeout error during %s operation. Timeout at %s", e.Operation, e.Timeout.Format("2006-01-02 15:04:05"))
}
func PerformOperation() error {
timeout := time.Now().Add(5 * time.Second)
if time.Now().After(timeout) {
return TimeoutError{Operation: "PerformOperation", Timeout: timeout}
}
return nil
}
func TestDefineError(t *testing.T) {
err := PerformOperation()
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Operation completed successfully.")
}
}
src 目录下创建 chapter7,Go 语言的包和依赖管理主要通过其内置的模块系统(Go Modules)来实现。
编写 my_series.go:
package series
import "fmt"
func init() {
fmt.Println("init1")
}
func Square(n int) int {
return n * n
}
func GetFibonacciSerie(n int) []int {
ret := []int{1, 1}
for i := 2; i < n; i++ {
ret = append(ret, ret[i-2]+ret[i-1])
}
return ret
}
使用 go get 命令来下载并添加远程依赖到项目中。
package remote
import (
"fmt"
"testing"
cm "github.com/easierway/concurrent_map"
)
func TestConcurrentMap(t *testing.T) {
m := cm.CreateConcurrentMap(99)
m.Set(cm.StrKey("key"), 10)
value, ok := m.Get(cm.StrKey("key"))
if ok {
fmt.Println("Key found:", value)
}
}
Go Modules 提供了版本控制和模块隔离的机制,避免了上述问题。
src 目录下创建 chapter8,展开后续的学习。
| 比较项 | Java Thread | Goroutine |
|---|---|---|
| 栈的初始大小 | 1MB | 2KB |
| 栈的增长方式 | 固定大小 | 动态增长 |
| 与内核线程的对应关系 | 1:1 模型 | M 模型 |
| 调度方式 | 由操作系统调度 | 由 Go 运行时调度 |
Go 运行时调度器通过 M:P:G 模型实现了 Goroutine 的高效调度。
package groutine
import (
"fmt"
"testing"
"time"
)
func sayHello() {
fmt.Println("Hello, Goroutine!")
}
func TestSayHello(t *testing.T) {
go sayHello()
time.Sleep(time.Millisecond * 10)
fmt.Println("Main function finished")
}
package share_mem
import (
"testing"
"time"
)
func TestCounter(t *testing.T) {
counter := 0
for i := 0; i < 5000; i++ {
go func() {
counter++
}()
}
time.Sleep(1 * time.Second)
t.Logf("counter = %d", counter)
}
func TestCounterThreadSafe(t *testing.T) {
var mut sync.Mutex
counter := 0
for i := 0; i < 5000; i++ {
go func() {
mut.Lock()
defer mut.Unlock()
counter++
}()
}
time.Sleep(1 * time.Second)
t.Logf("counter = %d", counter)
}
func TestCounterWaitGroup(t *testing.T) {
var mut sync.Mutex
counter := 0
var wg sync.WaitGroup
for i := 0; i < 5000; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mut.Lock()
defer mut.Unlock()
counter++
}()
}
wg.Wait()
t.Logf("counter = %d", counter)
}
Go 语言的并发机制是基于 CSP(Communicating Sequential Processes) 模型。
package concurrency
import (
"fmt"
"testing"
"time"
)
func AsyncService() chan string {
retCh := make(chan string, 1)
go func() {
ret := service()
retCh <- ret
}()
return retCh
}
func TestAsynService(t *testing.T) {
retCh := AsyncService()
otherTask()
fmt.Println(<-retCh)
}
select {
case msg1 := <-chan1:
fmt.Println("Received", msg1)
case msg2 := <-chan2:
fmt.Println("Received", msg2)
default:
fmt.Println("No channel is ready")
}
package timeout
import (
"testing"
"time"
)
func TestTimeout(t *testing.T) {
ch := make(chan string)
go func() {
time.Sleep(3 * time.Second)
ch <- "result"
}()
select {
case result := <-ch:
t.Errorf("Expected timeout, but received %s", result)
case <-time.After(2 * time.Second):
t.Log("Test passed: Operation timed out as expected")
}
}
close(ch)
检测通道是否关闭:
msg, ok := <-ch
if !ok {
// 通道已关闭
}
package broadcast
import (
"fmt"
"sync"
"testing"
"time"
)
func TestSimpleBroadcast(t *testing.T) {
dataCh := make(chan int)
var wg sync.WaitGroup
numConsumers := 3
numMessages := 5
for i := 0; i < numConsumers; i++ {
wg.Add(1)
go func(consumerID int) {
defer wg.Done()
for data := range dataCh {
fmt.Printf("Consumer %d received: %d\n", consumerID, data)
}
}(i)
}
go func() {
for i := 0; i < numMessages; i++ {
dataCh <- i
time.Sleep(500 * time.Millisecond)
}
close(dataCh)
}()
wg.Wait()
}
package simplecancel
import (
"fmt"
"testing"
"time"
)
func isCancelled(cancelChan chan struct{}) bool {
select {
case <-cancelChan:
return true
default:
return false
}
}
func TestCancel(t *testing.T) {
cancelChan := make(chan struct{}, 0)
for i := 0; i < 5; i++ {
go func(i int, cancelCh chan struct{}) {
for {
if isCancelled(cancelCh) {
break
}
time.Sleep(time.Millisecond * 5)
}
fmt.Println(i, "Cancelled")
}(i, cancelChan)
}
cancel_1(cancelChan)
time.Sleep(time.Second * 1)
}
在 Go 语言中,context 包是处理任务取消、超时控制和跨 API 边界传递请求范围数据的强大工具。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online