GoToCode


  • 首页

  • 标签

  • 归档

  • 站点地图

枚举类型需要重写hashcode()吗?

发表于 2020-04-07

需要重写equals()方法的类,也需要重写hashcode(),不然在使用HashMap或者HashSet类时,会出现不正确的结果。
那么什么时候需要equals()方法呢?
Object类的equals()实现为比较对象的引用地址,所以,不想比较对象引用,而比较其他情况时,需要重写equals()方法。
大多情况下,我们需要比较对象的变量是否相等,此时,这种类型被称为“值类型”。
对于枚举类型,如果我们需要比较其变量相等情况时,看看是否需要重写equals()和hashcode()?

语法糖

枚举类型enum为Java语言的一个语法糖,可以使用反编译工具,看一下Java编译器是否如何实现enum的。
源代码:

1
2
3
4
enum Sex{
MALE,
FAMALE
}

反编译的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static final class JavaSugar.Sex
extends Enum<JavaSugar.Sex> {
public static final /* enum */ JavaSugar.Sex MALE = new JavaSugar.Sex((String)"MALE", (int)0);
public static final /* enum */ JavaSugar.Sex FAMALE = new JavaSugar.Sex((String)"FAMALE", (int)1);
private static final /* synthetic */ JavaSugar.Sex[] $VALUES;

public static JavaSugar.Sex[] values() {
return (JavaSugar.Sex[])$VALUES.clone();
}

public static JavaSugar.Sex valueOf(String name) {
return Enum.valueOf(JavaSugar.Sex.class, (String)name);
}

private JavaSugar.Sex(String string, int n) {
super((String)string, (int)n);
}

static {
$VALUES = new JavaSugar.Sex[]{MALE, FAMALE};
}
}

通过反编译后的代码,我们可以看到:

  • 构造函数为私有的,所有的枚举值都是静态创建。这属于懒汉式的单例模式,并且创建过程是线程安全的。
  • 类型和所有的枚举值都由final来修饰,并且所有方法对于变量操作都是只读的。说明这是一个不可变类型。

枚举同时具备单例模式和不可变类型,这样整个Java应用中的枚举类型实例就只有定义的枚举值(MALE和FAMALE)。
这样,不同枚举值的变量(name)理论上是不会出现相等情况的(除非定义枚举值使用相同的变量值,但这个枚举的意义是相悖的)。引用不同的枚举对象,其枚举变量肯定是不同的。
所以,结果是: 枚举类型不需要重写equals()方法和hashcode()方法。

枚举类型的反序列化

枚举类型是否还有其他方式创建枚举值?我不能确定反序列化是否有新的方式来创建枚举值。
通过阅读反序列化的源代码,可以窥探一二。
默认情况下,序列化枚举实例,得到的结果为枚举的name变量值。
Enum类,提供了valueOf(Class<T> enumType,String name)方法,可以使用name值得到枚举实例。
其源代码:

1
2
3
4
5
6
7
8
9
10
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}

再看一下enumConstantDirectory()方法:

  • enumConstantDirectory为一个name->Enum Type的Map
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Map<String, T> enumConstantDirectory() {
    if (enumConstantDirectory == null) {
    T[] universe = getEnumConstantsShared();
    if (universe == null)
    throw new IllegalArgumentException(
    getName() + " is not an enum type");
    Map<String, T> m = new HashMap<>(2 * universe.length);
    for (T constant : universe)
    m.put(((Enum<?>)constant).name(), constant);
    enumConstantDirectory = m;
    }
    return enumConstantDirectory;
    }
    private volatile transient Map<String, T> enumConstantDirectory = null;

再看一下getEnumConstantsShared()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
T[] getEnumConstantsShared() {
if (enumConstants == null) {
if (!isEnum()) return null;
try {
final Method values = getMethod("values");
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
public Void run() {
values.setAccessible(true);
return null;
}
});
@SuppressWarnings("unchecked")
T[] temporaryConstants = (T[])values.invoke(null);
enumConstants = temporaryConstants;
}
// These can happen when users concoct enum-like classes
// that don't comply with the enum spec.
catch (InvocationTargetException | NoSuchMethodException |
IllegalAccessException ex) { return null; }
}
return enumConstants;
}

