PHP JAVA 面向对象疑惑整理

java中面向对象的修饰符

修饰符 本类内 同包内 子类中(不同包) 其他包中
private
默认(不写)
protected
public
修饰符 口诀
private 自家独享
默认 同包共享
protected 同包 + 子女共享
public 人人共享

PHP中面向对象的修饰符

修饰符 本类内 子类中 外部访问
private
protected
public
修饰符 口诀
private 自家独享
protected 自家 + 子女共享
public 人人共享

构造方法

java构造方法的名称就是类名。构造方法的参数没有限制,在方法内部,也可以编写任意语句。但是,和普通方法相比,构造方法没有返回值(也没有void),调用构造方法,必须用new操作符。
一个类没有定义构造方法,编译器会自动为我们生成一个默认构造方法,它没有参数,也没有执行语句,类似这样:

class Person {
    public Person() {
    }
}

自定义了一个构造方法,那么,编译器就不再自动创建默认构造方法
如果既要能使用带参数的构造方法,又想保留不带参数的构造方法,那么只能把两个构造方法都定义出来

// 构造方法
public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("Xiao Ming", 15); // 既可以调用带参数的构造方法
        Person p2 = new Person(); // 也可以调用无参数构造方法
    }
}

class Person {
    private String name;
    private int age;

    public Person() {
    }

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

    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }
}

可以定义多个构造方法,在通过new操作符调用的时候,编译器通过构造方法的参数数量、位置和类型自动区分

如果调用new Person("Xiao Ming", 20);,会自动匹配到构造方法public Person(String, int)。

如果调用new Person("Xiao Ming");,会自动匹配到构造方法public Person(String)。

如果调用new Person();,会自动匹配到构造方法public Person()。

一个构造方法可以调用其他构造方法,这样做的目的是便于代码复用。调用其他构造方法的语法是this(…):

class Person {
    private String name;
    private int age;

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

    public Person(String name) {
        this(name, 18); // 调用另一个构造方法Person(String, int)
    }

    public Person() {
        this("Unnamed"); // 调用另一个构造方法Person(String)
    }
}

方法重载

在一个类中,我们可以定义多个方法。如果有一系列方法,它们的功能都是类似的,只有参数有所不同,那么,可以把这一组方法名做成同名方法。例如,在Hello类中,定义多个hello()方法:

class Hello {
    public void hello() {
        System.out.println("Hello, world!");
    }

    public void hello(String name) {
        System.out.println("Hello, " + name + "!");
    }

    public void hello(String name, int age) {
        if (age < 18) {
            System.out.println("Hi, " + name + "!");
        } else {
            System.out.println("Hello, " + name + "!");
        }
    }
}

这种方法名相同,但各自的参数不同,称为方法重载(Overload)。
注意:方法重载的返回值类型通常都是相同的。
方法重载的目的是,功能类似的方法使用同一名字,更容易记住,因此,调用起来更简单。
举个例子,String类提供了多个重载方法indexOf(),可以查找子串:
int indexOf(int ch):根据字符的Unicode码查找;
int indexOf(String str):根据字符串查找;
int indexOf(int ch, int fromIndex):根据字符查找,但指定起始位置;
int indexOf(String str, int fromIndex)根据字符串查找,但指定起始位置。

继承

Student从Person继承时,Student就获得了Person的所有功能,我们只需要为Student编写新增的功能。
Java使用extends关键字来实现继承:

class Person {
    private String name;
    private int age;

    public String getName() {...}
    public void setName(String name) {...}
    public int getAge() {...}
    public void setAge(int age) {...}
}

class Student extends Person {
    // 不要重复name和age字段/方法,
    // 只需要定义新增score字段/方法:
    private int score;

    public int getScore() { … }
    public void setScore(int score) { … }
}

子类自动获得了父类的所有字段,严禁定义与父类重名的字段!
在OOP的术语中,我们把Person称为超类(super class),父类(parent class),基类(base class),把Student称为子类(subclass),扩展类(extended class)。
注意到我们在定义Person的时候,没有写extends。在Java中,没有明确写extends的类,编译器会自动加上extends Object。所以,任何类,除了Object,都会继承自某个类。
Java只允许一个class继承自一个类,因此,一个类有且仅有一个父类。只有Object特殊,它没有父类。
super关键字表示父类(超类)。子类引用父类的字段时,可以用super.fieldName 例如:

class Student extends Person {
    public String hello() {
        return "Hello, " + super.name;
    }
}

Java中,任何class的构造方法,第一行语句必须是调用父类的构造方法。如果没有明确地调用父类的构造方法,编译器会帮我们自动加一句super();
如果父类没有默认的构造方法,子类就必须显式调用super()并给出参数以便让编译器定位到父类的一个合适的构造方法
即子类不会继承任何父类的构造方法。子类默认的构造方法是编译器自动生成的,不是继承的。

阻止继承

正常情况下,只要某个class没有final修饰符,那么任何类都可以从该class继承。
从Java 15开始,允许使用sealed修饰class,并通过permits明确写出能够从该class继承的子类名称。

public sealed class Shape permits Rect, Circle, Triangle {
    ...
}

Shape 类 只能 Rect, Circle, Triangle 来继承

向上转型

如果一个引用变量的类型是Student,那么它可以指向一个Student类型的实例:

Student s = new Student();

如果一个引用类型的变量是Person,那么它可以指向一个Person类型的实例:

Person p = new Person();

现在问题来了:如果Student是从Person继承下来的,那么,一个引用类型为Person的变量,能否指向Student类型的实例?

Person p = new Student(); // ???

测试一下就可以发现,这种指向是允许的!

这是因为Student继承自Person,因此,它拥有Person的全部功能。Person类型的变量,如果指向Student类型的实例,对它进行操作,是没有问题的!

这种把一个子类类型安全地变为父类类型的赋值,被称为向上转型(upcasting)。

向上转型实际上是把一个子类型安全地变为更加抽象的父类型:

Student s = new Student();
Person p = s; // upcasting, ok
Object o1 = p; // upcasting, ok
Object o2 = s; // upcasting, ok

注意到继承树是Student > Person > Object,所以,可以把Student类型转型为Person,或者更高层次的Object。

向下转型

和向上转型相反,如果把一个父类类型强制转型为子类类型,就是向下转型(downcasting)。例如:

Person p1 = new Student(); // upcasting, ok
Person p2 = new Person();
Student s1 = (Student) p1; // ok
Student s2 = (Student) p2; // runtime error! ClassCastException!

如果测试上面的代码,可以发现:

Person类型p1实际指向Student实例,Person类型变量p2实际指向Person实例。在向下转型的时候,把p1转型为Student会成功,因为p1确实指向Student实例,把p2转型为Student会失败,因为p2的实际类型是Person,不能把父类变为子类,因为子类功能比父类多,多的功能无法凭空变出来。
因此,向下转型很可能会失败。失败的时候,Java虚拟机会报ClassCastException。

为了避免向下转型出错,Java提供了instanceof操作符,可以先判断一个实例究竟是不是某种类型:

Person p = new Person();
System.out.println(p instanceof Person); // true
System.out.println(p instanceof Student); // false

Student s = new Student();
System.out.println(s instanceof Person); // true
System.out.println(s instanceof Student); // true

Student n = null;
System.out.println(n instanceof Student); // false

instanceof实际上判断一个变量所指向的实例是否是指定类型,或者这个类型的子类。如果一个引用变量为null,那么对任何instanceof的判断都为false。

利用instanceof,在向下转型前可以先判断:

Person p = new Student();
if (p instanceof Student) {
    // 只有判断成功才会向下转型:
    Student s = (Student) p; // 一定会成功
}

从Java 14开始,判断instanceof后,可以直接转型为指定变量,避免再次强制转型。例如,对于以下代码:

Object obj = "hello";
if (obj instanceof String) {
    String s = (String) obj;
    System.out.println(s.toUpperCase());
}

可以改写如下:

// instanceof variable:
public class Main {
    public static void main(String[] args) {
        Object obj = "hello";
        if (obj instanceof String s) {
            // 可以直接使用变量s:
            System.out.println(s.toUpperCase());
        }
    }
}

