Go 语言学习总结(9)—— Go 与 Java 全面对比总结

10 篇文章 7 订阅
订阅专栏

基本语法格式

Golang: 编码风格相对统一,简单,没有太多的语法糖等,Java层次清晰,全面面向对象。

变量相关

  • 变量的声明及使用

在Java或者PHP、Python中,声明了变量,可以不使用,也不报错。

private static String CToString(int n) {
  int data = n; 
  return String.valueOf(n);
} 

在Golang中,变量申明以后,必须被使用,或者使用_标明,否则编译会报错,一般来讲,我们都不会申明不使用的变量了。

func CToStringPanic(n int) string {
	newData := n     // newData编译无法通过
	return strconv.Itoa(n)
}

func CToStringOk(n int) string {
	_ := n	      // ok
	return strconv.Itoa(n)
}
  • 变量声明及初始化

在Java中,如果内部申明变量,但是没有初始化,有时候会编译报错,需要注意,例如以下代码;

public void Cpainc() {
        int salary;
        Object object;
        System.out.println(salary); // 编译错误
        System.out.println(object); // 编译错误
}

在Golang中:对于基本类型来讲,声明即初始化,对于引用类型,声明则初始化为nil。

结构体、类、变量的作用范围的标准与规则

Java: 对方法、变量及类的可见域规则是通过private、protected、public关键字来控制的,类似PHP

Golang: 控制可见域的方式只有一个,当字段首字母开头是大写时说明其是对外可见的、小写时只对包内成员可见。

//公开
type Public struct{
	Name string    //公开 
  Age uint8      //公开
  salary uint    //私有
}


//私有
type private struct{
	name string    //私有 
  age uint8      //私有
  salary uint    //私有
}

类、结构体、方法、函数

结构体声明及使用

  • Java:Java是一种面向对象的编程语言,它使用类来组织数据和行为。类是对象的蓝图,它定义了对象的属性和方法。Java中的继承和接口实现允许创建层次结构,支持多态性。
  • Go:Go没有类的概念,而是使用结构体(structs)来组织数据。结构体是一种用户自定义的数据类型,它可以包含字段(fields)和方法(methods)。Go不支持继承,而是使用组合来复用代码。方法可以在结构体上定义,但与结构体本身没有紧密关联。

先看Golang的:

// 定义people结构体
type People struct {
	 Title string
   Age uint8
}

// 使用
func main() {
	P := new(People) // 通过new方法创建结构体指针
	person1 := People{}        
	person2 := People{
		Title: "xiaoHong",
		Age:  18,
	}
  ...........
}

再Java的:

方法和函数是有区别的

在Java中:所有的“函数”都是基于“类”这个概念构建的,也就是只有在“类”中才会包含所谓的“函数”,这里的“函数”被称为“方法”,可见上方声明实体并使用。

在Golang中:“函数”和“方法”的最基本区别是:函数不基于结构体而是基于包名调用,方法基于结构体调用。如下实例:

值类型、引用类型以及指针

Java中的指针和数据类型:

  1. 指针操作: 在Java中,不存在显式的指针操作。Java采用引用类型,但不允许直接访问内存地址。对象在Java中通过引用进行传递,而不是通过值。
  2. 基本数据类型和引用类型: Java有8种基本数据类型,它们是值类型,包括int、float、boolean等。除了基本数据类型外,Java的数组和对象属于引用类型。引用类型的变量存储的是对象的引用,而不是对象本身的值。

Go中的指针和数据类型:

  1. 指针操作: Go中存在显式的指针操作,但与C相比,Go的指针更加安全和简化。Go不支持指针运算,这有助于减少指针错误的发生。
  2. 基本数据类型和引用类型: Go中的所有基本类型都是值类型,包括int、float、bool等。但有几个类型(slice、map、channel、interface)表现出引用类型的特征,它们可以被看作引用类型。这些类型在传递时不会复制整个值,而是传递引用,因此可以直接使用变量本身而无需使用指针。
  3. slice和数组的区别: 在Go中,数组是值类型,其长度是固定的。而slice是一种动态数组,它没有固定长度,因此在使用上更加灵活。
  4. 类型的一致性: 在Go中,同类型的变量是可比较的,但只有同样长度和类型的数组才被视为同一类型。例如,[]int和[3]int被认为是不同的类型,这在参数传递和类型断言时需要注意。

Java和Go在指针和数据类型的处理方式上存在明显差异。Java采用引用类型,不支持指针操作,而Go在保持安全性的同时,允许显式的指针操作,并将基本数据类型和特定引用类型区分对待。这些差异在编程时需要注意,以确保正确处理数据和内存管理。

数组对比

在Java中:当向方法中传递数组时,可以直接通过该传入的数组修改原数组内部值(浅拷贝)。在Golang中:则有两种情况:在不限定数组长度(为slice)时也直接改变原数组的值,当限定数组长度时会完全复制出一份副本来进行修改(深拷贝):

Java:

public static void main(String[] args) {
        int[] arr = {11, 21, 31};
        c(arr);
        System.out.println(Arrays.toString(arr)); // 2,21,31
    }

    private static void c(int[] a r r) {
        arr[0] = 2;
}

Golang:

对象也不太一样哦

在Go中,当将对象传递给函数作为参数时,实际上会创建原对象的一个全新拷贝,这个拷贝具有自己独立的内存地址。而在Go中,对象之间的赋值操作会复制对象内存中的内容,这就是为什么即使在修改globalUser之前后,其地址保持不变,但对象的内容却发生了变化。

相比之下,在Java中,当将对象传递给函数时,传递的是原对象的引用的拷贝,这个拷贝仍然指向同一块内存地址。因此,Java对象之间的赋值操作实际上是复制对象的引用,而不是对象的内容。当引用指向不同的地址时,对象的内容也会发生变化。

Golang:

//User 定义User结构体
type User struct {
   Name string
   Age int
}


var globalUser = User {
   "zz",
   33,
}

func modifyUser(user User) {
   fmt.Printf("user的addr = %p\n",&user) 
   fmt.Printf("globalUser修改前的addr = %p\n",&globalUser)
   fmt.Println("globalUser修改前 = ",globalUser)
   // 修改指向
   globalUser = user
   fmt.Printf("globalUser修改后的addr = %p\n",&globalUser)
   fmt.Println("globalUser修改后 = ",globalUser)
}

func main() {
   var u User = User {
      "kx",
      32,
   }
   fmt.Printf("传递的参数u的addr = %p\n",&u)
   modifyUser(u)
}

指针的差异常

在Java中,当传递引用类型(例如对象或数组)作为函数参数时,实际上传递的是引用的副本,也可以说是引用的拷贝。这意味着传递的引用仍然指向相同的对象或数组,因此对对象或数组的修改将在原对象或数组上反映出来。