上述getMethod("values")方法,应该就是下面的方法。这样的话,valueOf(Class<T> enumType,String name)方法返回的枚举实例,并没有创建新的实例。

1
2
3
public static JavaSugar.Sex[] values() {
return (JavaSugar.Sex[])$VALUES.clone();
}

反序列化,有很多方式,这里看一下ObjectInputStream和Gson两种方式:

ObjectInputStream

其核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private Enum<?> readEnum(boolean unshared) throws IOException {
if (bin.readByte() != TC_ENUM) {
throw new InternalError();
}

ObjectStreamClass desc = readClassDesc(false);
if (!desc.isEnum()) {
throw new InvalidClassException("non-enum class: " + desc);
}

int enumHandle = handles.assign(unshared ? unsharedMarker : null);
ClassNotFoundException resolveEx = desc.getResolveException();
if (resolveEx != null) {
handles.markException(enumHandle, resolveEx);
}

String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
try {
@SuppressWarnings("unchecked")
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
} catch (IllegalArgumentException ex) {
throw (IOException) new InvalidObjectException(
"enum constant " + name + " does not exist in " +
cl).initCause(ex);
}
if (!unshared) {
handles.setObject(enumHandle, result);
}
}

handles.finish(enumHandle);
passHandle = enumHandle;
return result;
}

其创建实例的代码:

1
2

Enum<?> en = Enum.valueOf((Class)cl, name);

所以,其不会创建新的枚举实例。

Gson

核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static final class EnumTypeAdapter<T extends Enum<T>> extends TypeAdapter<T> {
public EnumTypeAdapter(Class<T> classOfT) {
try {
for (T constant : classOfT.getEnumConstants()) {
String name = constant.name();
SerializedName annotation = classOfT.getField(name).getAnnotation(SerializedName.class);
if (annotation != null) {
name = annotation.value();
for (String alternate : annotation.alternate()) {
nameToConstant.put(alternate, constant);
}
}
nameToConstant.put(name, constant);
constantToName.put(constant, name);
}
} catch (NoSuchFieldException e) {
throw new AssertionError(e);
}
}
}

classOfT.getEnumConstants()方法的源代码如下

1
2
3
4
public T[] getEnumConstants() {
T[] values = getEnumConstantsShared();
return (values != null) ? values.clone() : null;
}

这个getEnumConstantsShared()方法,我们上面已经看过了。
这样,Gson反序列化,也不会产生新的实例。

总结

反序列化,并不会创建新的枚举实例。

Java反编译工具推荐-Bytecode Viewer

发表于 2020-04-06

语法糖

Java语言设计者为了在工程上提高开发效率,设计了很多语法糖。
Java反编译工具可以反编译class文件或jar包文件,得到Java源代码。
只是,有时候我们需要得到包含语法糖的代码,有时候,需要“剥开”语法糖的代码。
比如,需要了解代码的业务逻辑是,带糖的代码更易读;我们在研究Java编译逻辑时,需要不带糖的代码,看看这些糖到底隐藏了什么。

Fernflower

IDEA 一般会默认安装插件Java Bytecode Decompiler.这个插件使用了Java反编译工具Fernflower,而Fernflower反编译后源代码很多是包含语法糖的。
enum是Java的一个语法糖,这里以其为例,看一下效果:

1
2
3
4
public enum Sex {
MALE,
FEMALE;
}

查看class文件,和源代码基本没有差异

1
2
3
4
5
6
7
8
9
10
11
12
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

public enum Sex {
MALE,
FEMALE;

private Sex() {
}
}

CFR

CFR是另外一个Java反编译工具。
其提供了一些开关,让我们选择生成的源代码是否包含糖。
但是,其生成的源代码为纯文本文件,不是一个GUI工具,用户体验并不好。

Bytecode Viewer