这种使用instanceof的写法更加简洁。

Java初学第一篇

常用jdk版本
1.8(8)、17、23
下载地址
https://repo.huaweicloud.com/java/jdk/
https://repo.huaweicloud.com/openjdk/
https://d.injdk.cn/download/

基本数据类型是CPU可以直接进行运算的类型。Java定义了以下几种基本数据类型:

整数类型:byte,short,int,long
浮点数类型:float,double
字符类型:char
布尔类型:boolean

Java基本数据类型的字节大小

在 Java 中,每种基本数据类型占用的字节数是固定的,具体如下:
byte:1 字节
short:2 字节
int:4 字节
long:8 字节
float:4 字节
double:8 字节
char:2 字节(用于存储 Unicode 字符)

数据类型占用字节数

byte:1 字节(8 位)。适合表示 -128 到 127 范围内的整数。
short:2 字节(16 位)。适合表示 -32,768 到 32,767 范围内的整数。
int:4 字节(32 位)。适合表示 -2^31 到 2^31-1 范围内的整数。
long:8 字节(64 位)。适合表示 -2^63 到 2^63-1 范围内的整数。
float:4 字节(32 位)。用于表示单精度浮点数。
double:8 字节(64 位)。用于表示双精度浮点数。
char:2 字节(16 位)。用于表示单个字符(Unicode 编码)。

整型

对于整型类型,Java只定义了带符号的整型,因此,最高位的bit表示符号位(0表示正数,1表示负数)。各种整型能表示的最大范围如下:
byte:-128 ~ 127
short: -32768 ~ 32767
int: -2147483648 ~ 2147483647
long: -9223372036854775808 ~ 9223372036854775807
我们来看定义整型的例子:

// 定义整型
public class Main {
    public static void main(String[] args) {
        int i = 2147483647;
        int i2 = -2147483648;
        int i3 = 2_000_000_000; // 加下划线更容易识别 
        // 等价 int i3 = 2000000000;
        int i4 = 0xff0000; // 十六进制表示的16711680
        int i5 = 0b1000000000; // 二进制表示的512

        long n1 = 9000000000000000000L; // long型的结尾需要加L
        long n2 = 900; // 没有加L,此处900为int,但int类型可以赋值给long
        int i6 = 900L; // 错误:不能把long型赋值给int
    }
}

特别注意:同一个数的不同进制的表示是完全相同的,例如15=0xf=0b1111
注意 在 Java 7(JDK1.7)开始,引入了这个语法特性,允许在数字字面量中使用下划线作为“分隔符”,目的是提高大数字的可读性,比如像:

int oneBillion = 1_000_000_000;
long creditCardNumber = 1234_5678_9012_3456L;
int hexBytes = 0xFF_EC_DE_5E;

语法规则虽然你可以加下划线,但要遵守一些规则:

✅ 合法的 ❌ 非法的
1_000_000 _1000000(不能开头)
1000000_ _不能在结尾
1_000_000L 0x_FF_EC(不能在前缀 0x 和数字之间)
3.14_15 3._1415(不能在小数点前后直接用)
1_2_3_4 1__2(不能连续多个下划线)

浮点型

浮点类型的数就是小数,因为小数用科学计数法表示的时候,小数点是可以“浮动”的,如1234.5可以表示成12.345x102,也可以表示成1.2345x103,所以称为浮点数。
下面是定义浮点数的例子:

float f1 = 3.14f;
float f2 = 3.14e38f; // 科学计数法表示的3.14x10^38
float f3 = 1.0; // 错误:不带f结尾的是double类型,不能赋值给float
double d = 1.79e308;
double d2 = -1.79e308;
double d3 = 4.9e-324; // 科学计数法表示的4.9x10^-324

对于float类型,需要加上f后缀。
float 类型可表示的最大值是:3.4028235e+38
double 类型可表示的最大值是:1.7976931348623157e+308

布尔类型

布尔类型boolean只有true和false两个值,布尔类型总是关系运算的计算结果:

boolean b1 = true;
boolean b2 = false;
boolean isGreater = 5 > 3; // 计算结果为true
int age = 12;
boolean isAdult = age >= 18; // 计算结果为false

Java语言对布尔类型的存储并没有做规定,因为理论上存储布尔类型只需要1 bit,但是通常JVM内部会把boolean表示为4字节整数。

字符类型

字符类型char表示一个字符。Java的char类型除了可表示标准的ASCII外,还可以表示一个Unicode字符:

// 字符类型
public class Main {
    public static void main(String[] args) {
        char a = 'A';
        char zh = '中';
        System.out.println(a);
        System.out.println(zh);
    }
}

注意char类型使用单引号',且仅有一个字符,要和双引号"的字符串类型区分开。

引用类型

除了上述基本类型的变量,剩下的都是引用类型。例如,引用类型最常用的就是String字符串:

String s = "hello";

常量

定义变量的时候,如果加上final修饰符,这个变量就变成了常量:

final double PI = 3.14; // PI是一个常量
double r = 5.0;
double area = PI * r * r;
PI = 300; // compile error!

常量在定义时进行初始化后就不可再次赋值,再次赋值会导致编译错误。
常量的作用是用有意义的变量名来避免魔术数字(Magic number),例如,不要在代码中到处写3.14,而是定义一个常量。如果将来需要提高计算精度,我们只需要在常量的定义处修改,例如,改成3.1416,而不必在所有地方替换3.14。
为了和变量区分开来,根据习惯,常量名通常全部大写。

var关键字

有些时候,类型的名字太长,写起来比较麻烦。例如:

StringBuilder sb = new StringBuilder();

这个时候,如果想省略变量类型,可以使用var关键字:

var sb = new StringBuilder();

编译器会根据赋值语句自动推断出变量sb的类型是StringBuilder。对编译器来说,语句:

var sb = new StringBuilder();

实际上会自动变成:

StringBuilder sb = new StringBuilder();

因此,使用var定义变量,仅仅是少写了变量类型而已。

Dify 社区版Docker部署-(私有化部署知识库1)

克隆代码

git clone https://github.com/langgenius/dify.git

启动Dify

进入 Dify 源代码的 Docker 目录

cd dify/docker
复制环境配置文件
cp .env.example .env
启动 Docker 容器

根据你系统上的 Docker Compose 版本,选择合适的命令来启动容器。你可以通过 $ docker compose version 命令检查版本,详细说明请参考 Docker 官方文档:

如果版本是 Docker Compose V2,使用以下命令:

docker compose up -d

如果版本是 Docker Compose V1,使用以下命令:

docker-compose up -d

更新 Dify

进入 dify 源代码的 docker 目录,按顺序执行以下命令:

cd dify/docker
docker compose down
git pull origin main
docker compose pull
docker compose up -d

同步环境变量配置 (重要!)
如果 .env.example 文件有更新,请务必同步修改你本地的 .env 文件。

检查 .env 文件中的所有配置项,确保它们与你的实际运行环境相匹配。你可能需要将 .env.example 中的新变量添加到 .env 文件中,并更新已更改的任何值。

修改默认端口

EXPOSE_NGINX_PORT=80
EXPOSE_NGINX_SSL_PORT=443

Rust初学-安装篇

mac下安装

首先通过homebrew安装rustup管理工具:

brew install rustup

安装完后发现并不能够找到rustup指令,通过brew list进行查询,发现实际上安装的为rustup-init,于是再在命令行执行:

rustup-init

顺着流程安装完成之后,重启终端便可以安装好rust环境以及相关的工具链。

可以查看对应rustc以及cargo的版本:

rustc --version
rustc 1.53.0 (53cb7b09b 2021-06-17)
cargo --version
cargo 1.53.0 (4369396ce 2021-04-27)

简单的创建一个rust语言版的Hello World进行测试:

fn main() {
    println!("Hello World!");
}

将其保存为hello.rs。

在命令行使用rustc将其编译为可执行文件:

rustc hello.rs

之后直接执行可执行文件,便可以在终端看到对应的输出了!

./hello
Hello World!

打开本地文档