在Go中,确实需要显式传递指针才能操作原对象。如果不传递指针,将只传递对象的副本,对副本的修改不会影响原对象。这是Go语言的设计决策,旨在提供更明确的内存控制和避免意外的副作用。因此,在Go中,需要特别注意传递对象的指针,以确保对原对象的修改能够生效。

Golang的指针:

Java的指针:

面向对象编程,不太一样,这里的不同,很多人都不习惯

Java的面向对象与Golang的结构体组合模式

在Java中,一般使用抽象类和继承

// 定义抽象类 Animal
abstract class Animal {
    private String name;
    private int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    // 抽象方法 bark,子类需要实现
    public abstract void bark();
}

// 定义 Dog 类,继承自 Animal
class Dog extends Animal {
    public Dog(String name, int age) {
        super(name, age);
    }

    // 实现抽象方法 bark
    @Override
    public void bark() {
        System.out.println("狗在汪汪叫!");
    }
}

在Go中,可以使用结构体和方法接收者来实现这个场景,以下是一个示例:

package main

import "fmt"

// 定义 Animal 结构体
type Animal struct {
    Name string
    Age  int
}

// 定义 Animal 结构体的方法
func (a Animal) GetName() string {
    return a.Name
}

func (a Animal) GetAge() int {
    return a.Age
}

// 定义 Dog 结构体,嵌套 Animal
type Dog struct {
    Animal // 嵌套 Animal 结构体,获得其属性和方法
}

// 定义 Dog 结构体的方法
func (d Dog) Bark() {
    fmt.Println("狗在汪汪叫!")
}

func main() {
    // 创建 Dog 实例
    myDog := Dog{
        Animal: Animal{
            Name: "旺财",
            Age:  3,
        },
    }

    // 使用嵌套的 Animal 方法
    fmt.Printf("狗的名字:%s\n", myDog.GetName())
    fmt.Printf("狗的年龄:%d岁\n", myDog.GetAge())

    // 调用 Dog 自身的方法
    myDog.Bark()
}

隐式与显式实现接口的实现

在Java中,接口主要用于定义不同组件之间的契约或规范。Java中的接口是一种侵入性接口,这意味着实现接口的类必须显式声明它们实现了特定接口。以下是一个示例,演示如何在Java中管理狗的行为:

// 定义一个 Animal 接口,描述动物的基本行为
interface Animal {
    void eat();
    void sleep();
}

// 定义一个 Dog 类,实现 Animal 接口
class Dog implements Animal {
    @Override
    public void eat() {
        System.out.println("狗正在吃东西");
    }

    @Override
    public void sleep() {
        System.out.println("狗正在睡觉");
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建 Dog 实例
        Dog myDog = new Dog();

        // 调用实现的接口方法
        myDog.eat();
        myDog.sleep();
    }
}

在Go中,可以定义一个接口(Factory)并为其定义一些方法,然后创建一个结构体(CafeFactory)并实现这些方法,以满足接口的要求。以下是一个示例代码,演示如何实现这个场景:

package main

import "fmt"

// 定义 Factory 接口
type Factory interface {
    Produce() string
    Consume() string
}

// 定义 CafeFactory 结构体,实现 Factory 接口
type CafeFactory struct {
    Product string
}

// 实现 Produce 方法
func (cf CafeFactory) Produce() string {
    return "生产了 " + cf.Product
}

// 实现 Consume 方法
func (cf CafeFactory) Consume() string {
    return "消费了 " + cf.Product
}

func main() {
    // 创建 CafeFactory 实例
    coffeeFactory := CafeFactory{Product: "咖啡"}

    // 使用 Factory 接口来调用方法
    factory := Factory(coffeeFactory)

    // 调用 Produce 和 Consume 方法
    fmt.Println(factory.Produce())
    fmt.Println(factory.Consume())
}

Golang的非侵入式接口的优势在于其简洁、高效、以及按需实现的特性。在Go语言中,不存在类的继承概念,而是只需知道一个类型实现了哪些方法以及每个方法的行为是什么。在实现类型时,我们只需考虑自己应该提供哪些方法,而不必担心接口需要分解得多细才算合理。接口是由使用方根据需要进行定义的,而不是事先规划好的。这种方式减少了包的引入,因为多引入外部包意味着更多的耦合。接口是由使用方根据其自身需求来定义的,使用方无需担心是否已经有其他模块定义了类似的接口。相比之下,Java的侵入式接口优势在于其清晰的层次结构以及对类型的行为有严格的管理。

异常处理,相差很大

在Java中,异常处理是通过try-catch块来实现的

public class ExceptionHandlingDemo {
    public static void main(String[] args) {
        try {
            // 可能会引发异常的代码
            int result = divide(10, 0);
            System.out.println("结果是: " + result);
        } catch (ArithmeticException e) {
            // 捕获并处理异常
            System.out.println("发生了算术异常: " + e.getMessage());
        } finally {
            // 不管是否发生异常,都会执行的代码块
            System.out.println("这里是finally块,无论如何都会执行");
        }
    }

    public static int divide(int dividend, int divisor) {
        // 尝试执行除法操作
        return dividend / divisor;
    }
}

Golang的异常处理:

在Golang中,"ok模式"或者叫做"错误值模式"(error value pattern)是一种常见的异常处理方式。

在这种模式中,所有可能引发异常的方法或代码将错误作为第二个返回值返回,程序通常需要对返回值进行判断,如果错误不为空(通常用 if err != nil {} 判断),则进行相应的错误处理,并可能中断程序的执行。这种方式相对于传统的异常处理机制如Java的异常捕获和处理机制来说,更为简洁和直观。

package main

import (
	"fmt"
	"io/ioutil"
)

// 一个用于读取文件内容的函数,可能会返回错误
func readFileContent(filename string) (string, error) {
	// 尝试打开文件
	content, err := ioutil.ReadFile(filename)
	if err != nil {
		// 如果发生错误,返回错误信息
		return "", err
	}
	// 如果没有错误,返回文件内容
	return string(content), nil
}

func main() {
	// 调用 readFileContent 函数,并检查是否有错误发生
	content, err := readFileContent("example.txt")
	if err != nil {
		// 处理错误,例如打印错误信息
		fmt.Println("发生错误:", err)
		return // 可以选择中断程序的执行
	}
	// 如果没有错误,打印文件内容
	fmt.Println("文件内容:")
	fmt.Println(content)
}

Golang的defer、panic及recover