Bytecode Viewer是一个强大的反编译工具,其集成了6个Java反编译库(包含Fernflower和CFR),Andorid反编译类库和字节码类库。
这个工具提供GUI界面,可以提升使用CFR的用户体验。
下面上面enum示例的反编译效果:

  • 左边为Fernflower的反编译效果
  • 右边为CRF的反编译效果。我们可以看到enum其实是定义了一个继承自Enum类的子类
    avatar

下图展示了CFR的语法糖开关:
avatar

一种处理Elasticsearch对象数组类型的方式

发表于 2020-03-10

目前情况

Elasticsearch中处理对象数组有两种格式array和nested,但这两种都有一定的不足。
以下面的文档为例:

1
2
3
4
5
6
7
8
9
10
11
12
{
"user": [
{
"first": "John",
"last": "Smith"
},
{
"first": "Alice",
"last": "White"
}
]
}

如果在mapping中以array存储,那么实际存储为:

1
2
user.first:["John","Alice"]
user.last:["Smith","White"]

如果以must的方式查询user.first:John和user.last:White,那么这篇文档也会命中,这不是我们期望的。

如果在mapping中以array存储,Elasticsearch将每个对象视为一个doc,这例子会存储3个doc,会严重影响ES写入和查询的效率。

Flatten格式

我想到的存储方式很简单,就是将对象数组打平保存为一个keyword类型的字符串数组,故起名Flatten格式。
以上面文档为例,数组对象需要转换为下面的格式

1
2
3
4
5
6
7
8
"user.flatten": [
"first:John",
"last:Smith",
"first:John&last:Smith",
"first:Alice",
"White:last",
"first:Alice&last:White"
]

这样以must的方式查询user.first:John和user.last:White,可以转换为term查询first:John&last:White,并不会命中文档。
同时,这种方式还是保存1个doc,避免了nested的缺点。

对于flatten格式有几点说明

user.flatten数组的大小

如果user对象个数为M,user属性个数为N,那么其数组大小为(2^N-1)*M。

对象为空怎么处理

建议以null方式保存,例如:

1
2
3
4
{
"first": "John",
"last": null
}

转换后的格式

1
2
3
4
5
[
"first:John",
"last:null",
"first:John&last:null",
]

保存和查询对于对象属性的处理顺序要保持一致

上述例子都是按first&last顺序存储的,那么以must的方式查询user.first:John和user.last:White也要以first:John&last:White方式查询,不能用last:White&first:John。

不足

  • 需要自己编码将JSON对象转换为字符串数组
  • 需要自己编码转换查询语句
  • 只支持term查询

使用Elasticsearch实现同段和同句搜索

发表于 2020-03-07

同句搜索要求搜索多个关键词时,返回的文章不只要包含关键词,而且这些关键词必须在同一句中。
同段搜素类似,只是范围为同一段落。

SpanQuery

同段、同句搜索,使用常用的term、match查询,没有找到办法可以实现。
Elasticsearch提供了SpanQuery,官方文档中如下的介绍:

Span queries are low-level positional queries which provide expert control over the order and proximity of the specified terms. These are typically used to implement very specific queries on legal documents or patents.

上面提到,SpanQuery常常应用在法律或专利的特定搜索。这些领域,常常提供同段 /同句搜索 。
下面我们看一下三种类型的SpanQuery,能否实现我们的需求:

准备数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PUT article


POST article/_mapping
{
"properties": {
"maincontent": {
"type": "text"
}
}
}


POST article/_doc/1
{
"maincontent":"the quick red fox jumps over the sleepy cat"
}


POST article/_doc/2
{
"maincontent":"the quick brown fox jumps over the lazy dog"
}

SpanTermQuery

SpanTermQuery 和 Term Query类似, 下面的查询会返回_id为1的doc。
the quick red fox jumps over the sleepy cat

1
2
3
4
5
6
7
8
9
10
11
POST article/_search
{
"profile": "true",
"query": {
"span_term": {
"maincontent": {
"value": "red"
}
}
}
}

SpanNearQuery