rustup doc

更新

 rustup update

卸载

rustup self uninstall

关于时序数据库influxdb的基本说明与使用整理

InfluxDB 是一种时序数据库,时序数据库通常被用在监控场景,比如运维和 IOT(物联网)领域。这类数据库旨在存储时序数据并实时处理它们。
可以写一个程序将服务器上 CPU 的使用情况每隔 10 秒钟向 InfluxDB 中写入一条数据。接着,我们写一个查询语句,查询过去 30 秒 CPU 的平均使用情况,然后让这个查询语句也每隔 10 秒钟执行一次。最终,我们配置一条报警规则,如果查询语句的执行结果>xxx,就立刻触发报警。
上述就是一个指标监控的场景,在 IOT 领域中,也有大量的指标需要我们监控。比如, 机械设备的轴承震动频率,农田的湿度温度等等。

为什么不用关系型数据库

  1. 写入性能
    关系型数据库也是支持时间戳的,也能够基于时间戳进行查询。但是,从我们的使用场景出发,需要注意数据库的写入性能。通常,关系型数据库会采用 B+树数据结构,在数据写入时,有可能会触发叶裂变,从而产生了对磁盘的随机读写,降低写入速度。
    数据价值

  2. 时间不可倒流,数据只写不改
    我们之前说,时序数据库一般用于指标监控场景。这个场景的数据有一个非常明显的特点就是冷热差别明显。通常,指标监控只会使用近期一段时间的数据,比如我只查询某个设备最近 10 分钟的记录,10 分钟前的数据我就不再用了。那么这 10 分钟前的数据,对我们来说就是冷数据,应该被压缩放到磁盘里去来节省空间。而热数据因为经常要用,数据库就应该让它留在内存里,等待查询。而市面上的时序数据库大都有类似的设计。

  3. 时间不可倒流,数据只写不改
    时序数据是描述一个实体在不同时间所处的不同状态。
    就像是我们打开任务管理器,查看 CPU 的使用情况。我发现 CPU 占用率太高了,于是杀死了一个进程,但 10 秒前的数据不会因为我关闭进程再发生改变了。这是时序数据的一大特点。与之相应,时序数据库基本上是插入操作较多,而且还没有什么更新需求。

一些概念

Bucket桶
是influxDb中基本存储单元 用于组织和存储时间序列数据 他是数据的逻辑容器,用于区分和隔离不同类型或来源的数据
oraganization 组织
用于隔离和管理用户 应用程序或者项目 每个用户可以属于一个或者多个组织 Bucket 也属于一个组织
Measurement 测量值
是influxDb中的数据表的概念 他类似关系型数据库中的数据表 用于存储时间序列数据 每个Measurement 包含一组字段和标签
Field 字段
field 是 Measurement 中的一种数据类型 用于存储实际的数值数据 例如温度 湿度 等是字段的例子
Tag标签
Tag 是 Measurement 中的一种索引类型 用于标识和过滤数据 标签通常用于存储维度信息 例如设备ID 地理位置 等是字段的例子
Point数据点
Point 是influxDb中时间序列数据的基本单位 它包含了时间戳 字段 标签 每个数据点都关联到一个Measurement
API密钥和访问令牌
为了进行安全的访问和操作
2.x 引入了Api 密钥和访问令牌 API密钥用于身份验证 而访问令牌用于授权对数据的访问
Flux查询语言
Flux是2.x 中的查询语言 用于执行强大的数据处理和分析操作 它支持灵活的 查询 过滤 聚合和转换操作
仪表盘和可视化
2.x 提供了仪表盘 和可视化工具 允许用户创建 定制 和共享图表 和仪表盘 以直观的展示时间序列数据

概念是oraganization,所有的数据都是属于org的。数据的存储都是在bucket里面。可以理解为数据库,2.x版本中没有database这个概念了。measurement可以理解成为一张表,但是实际上不是一个表。数据中point表示一条数据,每条数据都有一个_time表示时间戳,series表示point的集合,field是固定字段_field,key和value都是值,没有索引,tag是自定义字段名和值,索引的结合

1 2 3 4 5 12