defer:defer是用于延迟执行一段代码的关键字。无论函数是否正常返回或者是否发生了恐慌(panic),defer代码都会在函数退出时执行。这通常用于执行一些必须在函数结束时进行的清理工作,例如关闭文件、释放资源等。defer也可以用来调用函数或匿名函数。它确保在函数结束前执行。

//defer
func someFunction() {
    defer fmt.Println("This will be executed last")
    fmt.Println("This will be executed first")
}

//painc
func someFunction() {
    panic("Something went terribly wrong!")
}

//recover
func someFunction() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
        }
    }()
    panic("Something went terribly wrong!")
}

需要注意的是,defer使用一个栈来维护需要执行的代码,所以defer函数所执行的顺序是和defer声明的顺序相反的。

defer fmt.Println(1)   
defer fmt.Println(2)
defer fmt.Println(3)

执行结果:
3
2
1
  1. panic的作用:panic用于引发运行时恐慌(panic),并停止当前函数的执行流程。但是,在停止执行之前,它会执行当前函数中的defer语句,然后停止该函数的执行。然后,它会继续执行调用该函数的函数的defer语句,以此类推,直到goroutine的调用堆栈被清理。最终,整个程序可能会中止。panic通常用于处理严重错误,例如数组越界或空指针引用等,以避免程序进一步运行可能导致更严重问题的代码。
  2. recover的作用:recover用于捕获panic引发的运行时恐慌,从而允许程序继续执行而不中止。通常,recover与defer一起使用。当在defer内部调用recover时,它会捕获panic的错误信息,并返回该错误。如果没有panic,recover返回nil。你可以使用recover来在发生恐慌时采取适当的措施,例如记录错误、进行恢复或执行清理操作。需要注意的是,recover只能在defer内部使用,并且仅在发生panic时才有效。

recover的作用是捕捉panic抛出的错误并进行处理,需要联合defer来使用,类似于Java中的catch代码块:

func someFunction() {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered from panic:", r)
            // 在这里可以采取适当的措施
        }
    }()
    panic("Something went terribly wrong!")
}

注:利用recover处理panic指令,defer必须在panic之前声明,否则当panic时,recover无法捕获到panic。

并发编程,也不太一样

并发方面,Java 和 Golang 的基本实现

在 Java 中,要获得 CPU 资源并异步执行代码单元,通常需要创建一个实现了 Runnable 接口的类,并将该类的实例传递给一个 Thread 对象来执行。以下是一些示例代码,演示了如何在 Java 中执行异步代码单元:

// 创建一个实现了 Runnable 接口的类
class MyRunnable implements Runnable {
    @Override
    public void run() {
        // 在这里编写你的异步代码单元
        for (int i = 0; i < 5; i++) {
            System.out.println("线程执行:" + i);
            try {
                Thread.sleep(1000); // 模拟任务执行时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        // 创建一个 Thread 对象,并传递 MyRunnable 的实例
        Thread thread = new Thread(new MyRunnable());
        
        // 启动线程
        thread.start();
        
        // 主线程可以继续执行其他任务
        for (int i = 0; i < 3; i++) {
            System.out.println("主线程执行:" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在 Golang 中,执行异步代码单元通常不需要创建线程,而是通过创建 goroutine 来实现。Goroutine 是 Go 语言的轻量级线程,可以非常高效地执行并发任务。要执行异步代码单元,只需将代码包装成一个函数,并使用 go 关键字调用该函数,这将创建一个新的 goroutine 来执行该函数中的代码。以下是一个简单的示例:

package main

import (
	"fmt"
	"time"
)

// 异步执行的函数
func asyncFunction() {
	for i := 0; i < 5; i++ {
		fmt.Println("异步执行:", i)
		time.Sleep(time.Second) // 模拟任务执行时间
	}
}

func main() {
	// 使用 go 关键字创建一个新的 goroutine 来执行 asyncFunction
	go asyncFunction()

	// 主线程可以继续执行其他任务
	for i := 0; i < 3; i++ {
		fmt.Println("主线程执行:", i)
		time.Sleep(time.Second)
	}

	// 等待一段时间以确保异步任务有足够的时间执行
	time.Sleep(5 * time.Second)
}

并发方面,Java 和 Golang 的区别

Java 和 Golang 在并发编程方面有一些显著的区别,这些区别主要涉及到并发模型、线程管理、内存模型等方面:

并发模型:

  • Java 使用基于线程的并发模型。在 Java 中,线程是基本的并发单元,开发人员需要显式创建和管理线程。Java提供了java.lang.Thread类以及java.util.concurrent包中的各种工具来处理多线程编程。
  • Golang 使用 goroutines 和通道(channels)的并发模型。Goroutines 是轻量级的用户级线程,Go语言的运行时系统负责调度它们。通道是用于在不同的 goroutines 之间传递数据的机制。这种模型更简单、更容易使用和更安全。

线程管理:

  • Java 的线程管理相对复杂。开发人员需要显式创建线程对象、启动线程、停止线程,以及处理线程的同步和协调。Java提供了synchronized关键字和各种锁来处理多线程同步。
  • Golang 的 goroutine 管理由运行时系统自动处理,开发人员只需要使用 go 关键字启动一个函数作为 goroutine,不需要手动创建和管理线程。Golang 提供了通道来进行 goroutine 之间的通信和同步,这大大简化了并发编程。

内存模型:

  • Java 使用 Java Memory Model(JMM)来管理多线程程序中的内存访问。JMM 规定了共享变量的可见性和操作的顺序。
  • Golang 使用了一种更简单和更可预测的内存模型。Golang 的内存模型是顺序一致性(Sequential Consistency)的,不需要开发人员担心复杂的内存可见性问题,因为 goroutines 之间的通信是通过通道进行的,不需要显式的锁来保护共享数据。

并发工具:

  • Java 提供了大量的并发工具和类库,如 java.util.concurrent 包,用于处理各种并发问题,包括线程池、锁、队列、原子操作等。
  • Golang 也提供了一些内置的并发工具,如 sync 包中的锁、select 语句用于选择通道操作、goroutine 和通道本身。这些工具相对较少,但足以处理大多数并发场景。

总的来说,Golang的并发编程模型更简单、更安全,适用于构建高并发的分布式系统和网络服务。Java则更适合传统的企业级应用和复杂的多线程场景,但需要更多的线程管理和同步工作。开发人员在选择编程语言时,应根据项目的需求和复杂性来考虑哪种并发模型更适合。

Java 和 Go 官方库中同步方式的对应关系

Java synchronized 与Golang Mutex

Java 中的 synchronized 关键字和 Golang 中的 Mutex(互斥锁)都是用于实现多线程同步的工具,但它们有一些不同之处。以下是它们的主要区别以及示例代码来说明这些区别。Java 的 synchronized 关键字:

  1. synchronized 关键字是 Java 的内置同步机制,可用于方法或代码块级别的同步。
  2. synchronized 关键字可用于实现方法级别的同步,也可以用于同步代码块。
  3. synchronized 关键字在进入同步块之前要获取锁,在退出同步块后释放锁,这是隐式的。
  4. 可以使用 synchronized 来实现对对象的同步,例如在方法声明上使用 synchronized,或者在代码块中使用 synchronized (object)
class SynchronizedExample {
    private int count = 0;
    
    // 同步方法,锁是该对象实例
    public synchronized void increment() {
        count++;
    }
    
    // 同步代码块,锁是指定的对象
    public void decrement() {
        synchronized (this) {
            count--;
        }
    }
}

Golang 的 Mutex(互斥锁):

  1. Golang 使用 sync 包中的 Mutex 类型来实现同步。
  2. Mutex 可以用于实现代码块级别的同步,但通常不用于方法级别的同步(因为 Golang 的并发模型更倾向于使用 goroutines 和通道来处理并发)。
  3. 在 Golang 中,需要显式地锁定和解锁 Mutex,以确保在临界区内只有一个 goroutine 可以访问共享资源。
  4. Mutex 的锁定和解锁是显式的,需要在代码中调用 Lock 和 Unlock 方法。

以下是 Golang 中 Mutex 的示例:

package main

import (
	"fmt"
	"sync"
)

func main() {
	var mu sync.Mutex
	count := 0

	// 同步代码块,锁定 mu
	mu.Lock()
	count++
	// 解锁 mu
	mu.Unlock()

	fmt.Println("Count:", count)
}

修饰结构体:带锁结构体初始化后,直接调用对应的线程安全函数就可以。

条件变量,基本不一样

相似点:

  1. 条件变量和锁的关系:在 Java 和 Golang 中,条件变量通常与锁(互斥锁或同步块)一起使用,以确保多个线程或 goroutines 安全地访问共享资源。条件变量提供了等待和通知机制,用于线程或 goroutines 之间的协调。
  2. 多条件等待:在 Java 中,你可以使用多个条件变量(通过 Lock 和 Condition 实现)来等待不同的条件,从而更精确地控制线程的等待和通知。在 Golang 中,sync.Cond 结构体可以用于等待和通知多个 goroutines。

区别点:

  1. 条件变量的创建:
  • Java 中,条件变量通常通过 Lock 接口的 newCondition 方法创建。
  • Golang 中,条件变量由 sync.Cond 结构体表示,但需要与 sync.Mutex 结合使用。条件变量是通过 sync.NewCond(&mutex) 来创建的,其中 mutex 是一个互斥锁。
  1. 挂起和唤醒的时机:
  • Java 中,条件变量的等待和通知通常是在锁的保护下执行的。Object 的 wait() 方法和 notify()、notifyAll() 方法都在获取锁后才能调用。
  • Golang 中,sync.Cond 的 Wait() 方法是在获取锁后调用的,但是通知方法 Broadcast() 和 Signal() 是在解锁之后执行的,这意味着通知发出后,等待的 goroutines 需要重新竞争锁。

总的来说,条件变量在 Java 和 Golang 中的概念是相似的,都用于线程或 goroutines 之间的同步和通信。然而,它们在具体的使用方式和时机上存在一些区别,尤其是在 Golang 中,条件变量的通知不需要在锁的保护下执行,这可以避免一些潜在的死锁问题,但也需要开发人员更小心地处理竞争条件。

CAS/Atomic

在Java中:

  • CAS 操作由 volatile 关键字和 java.util.concurrent.atomic 包中的类支持,例如 AtomicInteger、AtomicLong 等。这些类提供了原子操作,可用于对整数和长整数进行原子操作。
  • java.util.concurrent 包还提供了一些无锁的数据结构,例如 ConcurrentHashMap、ConcurrentLinkedQueue 等,它们使用 CAS 操作来实现线程安全性。
  • Java 9 引入了 VarHandle,它提供了更通用的原子操作,不仅限于整数和长整数。

在Golang中:

  • Golang 的 sync/atomic 包提供了一组原子操作函数,可以用于对整数类型进行原子操作,例如 atomic.AddInt32、atomic.CompareAndSwapInt64 等。这些函数可以用于保证多goroutine环境下的原子性操作。
  • atomic.Value 类型提供了一种原子操作机制,允许你存储任意类型的值,并对其进行原子性的加载和存储操作。
  • Golang 的内置映射(map)类型是非线程安全的,但你可以使用 sync.Map 类型来实现线程安全的映射,它使用 CAS 操作来保证线程安全性。

总之,Java和Golang都支持CAS操作和原子操作,但在具体的实现和用法上有一些区别。在多线程或多goroutine环境下,这些操作可以确保共享变量的原子性,避免竞态条件和数据竞争问题。在选择哪种语言和库进行多线程编程时,可以根据具体的需求和语言特性来决定。

单例模式

Java懒汉式单例(Lazy Initialization):

public class LazySingleton {
    private static LazySingleton instance;

    private LazySingleton() {
        // 私有构造函数,防止外部直接实例化
    }

    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized (LazySingleton.class) {
                if (instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

饿汉式单例(Eager Initialization):

public class EagerSingleton {
    private static final EagerSingleton instance = new EagerSingleton();

    private EagerSingleton() {
        // 私有构造函数,防止外部直接实例化
    }

    public static EagerSingleton getInstance() {
        return instance;
    }
}

Golang的Once模式

package singleton

import (
    "sync"
)

type Singleton struct {
    // 在这里定义单例的属性
}

var instance *Singleton
var once sync.Once

func GetInstance() *Singleton {
    once.Do(func() {
        instance = &Singleton{}
    })
    return instance
}

垃圾回收机制

  1. GC策略的多样性:JVM支持多种GC策略,如分代GC、CMS(Concurrent Mark-Sweep)GC、G1(Garbage-First)GC等。这允许开发人员根据应用程序的需求选择最适合的策略。
  2. Go的单一GC方案:Go语言采用了一种单一的GC方案,它不分代,也不支持对象移动。这简化了GC的实现和配置,但也限制了对不同工作负载的优化。
  3. GC性能差异:由于Go的GC更频繁地触发(通常是通过并发标记清除算法实现的),因此它在某些情况下可能表现出更低的性能。相比之下,JVM的GC策略和性能更加灵活,可以根据需要进行调优。
  4. 暂停时间优化:Go非常注重减小垃圾回收的暂停时间,这对于需要低延迟的应用程序非常重要。Go的GC设计旨在最小化停顿时间,这在一些情况下可能比JVM更适用。

总之,每种编程语言和运行时环境都有其自己的GC实现和优化目标。Go的GC设计注重简单性和低延迟,而JVM的GC提供了更多的灵活性和优化选项,以适应不同类型的应用程序和工作负载。开发人员在选择编程语言和运行时环境时,需要根据项目的需求来考虑GC的性能和特性。

Java的垃圾回收

  1. 垃圾回收器(Garbage Collectors):Java的垃圾回收器有多种实现,包括G1(Garbage First)、CMS(Concurrent Mark-Sweep)、Serial、Parallel(Parallel Scavenge和Parallel Old)等。每个垃圾回收器都有其自己的特点和适用场景。
  2. 垃圾回收算法:Java使用不同的垃圾回收算法来管理内存,主要有标记-清除(Mark and Sweep)、标记-整理(Mark and Compact)、复制(Copying)和分代收集(Generational Collection)等。不同的算法用于处理不同内存区域的垃圾回收。
  3. 可达性算法:Java使用可达性分析(Reachability Analysis)来确定哪些对象是可达的,哪些对象是不可达的。这是判断对象是否可以被回收的重要依据。
  4. 引用类型:Java提供了不同类型的引用,包括强引用、软引用、弱引用和虚引用,用于更精细地控制对象的生命周期。
  5. JVM内存模型:Java虚拟机(JVM)内存模型定义了不同内存区域的用途和管理策略,例如新生代、老年代、持久代(在Java 7之前)、元空间(在Java 8及更高版本中)等。不同区域用于存储不同类型的对象,并且在垃圾回收时采用不同的算法。
  6. 内存压缩整理:你提到了压缩整理空间,这是垃圾回收过程中的一项重要任务。通过整理内存空间,可以减少内存碎片并提高内存利用率。

总之,Java的垃圾回收机制确实是一个庞大而复杂的体系,它的目标是自动管理内存,减轻开发人员的负担,并提供不同的算法和选项以适应不同类型的应用程序和工作负载。这使得Java成为一个非常强大和灵活的编程语言,适用于各种不同的应用场景。Golang GC特征

以下是一个简单的Java示例,演示了垃圾回收的行为:

public class GarbageCollectionDemo {
    public static void main(String[] args) {
        // 创建一个对象
        MyClass obj1 = new MyClass("Object 1");
        
        // 创建另一个对象
        MyClass obj2 = new MyClass("Object 2");
        
        // 让 obj1 不再被引用
        obj1 = null;
        
        // 强制垃圾回收
        System.gc();
        
        // 在这里等待一段时间,以便观察垃圾回收的效果
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class MyClass {
    private String name;

    public MyClass(String name) {
        this.name = name;
    }

    @Override
    protected void finalize() throws Throwable {
        System.out.println(name + " is being finalized");
    }
}

Golang的垃圾回收

Go语言(Golang)的垃圾回收是一种自动管理内存的机制,用于检测和回收不再被引用的对象,从而释放内存资源。Go的垃圾回收器(GC)是Go运行时的一部分,负责管理内存分配和回收。以下是关于Go垃圾回收的一些要点:

  1. GC触发:Go的垃圾回收器是并发的,它会在运行时自动触发。垃圾回收的触发通常是基于内存分配的情况,当分配的内存达到一定阈值时,GC会启动。
  2. 标记-清除算法:Go的垃圾回收器使用标记-清除算法。在标记阶段,它会标记所有仍然被引用的对象,然后在清除阶段,回收所有未标记的对象。
  3. 并发标记:Go的垃圾回收器采用并发标记的方式,这意味着垃圾回收可以与应用程序的执行并行进行。这有助于减小GC对应用程序性能的影响。
  4. 分代垃圾回收:与某些其他语言不同,Go没有严格的分代垃圾回收。Go的GC采用了一种基于内存分配速度的启发式方法,通常将新分配的对象放在新生代,而老年代的对象生命周期更长。
  5. 内存复用:Go的垃圾回收器非常注重内存的高效使用。它会尽量复用被回收的内存,而不是立即释放给操作系统。
  6. Stop-The-World(STW)时间的优化:Go的垃圾回收器努力减小STW的时间。这对于需要低延迟的应用程序非常重要。
  7. 手动GC控制:尽管Go的垃圾回收器通常是自动的,但你也可以使用runtime包中的函数来手动控制GC的行为,如runtime.GC()。

总之,Go语言的垃圾回收是一种自动且并发的机制,它负责管理内存,确保不再被引用的对象被及时回收,从而减少内存泄漏和提高应用程序的性能。Go的GC设计考虑了并发性、低延迟以及内存复用等因素,使其成为编写高性能和高并发应用程序的强大工具。

以下是一个简单的Go示例,演示了垃圾回收的行为:

package main

import (
    "fmt"
    "runtime"
    "time"
)

func main() {
    var obj *MyObject

    for i := 0; i < 10000; i++ {
        // 创建一个新的对象,并赋值给 obj
        obj = NewObject(i)

        // 手动调用垃圾回收
        runtime.GC()

        // 在这里等待一段时间,以便观察垃圾回收的效果
        time.Sleep(time.Millisecond * 100)
    }

    // 防止 obj 被编译器优化掉
    _ = obj
}

type MyObject struct {
    id int
}

func NewObject(id int) *MyObject {
    return &MyObject{id}
}

func (o *MyObject) String() string {
    return fmt.Sprintf("Object %d", o.id)
}

性能与资源使用情况

Java的JIT策略比Golang的AOT策略

Java的JIT(Just-In-Time)编译策略和Go的AOT(Ahead-Of-Time)编译策略是两种不同的编译和执行策略,它们在编译和性能方面有一些重要的区别。以下是它们的对比:

1. 编译时机:

  • JIT编译:在Java中,代码通常首先被编译成字节码(Bytecode),然后由Java虚拟机(JVM)在运行时动态编译成本地机器代码。这意味着Java应用程序的编译发生在运行时,因此称为JIT编译。
  • AOT编译:Go使用AOT编译策略,Go代码在构建时直接编译成本地机器代码,生成可执行文件。因此,Go应用程序的编译发生在构建时,不需要在运行时进行动态编译。

2. 性能优化:

  • JIT编译:JIT编译允许在运行时进行更多的性能优化,因为编译器可以根据实际运行时数据和环境进行决策。这可以导致更好的性能,尤其是对于长时间运行的应用程序。
  • AOT编译:AOT编译可以在构建时执行更多的静态分析和优化,但它不具备JIT编译的动态性能优化。因此,AOT编译通常适用于需要快速启动和更稳定性能的应用程序。

3. 启动时间:

  • JIT编译:JIT编译的应用程序在启动时可能需要更多时间来进行动态编译。这会导致较长的启动时间。
  • AOT编译:AOT编译的应用程序通常具有更快的启动时间,因为它们在构建时已经被编译成本地机器代码。

4. 开发体验:

  • JIT编译:Java的JIT编译允许更灵活的开发和调试体验,因为代码可以更快地重新编译和运行。
  • AOT编译:Go的AOT编译可能需要更多的构建时间,但在部署和执行时通常具有更好的性能和可预测性。

综上所述,JIT编译和AOT编译都有其适用的场景。JIT编译通常适用于需要动态性能优化的长时间运行的应用程序,而AOT编译适用于需要快速启动和更稳定性能的应用程序。选择哪种策略取决于应用程序的需求和性能目标。

生态环境

Java在生态系统和框架方面的强大是不可否认的。Spring生态系统的广泛应用确实使Java成为了许多企业级应用程序的首选开发语言之一。Spring框架提供了丰富的功能和模块,包括Spring Boot、Spring MVC、Spring Data、Spring Security等,使Java开发更加高效和便捷。相比之下,Go语言虽然在近年来的发展中取得了很大的成功,但在生态系统和框架方面相对较新。Go的成功主要体现在其简洁性、高性能和并发性等方面,这使得它在云原生开发、分布式系统和网络编程等领域表现出色。虽然Go的生态系统不如Java成熟,但Go也有一些知名的框架和库,例如:

  • Gin:一个轻量级的Web框架,适用于构建高性能的Web应用程序。
  • Echo:另一个流行的Web框架,也专注于性能和简洁性。
  • Django:用于构建Web应用程序的全栈框架。
  • Beego:一个全栈的Web框架,提供了一系列的工具和库。
  • gRPC:用于构建分布式系统的高性能RPC(远程过程调用)框架。

尽管Go的生态系统相对较新,但它正在不断发展,并且在一些领域表现出色。在选择编程语言和框架时,通常会根据项目的需求和目标来做出决策。有些项目可能更适合使用Java和Spring,而其他项目可能会更适合使用Go。两者都有自己的优势和特点,取决于你的具体用例。

简单总结

GolangJava:现代软件开发的两大工具对比
fudaihb的博客
04-09 1742
在现代软件开发领域,选择合适的编程语言对于项目的成功至关重要。在众多编程语言中,Golang(Go)和Java作为两大重要工具备受关注。Golang由谷歌开发,于2009年首次发布,旨在提供一种简洁、高效的编程语言,尤其适用于并发编程。Java则由Sun Microsystems于上世纪90年代推出,以其跨平台性、面向对象特性和强大的生态系统闻名于世,成为企业应用和大型系统开发的首选语言之一。
Go语言Java语言对比
Add小兵的博客
10-21 5998
宏观看Go与Java的差异 接着,我们来看一下Go语言Java的差异之处: ``` *.无虚拟机,不跨平台(这里的平台指操作系统)(可以运行多个平台,每个平台打不同的二进制程序包),需要打包编译成对应服务器操作系统版本(windows/linux)的可执行程序(比如windows是exe)。(注:说go跨平台的是指32位和64位相同操作系统之间的跨平台) ​ *.因为Go程序直接打包成操作系统可执行的文件,没有虚拟机在中间转换的一层,所以理论上执行效率会更高(理论上更高,实际情况需具
GolangJava:编程语言比较及如何选择
最新发布
欢迎拜读我的作品,喜欢的领域请给我留言
08-13 871
Java 最初的目的被誉为“一次编写,随处运行”的语言,可以创建小型、可靠、可移植、分布式和实时的应用程序,以满足几乎任何目的。时至今日,Golang(也称为“Go”)已被 Google 和全球各地的公司广泛使用,这些公司依赖开源技术来创建功能强大的后端应用程序,这些应用程序几乎可以完成任何事情。因此,如果您希望构建高度可扩展、性能良好的后端应用程序,Golang 就是您想要的语言Java 是可移植前端应用程序的出色选择,而 Golang 则是高度可扩展的后端应用程序的佼佼者。
Go和Java的区别
weixin_69163469的博客
06-11 1201
作为一名Java开发者,我早已习惯了其严谨的对象模型、丰富的API和强大的跨平台能力。然而,当我踏入Go语言的世界时,我被其简洁、高效和直接的风格深深吸引。Go语言,这个由Google精心打造的开源编程语言,不仅为我带来了全新的编程体验,也让我对编程有了更深刻的理解。在学习完Go的基本语法后,我深感两种语言在设计理念、语法特性和应用场景上的巨大差异。Go的简洁语法、强大的并发支持和静态类型安全,让我对编程有了全新的认识。与此同时,我也发现Java在面向对象编程、内存管理和生态系统方面的优势。
golangJava对比(整体和语法对比)
热门推荐
用文字记录下每一个瞬间的感动和每一次思考的火花
03-28 10万+
选择Golang还是Java取决于项目需求、团队的熟悉度和特定的应用场景。Golang以其简洁、高效和出色的并发支持,在云基础设施和微服务架构方面表现优异。而Java则以其强大的生态系统、跨平台能力和广泛的应用场景,在企业级应用和移动开发领域保持着强大的竞争力。选择哪种语言,应基于项目的具体需求和团队的技术栈进行决定。GolangJava
Go与java
weixin_45469930的博客
01-11 3475
Go与java基础 一.go的简介 Go语言是Google出品的近年来快速崛起的编程语言.简单高效是它的标签. 最近抽空从零开始学一下go.不罗嗦, 咱们从 java学习路线来 看看 go的实现吧~ 二.Go的基础语法 1.编译与运行 ##java用jvm做到程序与操作系统的隔离. 一次性编译生成的class文件,处处可以运行 ## 简单来说 windows上编译的出class文件,拿到安装了jvm的linux上可直接可以执行 javac Hello.java java Hello ## go 在不同的操
Go和Java的异同
qq_40229166的博客
03-26 3975
一、面向对象 1.Java是纯正的面向对象语言,Go相对于Java而言,就比较简洁,没有例如类的继承、接口的实现、构造函数和析构函数、隐藏的 this 指针等,也没有 public、protected、private 之类的访问修饰符。 2.类型系统 类型系统是指一个语言的类型体系结构。一个典型的类型系统通常包含如下基本内容: 基本类型,如 byte、int、bool、float、string 等; 复合类型,如数组、切片、字典、指针、结构体等; 可以指向任意对象的类型(Any 类型); 值语义和引用语义
GaussianBlur for golang go 语言图像处理库——高斯模糊.zip
04-14
包括C++、Java、python、web、C#、EDA等项目的源码。 【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】:项目具有较高的...
Go语言学习笔记——Golang 1.18新特性泛型
qq_39280718的博客
09-09 3287
泛型的英文是Generics,就是函数的参数,或者容器元素的类型,支持更广泛的类型,不再是特定的类型。
学习笔记整理——Go语言
A15280019132的博客
12-13 1003
个人Go语言笔记整理,不许你们看!
moutsea#TechFlow#Golang——Go语言中的变量声明1
07-25
第二点是Golang当中自带了map类型,像是java以及C++虽然也有map,但是都不是原生支持的,而是必须要通过引入包才可以使用的 第一种方式最复杂,我们不
Go语言学习笔记——配置管理库viper
qq_39280718的博客
09-07 660
viper是一个golang配置管理库,很多项目都使用viper来构建,例如:docker、Hugo等等
golang语言JAVA对比
hitpter的专栏
10-15 987
同时,我们也要意识到,技术的发展是不断变化的,新的编程语言和框架可能会出现并取代现有的技术。在同样的在线购物网站场景中,由于Java的面向对象特性和庞大的类库,开发人员可以更方便地构建复杂的业务逻辑和处理订单的流程。相比之下,虽然Golang的社区相对较小,但也有一些优秀的开源项目和框架,如Gin、Echo、Beego等,可以提供类似于Java的功能和支持。本文将探讨GolangJava之间的比较和对比,分析它们在语言特性、性能、平台支持、社区和生态系统、开发效率和可维护性等方面的异同。
Go和Java异同总结(项目开发和数据类型)
qq_40229166的博客
03-25 4378
之前比较熟悉Java,最近两天看了一些Go的内容,总结一下: Java是严格的面向对象语言,在一些基础语法上来说,Go就显得比较随意。Go非常的灵活,虽然也是强类型的语言,但是不需要显示的声明变量的类型,可以通过赋值来确定。主要从以下几个方面进行以下区分: 一、项目开发 ...
golangJava的简单介绍和对比
Rcain_R的博客
04-02 2261
golangJava的简单介绍和对比
java与go对比(go与java语言区别)
点点的博客
01-30 2万+
Go和Java作为在世界上影响巨大的两门开发语言,在语言特点和应用领域上都存在共通和相似之处。Go从2009年开源至今,在docker、K8s、企业后台等领域都取得了非凡的影响。本文以Golang的主要feature为研究对象,共分为6个章节,在学习这些特性的同时,给出其在Java中对应的实现方式,并会详细分析其中的原理和差异。 1.接口 在面向对象语言中,接口是一个绕不开的话题和特性,我们首先通过一段代码来看一下Go中的接口是如何设计和使用的。 go的实现代码 1、在代码中定义了两个结构体:Teac
Go语言 vs Java语言
Kaitiren的专栏
08-28 775
天生骄傲的Go语言以高并发闻名于世,牛皮不能光靠吹,今天我们就来做一个对比试验,体会一下Go在并发方面的巨大优势!靶子就瞄准Java,谁让它是后端开发语言的老大哥呢。 基本任务:初始化一个100*100的矩阵,矩阵每个元素是从[0,1)随机取的双精度浮点数,计算矩阵跟自身相乘,需要100万次乘法运算。 机器配置:Linux云主机,8核16G内存。 分别尝试开10、100、1000、10000、100000、1000000个线程(协程)执行上述的基本任务。 试验结果(运行耗时/ms): 并发度
Java与GO语言对比分析
爱学习的狗蛋儿
05-21 1277
你是不是总听到go与java种种对比,其中在高并发的服务器端应用场景会有人推荐你使用go而不是 java。那我们就从两者运行原理和基本并发设计来对比分析,看看到底怎么回事。
go语言java语言对比两者到底怎么选
xiazai167的博客
07-30 1546
Go语言底层也是C实现的,又做了高并发的设计(Java出生时(1995)还没有多核cpu,所以他的并发支持后来添加上去的,Go(2009)出生时已经有了多核cpu的电脑,它在设计语言时就考虑了充分利用多核cpu(英特尔2005首次推出多核)的性能),所以性能高,高并发的支持(高并发支持其中指的一个就是充分利用多核cpu的性能资源,比如go程序默认使用所有cpu(除非自己设置使用多少))也好。相比Java语言和代码编写风格,Go更简洁,可以用更少的代码实现同样的功能。2、宏观看Go与Java的差异。
Google对比实验:C++、Java、Go与Scala编程语言性能深度解析
在本篇报告中,我们深入探讨了Google公司对四种主流编程语言——C++、Java、Go和Scala的运行效率进行了全面的对比研究。这份实验报告由Robert Hundt撰写,他在Google的1600 Amphitheatre Parkway地址工作,电子邮件...
写文章

热门文章

  • 10个高质量免费学习网站 126532
  • 各大免费邮箱邮件群发账户SMTP服务器配置及SMTP发送量限制情况 76503
  • 曾国藩座右铭:“物来顺应,未来不迎,当时不杂,既过不恋”! 66257
  • Docker学习总结(13)——从零开始搭建Jenkins+Docker自动化集成环境 60287
  • Tomcat学习总结(2)——Tomcat部署Java War包应用教程 59565

分类专栏

  • ELK学习总结 付费 3篇
  • SpringBoot 付费 34篇
  • 架构设计 付费 66篇
  • JavaWeb 付费 44篇
  • Dubbo(x) 付费 12篇
  • Mysql 付费 89篇
  • 项目管理 付费 21篇
  • Linux 付费 93篇
  • Java 付费 207篇
  • 干货杂记 付费 165篇
  • Git 付费 28篇
  • JavaSctipt 付费 18篇
  • Maven 付费 60篇
  • Spring 付费 37篇
  • SpringMVC 付费 21篇
  • WebService 付费 6篇
  • Docker 付费 82篇
  • Json 付费 9篇
  • ActiveMQ 付费 10篇
  • Windows 付费 26篇
  • Oracle 付费 9篇
  • Myeclipse 付费 17篇
  • Tomcat 付费 20篇
  • App后台 付费 12篇
  • PHP 付费 14篇
  • 敏捷开发 付费 18篇
  • Jenkins 付费 10篇
  • Mybatis 付费 27篇
  • Nginx 付费 16篇
  • Redis 付费 24篇
  • Java设计模式 付费 16篇
  • 支付系统 付费 12篇
  • 消息中间件 付费 23篇
  • 信贷系统 付费 4篇
  • Zookeeper 付费 4篇
  • Spring Cloud与微服务 付费 19篇
  • RabbitMQ 付费 11篇
  • IntelliJ IDEA 付费 3篇
  • Kafka 1篇
  • Android 6篇
  • MongoDB 2篇
  • Netty 6篇
  • Vue.js 8篇
  • 分库分表 6篇
  • 云计算 3篇
  • Hadoop 3篇
  • 软件工程 4篇
  • Shiro 5篇
  • Beetl 4篇
  • Kubernetes 46篇
  • Solr 1篇
  • ECMAScript 6 2篇
  • 产品经理 4篇
  • SQLite 3篇
  • 互联网金融 7篇
  • Java多线程 5篇
  • 商城 1篇
  • 分布式SQL 2篇
  • Java虚拟机 4篇
  • 工作流引擎activiti
  • Java NIO
  • Python 2篇
  • UML 3篇
  • Jquery 7篇
  • HTML 2篇
  • XML 2篇
  • puppet 1篇
  • C/C++
  • Ehcache 3篇
  • SQL Server
  • Jsp
  • Ajax 2篇
  • JNDI 4篇
  • Hibernate
  • SSO单点登录 3篇
  • DB2 1篇
  • 算法与数据结构 3篇
  • ZooKeeper学习总结 3篇
  • Shiro学习总结 5篇
  • Quartz学习总结 2篇
  • Css学习总结 5篇
  • Spring Boot学习总结 18篇
  • 软件工程学习总结 3篇
  • Netty学习总结 5篇
  • FastDFS学习总结 2篇
  • Ansible 11篇
  • App后台开发运维和架构实践学习总结 13篇
  • PostgreSQL 12篇
  • VMware vSphere学习总结 3篇
  • PHP学习总结 14篇
  • Go 10篇
  • Nginx学习总结 12篇
  • Gradle 2篇
  • FreeMarker学习总结
  • Rust 2篇
  • 嵌入式数据库H2学习总结
  • Jetty学习总结
  • Beetl模板引擎学习总结 4篇
  • 敏捷开发系列学习总结 18篇
  • 分库分表学习总结 6篇
  • 项目管理学习总结 19篇
  • Jquery EasyUI学习总结
  • Python学习总结 1篇
  • Vue.js学习总结 2篇
  • 微信小程序开发学习总结
  • 区块链相关技术学习总结 1篇
  • 深度学习技术学习总结
  • 人工智能学习总结 1篇
  • Scala编程语言学习总结
  • Shell编程学习总结
  • Spring Cloud与微服务学习总结 13篇
  • 云计算学习总结 1篇
  • kafka消息中间件学习总结
  • Thymeleaf模板引擎学习总结 1篇
  • ECMAScript 6学习总结 2篇
  • Kotlin学习总结
  • Solr开源搜索服务器学习总结
  • SQLite学习总结 3篇
  • WebSocket学习总结
  • 微信开发学习总结
  • Kubernetes学习总结 3篇
  • Loki 1篇
  • 支付业务与技术架构学习总结 7篇
  • OpenStack学习总结
  • Serverless学习总结
  • OpenShift学习总结
  • Service Mesh学习总结
  • Java虚拟机学习总结 3篇
  • Java多线程学习总结 4篇
  • Java IO/NIO学习总结
  • Java设计模式学习总结 14篇
  • Terraform 10篇
  • IntelliJ IDEA学习总结 2篇
  • 权限系统设计 5篇
  • 产品经理学习总结 3篇
  • 消息中间件学习总结 20篇
  • Postman 2篇
  • 互联网金融 7篇
  • 商城系统学习总结 1篇
  • 信贷系统学习总结 5篇
  • ETL学习总结 2篇
  • 压测学习总结 7篇

最新评论

  • 算法学习总结(2)——温故十大经典排序算法

    weixin_38711958: 我是配角,怎敢吃醋

  • PostgreSQL学习总结(1)—— PostgreSQL 入门简介与安装

    weixin_49019522: 设置的口令是啥呀,是密码吗,为啥我打不上去字

  • PostgreSQL学习总结(1)—— PostgreSQL 入门简介与安装

    Dear-super-han: window安装出现Problem running post-install step.Installation may not complete correctlyFailed to start the database server.

  • Vue.js 学习总结(3)—— vite 打包图片时报错 Rollup failed to resolve import...

    m0_57226263: 谢谢up,管用

  • 项目管理学习总结(7)——阿里云架构师:技术高手转向管理体会

    weixin_40859798: 有人格魅力的人会让别人喜欢跟你干,跟你在一块搞事比较舒服,同时还有积极性,能聊到一块,能玩到一块,大平台靠平台收揽人,但能发挥多大价值还得看相处姿势舒不舒服,小平台能收揽大才,还能拼命干,那这个人可能看到一些潜在价值,蔡总跟着马云,得到的东西只会多不会少的啊,人格魅力最终还是看价值

大家在看

  • 【快速上手】linux环境下Neo4j的安装与使用
  • 简单生活的快乐 337
  • 黑神话:悟空 后话 189

最新文章

  • Spring Boot 学习总结(34)—— spring-boot-starter-xxx 和 xxx-spring-boot-starter 区别?
  • Vue.js 学习总结(8)—— Vue 3 的 Teleport 特性,让你实现跨组件传输内容
  • Docker 学习总结(83)—— 配置文件daemon.json介绍及优化建议
2024年11篇
2023年51篇
2022年140篇
2021年113篇
2020年108篇
2019年123篇
2018年168篇
2017年246篇
2016年523篇
2015年122篇
2014年1篇

目录

目录

分类专栏

目录

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一杯甜酒

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或 充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

玻璃钢生产厂家生产厂家玻璃钢雕塑熊大玻璃钢雕塑漯河玻璃钢坐凳雕塑定做巢湖玻璃钢雕塑制作河北大型商场创意商业美陈制作茂名玻璃钢动物雕塑怎么样济南步行街玻璃钢雕塑市场云浮玻璃钢景观人物雕塑湖北景观园林玻璃钢彩绘雕塑大连百货商场美陈吉林公园玻璃钢雕塑厂家甘南卡通玻璃钢雕塑设计梅州玻璃钢景观雕塑造型精美重庆玻璃钢雕塑修补景区玻璃钢雕塑报价景观小品玻璃钢环境艺术雕塑佛山桂城玻璃钢景观雕塑玻璃钢蝎子雕塑北欧风玻璃钢花盆沈阳关公玻璃钢雕塑制作玻璃钢仙鹤雕塑厂家运城玻璃钢人物雕塑定做菏泽玻璃钢花盆批发价格图片商场美陈布置工作亮点怎么写转卖商场美陈广州超市商场美陈广安玻璃钢海豚雕塑价格许昌玻璃钢卡通雕塑定制厂家宿州玻璃钢雕塑制造厂家黄山商场春节美陈香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化