SpanNearQuery 表示邻近搜索,查找多个term是否邻近,slop可以设置邻近距离,如果设置为0,那么代表两个term是挨着的,相当于matchphase
in_order参数,代表文档中的term和查询设置的term保持相同的顺序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
POST article/_search
{
"query": {
"span_near": {
"clauses": [
{
"span_term": {
"maincontent": {
"value": "quick"
}
}
},
{
"span_term": {
"maincontent": {
"value": "brown"
}
}
}
],
"slop": 0,
"in_order": true
}
}
}

上面的查询会返回_id为2的doc。

the quick brown fox jumps over the lazy dog

SpanNotQuery

SpanNotQuery非常重要,它要求两个SpanQuery的跨度,不能够重合。
看下面的例子:

  • include: 匹配的SpanQuery,例子为需要一个包含quick和fox两个词的邻近搜索。
  • exclude:设置一个SpanQuery,要求include中的SpanQuery不能包含这个SpanQuery
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    POST article/_search
    {
    "query": {
    "span_not": {
    "include": {
    "span_near": {
    "clauses": [
    {
    "span_term": {
    "maincontent": {
    "value": "quick"
    }
    }
    },
    {
    "span_term": {
    "maincontent": {
    "value": "fox"
    }
    }
    }
    ],
    "slop": 1,
    "in_order": true
    }
    },
    "exclude": {
    "span_term": {
    "maincontent": {
    "value": "red"
    }
    }
    }
    }
    }
    }

上面的查询会返回_id为2的doc。
因为_id为1的文档,虽然quick red fox符合include中的SpanQuery,但是red也符合exclude中的SpanQuery。因此,这篇文章需要排除掉。
the quick red fox jumps over the sleepy cat

同句/同段搜索原理

同句搜索,反向来说,就是搜索词不能够跨句。再进一步,就是搜索词之间不能够有。 、?、!等其他标点符号。
其对应的查询类似如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
POST article/_search
{
"query": {
"span_not": {
"include": {
"span_near": {
"clauses": [
{
"span_term": {
"maincontent": {
"value": "word1"
}
}
},
{
"span_term": {
"maincontent": {
"value": "word2"
}
}
}
],
"slop": 1,
"in_order": true
}
},
"exclude": {
"span_term": {
"maincontent": {
"value": "。/?/!"
}
}
}
}
}
}

同段搜素类似,对应分隔符变为\n,或者<p>,</p>

同段/同句搜索实现

文本为HTML格式

创建索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
PUT sample1
{
"settings": {
"number_of_replicas": 0,
"number_of_shards": 1,
"analysis": {
"analyzer": {
"maincontent_analyzer": {
"type": "custom",
"char_filter": [
"sentence_paragrah_mapping",
"html_strip"
],
"tokenizer": "ik_max_word"
}
},
"char_filter": {
"sentence_paragrah_mapping": {
"type": "mapping",
"mappings": [
"""<h1> => \u0020paragraph\u0020""",
"""</h1> => \u0020sentence\u0020paragraph\u0020 """,
"""<h2> => \u0020paragraph\u0020""",
"""</h2> => \u0020sentence\u0020paragraph\u0020 """,
"""<p> => \u0020paragraph\u0020""",
"""</p> => \u0020sentence\u0020paragraph\u0020 """,
"""! => \u0020sentence\u0020 """,
"""? => \u0020sentence\u0020 """,
"""。 => \u0020sentence\u0020 """,
"""? => \u0020sentence\u0020 """,
"""! => \u0020sentence\u0020"""
]
}
}
}
},
"mappings": {
"properties": {
"mainContent": {
"type": "text",
"analyzer": "maincontent_analyzer",
"search_analyzer": "ik_smart"
}
}
}
}

我们创建了一个名称为sentence_paragrah_mapping的char filter,它的目的有两个:

  • 替换p,h1,h2标签为统一的分段符:paragraph;
  • 替换中英文 !,?,。 标点符号为统一的分页符:sentence。

有几个细节,需要说明:

  • paragraph和sentence前后都需要添加空格,并且需要使用Unicode \u0020表示空格。

    1
    2
    3
    4
    # 期望
    hello world! => hello world sentence
    # 不合理的配置,可能会出现下面的情况
    hello world! => hello worldsentence
  • </p>,</h1>,</h2>的结尾标签需要添加paragraph和sentence两个分隔符,避免结尾没有标点符号的情况

1
2
3
4
5
6
7
8
9
10
# 期望
<h1>hello world</h1> <p>hello china</p> => paragraph hello world sentence paragraph hello china sentence

# </p>,</h1>,</h2>只使用paragraph替换的结果
# 此时 hello world hello china 为同句
<h1>hello world</h1> <p>hello china</p> => paragraph hello world paragraph hello china sentence

# 上面配置结果有些冗余:有两个连续的paragraph
# 如果能保证HTML文本都符合标准,可以只替换</p>,</h1>,</h2>,不替换<p>,<h1>,<h2>
<h1>hello world</h1> <p>hello china</p> => paragraph hello world sentence paragraph paragraph hello china sentence
  • 注意sentence_paragrah_mapping和html_strip的配置顺序

插入测试数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
POST sample1/_doc/1
{
"mainContent":"<p>java python javascript</p><p>oracle mysql sqlserver</p>"
}

# 测试分词
POST sample1/_analyze
{
"text": ["<p>java python javascript</p><p>oracle mysql sqlserver</p>"],
"analyzer": "maincontent_analyzer"
}

# 返回结果
{
"tokens" : [
{
"token" : "paragraph",
"start_offset" : 1,
"end_offset" : 2,
"type" : "ENGLISH",
"position" : 0
},
{
"token" : "java",
"start_offset" : 3,
"end_offset" : 7,
"type" : "ENGLISH",
"position" : 1
},
{
"token" : "python",
"start_offset" : 8,
"end_offset" : 14,
"type" : "ENGLISH",
"position" : 2
},
{
"token" : "javascript",
"start_offset" : 15,
"end_offset" : 25,
"type" : "ENGLISH",
"position" : 3
},
{
"token" : "sentence",
"start_offset" : 26,
"end_offset" : 28,
"type" : "ENGLISH",
"position" : 4
},
{
"token" : "paragraph",
"start_offset" : 28,
"end_offset" : 28,
"type" : "ENGLISH",
"position" : 5
},
{
"token" : "paragraph",
"start_offset" : 30,
"end_offset" : 31,
"type" : "ENGLISH",
"position" : 6
},
{
"token" : "oracle",
"start_offset" : 32,
"end_offset" : 38,
"type" : "ENGLISH",
"position" : 7
},
{
"token" : "mysql",
"start_offset" : 39,
"end_offset" : 44,
"type" : "ENGLISH",
"position" : 8
},
{
"token" : "sqlserver",
"start_offset" : 45,
"end_offset" : 54,
"type" : "ENGLISH",
"position" : 9
},
{
"token" : "sentence",
"start_offset" : 55,
"end_offset" : 57,
"type" : "ENGLISH",
"position" : 10
},
{
"token" : "paragraph",
"start_offset" : 57,
"end_offset" : 57,
"type" : "ENGLISH",
"position" : 11
}
]
}

测试查询

  • 同段查询:java python
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
GET sample1/_search
{
"query": {
"span_not": {
"include": {
"span_near": {
"clauses": [
{
"span_term": {
"mainContent": {
"value": "java"
}
}
},
{
"span_term": {
"mainContent": {
"value": "python"
}
}
}
],
"slop": 12,
"in_order": false
}
},
"exclude": {
"span_term": {
"mainContent": {
"value": "paragraph"
}
}
}
}
}
}

//结果
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.1655603,
"hits" : [
{
"_index" : "sample1",
"_type" : "_doc",
"_id" : "1",
"_score" : 0.1655603,
"_source" : {
"mainContent" : "<p>java python javascript</p><p>oracle mysql sqlserver</p>"
}
}
]
}
}
  • 同段查询:java oracle
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
GET sample1/_search
{
"query": {
"span_not": {
"include": {
"span_near": {
"clauses": [
{
"span_term": {
"mainContent": {
"value": "java"
}
}
},
{
"span_term": {
"mainContent": {
"value": "oracle"
}
}
}
],
"slop": 12,
"in_order": false
}
},
"exclude": {
"span_term": {
"mainContent": {
"value": "paragraph"
}
}
}
}
}
}

#结果:没有文档返回
{
"took" : 0,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 0,
"relation" : "eq"
},
"max_score" : null,
"hits" : [ ]
}
}

纯文本格式

纯文本和HTML的区别是段落分割符不同,使用\n.

创建索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
PUT sample2
{
"settings": {
"number_of_replicas": 0,
"number_of_shards": 1,
"analysis": {
"analyzer": {
"maincontent_analyzer": {
"type": "custom",
"char_filter": [
"sentence_paragrah_mapping"
],
"tokenizer": "ik_max_word"
}
},
"char_filter": {
"sentence_paragrah_mapping": {
"type": "mapping",
"mappings": [
"""\n => \u0020sentence\u0020paragraph\u0020 """,
"""! => \u0020sentence\u0020 """,
"""? => \u0020sentence\u0020 """,
"""。 => \u0020sentence\u0020 """,
"""? => \u0020sentence\u0020 """,
"""! => \u0020sentence\u0020"""
]
}
}
}
},
"mappings": {
"properties": {
"mainContent": {
"type": "text",
"analyzer": "maincontent_analyzer",
"search_analyzer": "ik_smart"
}
}
}
}

测试分词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
POST sample2/_analyze
{
"text": ["java python javascript\noracle mysql sqlserver"],
"analyzer": "maincontent_analyzer"
}

# 结果
{
"tokens" : [
{
"token" : "java",
"start_offset" : 0,
"end_offset" : 4,
"type" : "ENGLISH",
"position" : 0
},
{
"token" : "python",
"start_offset" : 5,
"end_offset" : 11,
"type" : "ENGLISH",
"position" : 1
},
{
"token" : "javascript",
"start_offset" : 12,
"end_offset" : 22,
"type" : "ENGLISH",
"position" : 2
},
{
"token" : "sentence",
"start_offset" : 22,
"end_offset" : 22,
"type" : "ENGLISH",
"position" : 3
},
{
"token" : "paragraph",
"start_offset" : 22,
"end_offset" : 22,
"type" : "ENGLISH",
"position" : 4
},
{
"token" : "oracle",
"start_offset" : 23,
"end_offset" : 29,
"type" : "ENGLISH",
"position" : 5
},
{
"token" : "mysql",
"start_offset" : 30,
"end_offset" : 35,
"type" : "ENGLISH",
"position" : 6
},
{
"token" : "sqlserver",
"start_offset" : 36,
"end_offset" : 45,
"type" : "ENGLISH",
"position" : 7
}
]
}

升级nginx

发表于 2019-07-18

背景

当前部署的nginx服务器有安全问题,需要版本升级。

约束条件

  • 公司内网环境,不能使用外网
  • 操作系统CentOS 7

决定使用RPM文件方式进行更新。

步骤

下载RPM文件

从Nginx官方的YUM安装方式,可以找到RPM文件的地址:http://nginx.org/packages/centos/。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key


[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key

在目录/packages/centos/7/x86_64/RPMS/(7对应CentOS 7)下,可以下载到新版本的nginx RPM文件以及相应版本的Module安装文件。

停止nginx服务

1
sudo systemctl stop nginx

拷贝RPM文件到服务器

使用rz命令拷贝文件

卸载nginx Module

1
sudo yum remove nginx-mod*

使用下面的命令,可以查看已安装的module

1
sudo yum list |grep ngnix-mod

升级nginx

1
sudo rpm -Uvh [nginx 包路径]

安装新版本的nginx Module

1
sudo rpm -i [nginx moduel 包路径]

启动nginx服务

1
sudo systemctl start nginx

参考资料

  • https://qizhanming.com/blog/2018/08/06/how-to-install-nginx-on-centos-7
  • http://nginx.org/en/linux_packages.html#RHEL-CentOS
  • https://blog.csdn.net/jiangzeyin_/article/details/80397009

trycatchfinal

5 日志
5 标签
© 2020 trycatchfinal
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4