Keep and carry on.
post @ 2019-06-10

java.util.PriorityQueue源码学习笔记

1
transient Object[] queue; // non-private to simplify nested class access

解读源码的注释:

基于数组实现的,节点 n 的 子节点的 index 为:

如果没有指定排序规则,那么按照自然排序规则排序,通过数组的形式表达了平衡二叉树的结构。默认是小顶堆的实现。

1560149252015

图解 PriorityQueue 小顶堆的实现

这个类是非线程安全的1

主要看 addofferpeekremovepoll这几个方法。

add、offer

add 中直接调用offer,所以我们直接看offer。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public boolean offer(E e) {
if (e == null)
throw new NullPointerException();
modCount++;
int i = size;
if (i >= queue.length)
grow(i + 1);
size = i + 1;
if (i == 0)
queue[0] = e;
else
siftUp(i, e);
return true;
}
  1. 如果插入的元素是空,那么抛出空指针
  2. 判断是否需要扩容
  3. 如果是第一个参数直接入列。
  4. 否则执行方法 siftUp,入参 i 数组的长度,e 需要入队的数据
1
2
3
4
5
6
private void siftUp(int k, E x) {
if (comparator != null)
siftUpUsingComparator(k, x);
else
siftUpComparable(k, x);
}
  1. 我们以未指定排序规则为例进入方法 siftUpComparable
1
2
3
4
5
6
7
8
9
10
11
12
13
@SuppressWarnings("unchecked")
private void siftUpComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>) x;
while (k > 0) {
int parent = (k - 1) >>> 1;
Object e = queue[parent];
if (key.compareTo((E) e) >= 0)
break;
queue[k] = e;
k = parent;
}
queue[k] = key;
}

例:如果我们以此通过add加入 3、5、2,那么此方法的执行解析为

  1. 3 -> queue[3]
  2. 5 -> siftUpComparable(k,x),k = 1 x = 5
    • parent = (1 -1) / 2 = 0
    • e = 3
    • key > 3 break;queue[3,5]
  3. 2 -> siftUpComparable(k,x),k = 2 x = 2
    • parent = (2-1)/2 = 0
    • e = 3
    • key < 3
    • queue[2] = 3 ,k = 0;queue[3,5,3];
    • queue[0] = 2;queue[2,5,3]

至此完成添加 siftUpUsingComparator 同样的步骤。

peek

1
2
3
4
@SuppressWarnings("unchecked")
public E peek() {
return (size == 0) ? null : (E) queue[0];
}

poll

1
2
3
4
5
6
7
8
9
10
11
12
13
@SuppressWarnings("unchecked")
public E poll() {
if (size == 0)
return null;
int s = --size;
modCount++;
E result = (E) queue[0];
E x = (E) queue[s];
queue[s] = null;
if (s != 0)
siftDown(0, x);
return result;
}

返回 rote 数据,并移除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SuppressWarnings("unchecked")
private void siftDownComparable(int k, E x) {
Comparable<? super E> key = (Comparable<? super E>)x;
int half = size >>> 1; // loop while a non-leaf
while (k < half) {
int child = (k << 1) + 1; // assume left child is least
Object c = queue[child];
int right = child + 1;
if (right < size &&
((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
c = queue[child = right];
if (key.compareTo((E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = key;
}

又遇到了这种我思维跟不上的逻辑了。

查阅资料对于这种平衡二叉树,index 小于 half size 的都不是叶子节点。这里利用了这个特点。

例:queue[2,5,3]

  1. queue[2,3],half = 1,k = 0,x = 5
  2. 移除的 index 不是叶子节点,那么获取其左右子节点进行比较。
    默认取左节点为基准值,如果右节点存在且比作节点小,那么以右节点为基准值。
    如果入参 x 比基准值小,那么退出循环。
    否则将 k 处的值设置为基准值 c,k = 左右节点较小的index。继续循环。
    queue[3,3]
  3. 找到了 x 应该位置 k ,queue[3,5]。
1. use the thread-safe {@linkjava.util.concurrent.PriorityBlockingQueue} class
阅读此文
post @ 2019-05-29

准备

摩托:赛艇250

小空气瓶 4,补胎钉,胎压器,手机防水套(可充电),骑行服,补胎液,骑行鞋,防雨衣,机油5桶。

红牛,葡萄干等小零食。

路线

day one

成都(蓝润)-雅安 134.5公里 预计时间 2.5个小时

雅安-泸定(飞夺泸定桥纪念馆)101.8公里 预计时间 2个小时 加满油

泸定-理塘 322公里 这段有点长打算拆分成两段

理塘海拔 4014.187 ,如果骑到这里睡觉可能会不舒服,所以第一天不到理塘,终点新都桥,在新都桥留宿。估计到了新都桥还很早,所以路上可以多看几个附近的景点。

泸定-新都桥 126.1公里 预计时间 3.5小时。加满油

day two

新都桥-理塘 195.5公里 预计时间 4.5小时 加满油

理塘-巴塘县 168.8公里 预计时间 4小时 加满油

day three

巴塘-芒康 103.6公里 预计时间 4小时 加满油

芒康-左贡 159.1公里 预计时间 5.5小时 加满油

day four

左贡-八宿 198.6公里 预计时间 4.5小时 加满油(中途看情况油箱半以下找加油站加油)

八宿-波密县 217.1公里 预计时间 4.5小时 加满油(中途看情况油箱半以下找加油站加油)

day five

波密-林芝 227.6公里 预计时间 5小时 加满油(中途看情况油箱半以下找加油站加油)

林芝-工布江达县政府 128.7公里 预计时间 2小时 加满油

final

工布江达县政府 - 拉萨 274公里 预计时间 4小时

阅读此文
post @ 2019-05-01

Algorithm

Valid Parentheses

判断只由小中大括号( ‘(‘, ‘)’, ‘{‘, ‘}’, ‘[‘, ‘]’ ),是否符合以下条件:

  • 必须以相同类型的括号关闭。
  • 关闭的顺序必须和打开的一致。

空字符串认为是有效的。

经过前几次的学习,最开始不会想到暴力破解法了,想到了用栈来解决这个问题,FILO。

Details

Runtime: 3 ms, faster than 74.38% of Java online submissions for Valid Parentheses.

Memory Usage: 36.7 MB, less than 36.06% of Java online submissions for Valid

及格线上,代码结构依旧不是很规整。参考官方的解决方案。带来的思考:

我的答案中,提出了一个 filter的方法,其实我也注意到了,这个问题其实关键在于这个判断怎么去写,官网中用Map来制定规则,而且是以结尾的括号为key,开始的括号为value,很精髓。

Review

文章来源耗子叔、极客时间左耳听风专栏79讲推荐文章

另外,也推荐一下 JVM Anatomy Park JVM解剖公园,这是一个系列的文章,每篇文章都不长,但是都很精彩,带你一点一点地把 JVM 中的一些技术解开。

极客时间版权所有: https://time.geekbang.org/column/article/10216

Q:什么是 Large Pages? 什么是 Transparent Huge Pages? 对我们有什么帮助呢。

Theory

如今Virtual memory已被广为接受了,如今已经很少有人会去直接接触物理内存的操作,取而代之的是,没一个进程都有自己的虚拟内存,与实际的无力内存相映射。也就是两个进程的虚拟内存地址可能相同,但是映射的物理内存是唯一的。当进程访问这个虚拟地址时,某些处理机制会将虚拟地址转换为实际的物理地址。

操作系统维护page table,硬件通过page table walk来转换地址。以页的粒度进行地址转换是比较容易的。但是当每次内存访问都需要进行地址转换时,这就不是很划算了。因此更小的转换机制Translation Lookaside Buffer(TLB)应运而生。TLB 通常都很小,少于100条记录,因为它需要比 L1 cache 更快。TLB 以及页表移动的不命中,将带来昂贵的时间消耗。

虽然我们不能把 TLB 做大,但是我们可以把页变大,大多数的硬件支持 4K 大小的基本页,以及 2M/4M/1G 的大页。larger pages 覆盖内容相同的区域,减少page table walk以提高效率。

  • Hugetlbfs: 占用部分系统内存,将其暴露为虚拟文件系统,让应用程序从其中 mmap(2)。这种特有的接口需要应用程序和操作系统协同配置。这是一种“要么全部,要么是要么都没有”的方式:hugetlbfs不能被正常的进程使用。
  • Transparent Huge Pages (THP): 这种方式对于应用程序来说是透明的,使得应用程序可以像平常那样操作内存。理想情况下,应用程序不需要进行任何改动,我们将会看到应用程序从已知的THP中受益是可得的。在实践中,在一些特殊情况下的内存、时间开销也是不容忽视的,如:为一些小的内容分配整个 large pageTHP有时需要整理内存随便以分配页面。还在这有一个折中的方案:madvise(2) 方法让Linux系统知道应用程序在哪里使用THP

OpenJDK两种方式都支持:

原文作者执行后的示例:

1
2
3
4
5
$ java -XX:+PrintFlagsFinal 2>&1 | grep Huge
bool UseHugeTLBFS = false {product} {default}
bool UseTransparentHugePages = false {product} {default}
$ java -XX:+PrintFlagsFinal 2>&1 | grep LargePage
bool UseLargePages = false {pd product} {default}

我自己在我的mac上执行没有 UseTransparentHugePages这项,在Centos7上是有的。

mac 上的 Java 版本:

1
2
3
java version "1.8.0_171"
Java(TM) SE Runtime Environment (build 1.8.0_171-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.171-b11, mixed mode)

Centos 7 上的 Java 版本:

1
2
3
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)

-XX:+UseHugeTLBFS 映射 Java 堆到 hugetlbfs,应该单独编写。

-XX+UseTransparent仅 Java heap 通过 madvise -s使用 THP。我们知道 Java heap 一般都很大、大部分都是连续的。最有可能最大程度的从 large pages 中受益。

-XX:+UseLargepages是一个通用的配置,Linux 中,这个指令默认开启的是 hugetlbfs 而不是 THP,应该是因为 历史原因,毕竟 hugetlbfs 更早出现。

一些应用在启用 large pages 后性能下降。原文笔者的直觉是 THP 对于大多数短生命周期的应用,内存整理的时间相对于应用程序较短的生命周期相比更明显。

Experiment

同样我们还要使用JMH(JAVA MICROBENCHMARK HARNESS)来进行实验。有点本命工具的意思😄。

实验材料 :Aliyun ESC 下的 Centos 7 1核 2G 。

我们通过随机访问不同大小的 byte[] 数组,来观测结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class ByteArrayTouch {

@Param(...)
int size;

byte[] mem;

@Setup
public void setup() {
mem = new byte[size];
}

@Benchmark
public byte test() {
return mem[ThreadLocalRandom.current().nextInt(size)];
}
}

完整代码

我们知道随着数组的增大,系统性能开始受到L1、L2、L3缓存未命中的影响等等,通常我们忽略 TLB 不命中的影响。细说Cache-L1/L2/L3/TLB

测试前我们需要确定使用多大的堆内存,在我的机器上,L3 缓存有 8M(mac 中使用 sysctl hw查看,Linux 使用 lscpu), 100M 的数组足以满足条件,通过 -Xmx1G -Xms1G分配 1G 内存足够了(我自己设置的256m)。这也为我们分配 hugetlbfs 提供了指导。

同时,确保如下参数已经设置:

1
2
3
4
5
6
# HugeTLBFS should allocate 1000*2M pages:
sudo sysctl -w vm.nr_hugepages=1000

# THP to "madvise" only (some distros have an opinion about defaults):
echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/enabled
echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/defrag

通过 madvise 使用 THP,因为它可以让我们选择性内存可以受益的特定部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# BaseLine
Benchmark (size) Mode Cnt Score Error Units
ByteArrayTouch.test 1000 avgt 15 11.866 ± 1.455 ns/op
ByteArrayTouch.test 10000 avgt 15 11.574 ± 1.546 ns/op
ByteArrayTouch.test 1000000 avgt 15 15.395 ± 2.052 ns/op
ByteArrayTouch.test 10000000 avgt 15 63.419 ± 8.201 ns/op
ByteArrayTouch.test 100000000 avgt 15 81.424 ± 9.635 ns/op
# -XX:+UseTransparentHugePages
Benchmark (size) Mode Cnt Score Error Units
ByteArrayTouch.test 1000 avgt 15 11.275 ± 1.332 ns/op
ByteArrayTouch.test 10000 avgt 15 11.463 ± 1.563 ns/op
ByteArrayTouch.test 1000000 avgt 15 15.767 ± 1.927 ns/op
ByteArrayTouch.test 10000000 avgt 15 63.799 ± 7.880 ns/op
ByteArrayTouch.test 100000000 avgt 15 81.149 ± 12.851 ns/op
# -XX:+UseHugeTLBFS
Benchmark (size) Mode Cnt Score Error Units
ByteArrayTouch.test 1000 avgt 15 11.584 ± 1.343 ns/op
ByteArrayTouch.test 10000 avgt 15 11.685 ± 1.267 ns/op
ByteArrayTouch.test 1000000 avgt 15 15.260 ± 1.831 ns/op
ByteArrayTouch.test 10000000 avgt 15 58.797 ± 7.765 ns/op
ByteArrayTouch.test 100000000 avgt 15 80.017 ± 10.773 ns/op

综上一些总结:

  1. 在数组较小的情况下,缓存和 TLB 都表现良好,同默认设置下跑出来的结果基本无异。
  2. 随着数组的增大,缓存不命中成为影响性能的主要因素,三种场景的耗时都有所增加。
  3. 随着数组的增大,TLB 未命中也有了影响,通过设置大页有所提升。
  4. UseTHPUseHTLBS 带来的提升基本一致,因为它们对于应用程序来说,提供的功能是一样的。

为了验证 TLB 不命中的假设,我们可以通过硬件计数器来观察,JMH 的 -prof perfnorm可以使执行标准化。

这个实验也没跑通,本来想纠结一下了,综合考虑暂时pass,留给以后的自己

直接引用原文的实验数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
Benchmark                                (size)  Mode  Cnt    Score    Error  Units

# Baseline
ByteArrayTouch.test 100000000 avgt 15 33.575 ± 2.161 ns/op
ByteArrayTouch.test:cycles 100000000 avgt 3 123.207 ± 73.725 #/op
ByteArrayTouch.test:dTLB-load-misses 100000000 avgt 3 1.017 ± 0.244 #/op // !!!
ByteArrayTouch.test:dTLB-loads 100000000 avgt 3 17.388 ± 1.195 #/op

# -XX:+UseTransparentHugePages
ByteArrayTouch.test 100000000 avgt 15 28.730 ± 0.124 ns/op
ByteArrayTouch.test:cycles 100000000 avgt 3 105.249 ± 6.232 #/op
ByteArrayTouch.test:dTLB-load-misses 100000000 avgt 3 ≈ 10⁻³ #/op
ByteArrayTouch.test:dTLB-loads 100000000 avgt 3 17.488 ± 1.278 #/op

结合数据,可以看出,基准测试中,每次都会操作 dTLB-load-misses ,但是启动了 THP 之后却很少。

开启 THP 碎片整理,分配和访问时,碎片整理会带来而外的成本。将这些成本转移到 JVM 启动,将避免在应用运行时出现意外的延迟问题。你可以通过设置 -XX:AlwaysPreTouch 让 JVM 在启动时访问 java heap 中的每个单独的 page。不管怎么样,预访问对于 larger heaps 来说是一种好的策略。

-XX:+UseTransparentHugePages 事实上让 -XX:+AlwaysPreTouch更快,因为 JVM 知道以什么规模去访问堆。进程关闭后释放内存也会更快,这种优势一直延续到并行释放补丁发行版内核。

使用 4TB 的堆:

1
2
3
4
5
6
7
8
9
$ time java -Xms4T -Xmx4T -XX:-UseTransparentHugePages -XX:+AlwaysPreTouch
real 13m58.167s
user 43m37.519s
sys 1011m25.740s

$ time java -Xms4T -Xmx4T -XX:+UseTransparentHugePages -XX:+AlwaysPreTouch
real 2m14.758s
user 1m56.488s
sys 73m59.046s

提交和释放 4TB 的内存确实需要点时间。

Observations

Large pages 是一个提高我们的应用程序性能的小技巧。Linux 内核中透明大页很容易使用,JVM 配置透明页也很简单。所以,当你的应用有很大的数据量以及堆内存时,启用 large pages 是一个不错的选择。

Tips

1
2
3
4
5
# 创建用户可以使用ssh登录,但只有只读权限可以浏览下载部分文件无法写和修改
useradd -d /home/be-web -m be-web
passwd be-web
chown -R be-web:be-web /mnt/logs
chmod 760 /mnt/logs

Share

what

ElasticSearch 是一个基于 Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。 ElasticSearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索、稳定、可靠、快速、安装使用方便。

学习资源,感谢《龙果学院》老师的分享。以下也是我的随堂笔记。

龙果学院 《Elasticsearch 顶尖高手系列 - 快速入门篇》

Elasticsearch顶尖高手系列-快速入门篇

大白话介绍Elasticsearch

什么是搜索?

垂直搜索(站内搜索)

  • 互联网的搜索:电商网站,招聘网站,新闻网站,app等。
  • IT系统的搜索:OA软件

搜索,就是在任何场景下,根据你想要得到的信息,根据关键字找到的相关信息。

如果用数据库搜索会怎么样?

慢、无法更精确的匹配、无法分词。

什么是全文检索,什么是Lucene

  • 全文检索:将所有结构化和非结构化的数据提取信息,创建索引,根据用户的查询请求,搜索创建的索引,返回结果。
  • 倒排索引:根据词的关键字找文档
  • Lucene,就是一个jar包,里面包含了封装好的各种建立倒排索引,以及进行搜索的代码,包括各种算法。我们使用 java 开发的时候,引入lucene jar,然后基于 lucene 的 api 进行开发就可以了。用 lucene,我们接可以去将已有的数据建立索引,lucene会在本地磁盘上面,给我们组织索引的数据结构。另外的话,我们也可以用lucene提供的一些功能和 api 来针对磁盘上的索引数据进行搜索。

什么是Elasticsearch?

What

Elasticsearch的功能、使用场景以及特点

Elasticsearch的功能

  1. 分布式的搜索引擎和数据分析引擎

  2. 全文搜索,结构化检索,数据分析

    • 结构化搜索:分类搜索 : sql where =
    • 全文搜索:内容搜索 :sql like
    • 数据分析:某分类下有多少个数 :sql group by
  3. 对海量数据进行近实时的处理

    分布式: ES自动可以将海量数据分散到多台服务器上去存储和检索

    海量数据的处理;分布式以后,就可以采用大量的服务器去吃存储和检索数据,自然而然就可以实现海量数 据的处理了。跟分布式/海量数据相反的,lucene,单机应用,只能在单台服务器上使用,最多只能处理单台服 务器的数据。

    近实时:在秒级别对数据进行搜索和分析

    Batch-processing: 花费n小时,离线批处理

Elasticsearch的使用场景

  • 维基百科
  • The Guardian (国外新闻网站)
  • Stack Overflow
  • GitHub 搜索上千亿行代码
  • 电商网站,检索商品
  • 日志数据分析,logstash采集日志,ES进行复杂的数据分析(ELK技术,elasticsearch+logstash+kibana)
  • 商品价格监控网站,用户设定某商品的价格阈值
  • BI系统,商业智能,Business Intelligence,分析某区域近三年的用户消费金额趋势以及用户群体的组成构成,产出相关的报表。ES执行数据分析和挖掘,Kibana进行数据可视化
  • 国内:站内搜索(电商、招聘、门户等等)、IT系统搜索(OA、CRM、ERP等等),数据分析

Elasticsearch的特点

  • 可以作为一个大型分布式集群(数百台服务器)技术,处理PB级数据,服务大公司;也可以运行在单机上,服务小公司
  • Elasticsearch 不是新技术,主要是将全文检索、数据分析以及分布式技术,合并在了一起,才形成了独一无二的ES;lucene (全文检索),商用的数据分析软件(也是有的),分布式数据库(mycat)
  • 对用户而言,是开箱即用的,非常简单,作为中小型的应用,直接3分钟部署ES,就可以作为生产环境的系统来使用了,数据量不大,操作不是太复杂
  • 数据库的功能面对很多领域是不够用的 (事务,还有各种联机事务性的操作)。比如全文检索;同义词处理,相关度排名;复杂数据分析,海量数据的近实时处理;Elasticsearch作为传统数据库的一个补充,提供了数据库所不能提供的很多功能

手工画图剖析Elasticsearch核心概念:NRT、索引、分片、副本等

lucene和elasticsearch的前世今生

lucene,最先进、功能最强大的搜索库,直接基于lucene开发,非常复杂,api复杂(实现一些简单的功能,写大量java代码),需要深入理解原理 (各种索引结构)

Elasticsearch, 基于lucene,隐藏复杂性,提供简单易用的restful、api接口、java api接口(还有其他语言的api接口)

  • 分布式文档存储引擎
  • 分布式搜索引擎和分析引擎
  • 分布式;支持PD级数据

开箱即用,优秀的默认参数,不需要任何额外设置,完全开源

失业程序员-陪老婆英国伦敦学习-写一个菜谱搜索引擎-compass-elasticsearch

elasticsearch的核心概念

  • Near Realtime (NRT),近实时,两个意思,从写入数据到数据可以被搜索到有一个小延迟(大概1秒);基于es执行搜索和分析可以达到秒级
  • Cluster:集群,包含多个节点,每个节点属于哪个集群是通过一个配置(集群名称,默认是elasticsearch)来决定的,对于中小型应用来说,刚开始一个集群就一个节点很正常
  • Node:节点,集群中的一个节点,节点也有一个名称(默认是随机分配的),节点名称很重要(在执行运维管理操作的时候),默认节点会去加入一个名称为“elasticserach”的集群,如果直接启动一堆节点,那么它们会自动组成一个elasticserach集群,当然一个节点也可以组成一个elasticsearch集群
  • Document:文档,es中的最小数据单元,一个document可以是一条客户数据,一条商品分类数据,一条订单数据,通常用JSON数据结构表示,每个index下的type中,都可以存储多个document。
  • Indexd:索引,包含一堆有相似结构的文档数据,比如可以有一个客户索引,商品分类索引,订单索引,索引有一个名称,一个index包含很多document,一个index就代表了一类类似的或者相同的document,比如说建立一个product index,商品索引,里面可能就存放了所有的商品数据,所有的商品document。
  • Type:类型,每个索引里面都可以有一个活多个type,type是idnex中的一个逻辑数据分类,一个type下的document,都有相同的field,比如博客系统,有一个索引,可以定义用户数据type,博客数据type,评论数据type。一个index可以放很多个type。
  • shard:单台机器无法存储大量数据,es可以将一个索引中的数据切分成多个shard,分布在多给服务器上存储。有了shard就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能,每个shard都是一个lucene index。
  • replica:任何一个服务器随时可能故障或宕机,此时shrad可能就会丢失,因此可以为每个shard创建多个replica副本。replica可以在shrad故障时提供备用服务,保证数据不丢失,多个replica还可以提升搜索操作的吞吐量和性能,primary shard(建立索引时一次设置,不能修改,默认5个),replica shard(随时修改数量,默认1个),默认每个索引10个shrad,5个primary shard。5个replica shard,最小的高可用配置,是两台服务器。

shard

Elasticsearch 核心概念 vs. 数据库核心概念

Elasticsearch 数据库
Document
Type
Index 数据库

使用Docker创建Elasticsearch+kibana服务

docker pull elasticsearch

docker run -d -p 9200:9200 -e ES_JAVA_OPTS="-Xms125m -Xmx125m" --name es_test docker.io/elasticsearch

ip:post访问即可。开箱即用。

docker pull kibana

docker run --name leon_kibana -p 5601:5601 -d -e ELASTICSEARCH_URL=http://ip:port kibana

我使用的是vm+centos 7 的虚拟环境

需要防火墙开启9200端口

firewall-cmd --zone=public --add-port=80/tcp --permanent

firewall-cmd --reload

指令操作参考

快速入门,文档CRUD

document的数据格式

面向文档的搜索分析引擎

  1. 应用系统的数据结构都是面向对象的,复杂的
  2. 对象数据存储到数据库中,只能拆解开来,变为扁平的多张表,每次查询的时候还得还原回对象格式,相当麻烦
  3. ES是面向文档的,文档中存储的数据结构,与面向对象的数据结构是一样的,基于这种文档数据结构,es可以提供复杂的索引,全文检索,分析聚合等功能。
  4. es的document用 josn数据格式来表达。

实验:电商网站商品管理案例

有一个电商网站,需要为其基于ES构建一个后台系统,提供一下功能:

  1. 对商品信息进行CRUD操作
  2. 执行简单的结构化查询
  3. 可以执行简单的全文检索,以及负载的phrase(短语)检索
  4. 对于全文检索的结果,可以进行高亮显示
  5. 对数据进行简单的聚合分析

简单的集群管理

  1. 快速检查集群的健康状况

    GET /_cat/health?v

    如何快速了解集群的健康状况?green、yellow、red

    green:每个索引的primary shard 和 reploca shard 都是active状态的

    yellow:每个索引的primary shard 都是active状态的,但是部分replica shard 不是active状态,处于不可用的状态

    red:不是所有索引的primary shard都是active状态,部分索引有数据丢失了

    为什么现在会处于一个yellow状态?

    我们单机启动了一个es进程,相当于只有一个node,现在es中有一个index,就是kibana自己内置建立的index。由于默认的配置是给每个index分配5个primary shrad 和5个 replica shrad,而且primary shard 和 replica shard 不能在同一个机器上(为了容错)。现在kibana自己建立的index是1个primary shard和1个replica shard。当前就一个node,所有只有1个primary shard被分配和启动,但是一个replica shard没有第二台机器去启动。

    进行一个小实验 ,此时只要启动第二个es进程,就会在es集群中有2个node,然后那1个replica shrad就会自动分配过去,然后cluster status 就会变成green状态。

    centos 7+dcoker 未顺利完成实验,TODO

    2019-06-05 参考官方文档done

  2. 快速查看集群有哪些索引?

    GET _cat/indicess?v

  3. 简单的索引操作

    • 创建索引:PUT /test_index?pretty
    • 删除索引:DELETE /test_index?pretty

商品的CRUD操作

  • 新增商品:新增文档,建立索引

    PUT /index/type/id

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    PUT /ecommerce/product/1 
    {
    "name": "gaoluejie yagao",
    "desc": "gaoxiao meibai",
    "price": 30,
    "producer": "gaolujie producer",
    "tags": ["meibai", "fangzhu"]
    }
    PUT /ecommerce/product/2
    {
    "name": "jiajieshi yagao",
    "desc": "youxiao fangzhu",
    "price": 25,
    "producer": "jiajieshi producer",
    "tags": ["fangzhu"]
    }
    PUT /ecommerce/product/3
    {
    "name": "zhonghua yagao",
    "desc": "caoben zhiwu",
    "price": 40,
    "producer": "zhonghua producer",
    "tags": ["qingxin"]
    }

    es会自动建立index和type,不需要提前创建,而且es默认会对document每个field都建立倒排索引,让其可以被搜索。

  • 查询商品:检索文档

    GET /index/type/id

    GET /ecommerce/product/1

  • 修改商品:替换文档

    1
    2
    3
    4
    5
    6
    7
    8
    PUT /ecommerce/product/1
    {
    "name": "gaoluejie yagao",
    "desc": "gaoxiao meibai",
    "price": 32,
    "producer": "gaolujie producer",
    "tags": ["meibai", "fangzhu"]
    }

    替换方式有一个坏处,必须带上所有的field才能进行信息修改。

  • 修改商品:更新文档

    1
    2
    3
    4
    5
    6
    POST /ecommer/product/1/_update
    {
    "doc":{
    "name": "jiaqiangban gaolujie yagao"
    }
    }
  • 删除商品:删除文档

    DELETE /ecommerce/product/1?pretty

阅读此文
post @ 2019-04-16

记一次Spring Boot 跨域支持问题

项目版本:Spring boot 2.1.1.RELEASE

问题描述:配置的跨域支持失效。

配置方法:Application实现WebMvcConfigurer接口然后重写addCorsMappings方法。

其他配置:同时还是实现了addInterceptorsaddArgumentResolvers这两个方法。

确定问题实验步骤

  • 取消其他配置,发现跨域生效,初步猜测问题发生在,另外两个配置实现中。
  • 取消addArgumentResolvers,放开addInterceptors,失效。
  • 取消addInterceptors,放开addArgumentResolvers,有效。

结合上述问题,初步断定问题出现在自定义的HandlerInterceptor中。

自定义的拦截器里面,从header中获取 token 然后进行登录用户的判断。如果验证失败则抛出异常,在ControllerAdvice中进行统一的异常处理。

  • 将其他逻辑注释,直接return true,发现配置生效。缩小问题范围。如果不返回true, Spring 会做什么呢。这时自然想到DispatcherServlet

debug 跟进源码中,发现在 line 1033时,如果有一个拦截器返回false就直接结束了逻辑,如果抛出异常,后面的拦截器的逻辑也不执行了。

但是现在并不能说明跨域不生效的问题。这时找到CorsRegistry查看这个类到底被 Spring 施展了什么魔法。

CorsRegistry中有一个getCorsConfigurations,然后定位到WebMvcConfigurationSupport中调用了这个方法。分别在一下方法中进行了设置。

  • 1
    2
    3
    4
    requestMappingHandlerMapping
    viewControllerHandlerMapping
    beanNameHandlerMapping
    resourceHandlerMapping

    结合调用接口时,DispatcherServletdoDispatch方法中的mappedHandler.interceptorList,有如下四个 Interceptor。

    • 自定义拦截器
    • ConversionServiceExposingInterceptor
    • ResourceUrlProviderExposingInterceptor
    • AbstractHandlerMapping$CorsInterceptor

    至此,断案。自定义的拦截器会第一个执行preHandle,抛出异常或者 return false后,后面的拦截器都没有执行,所以我们设置的跨域支持的相关配置也没有生效。

解决方法

  • 使用Filter
  • 使用 InterceptorRegistrationorder方法将自定义的拦截器配置到AbstractHandlerMapping$CorsInterceptor后面,第四个位置order(4),有效。

如果使用了nginx做反向代理服务器。

1
2
proxy_pass  http://127.0.0.1:8080;
proxy_set_header Host $host;

相当于进行一次转发。

二者方法各有千秋,技术上很多事情都是取舍,因为我们没有过 nginx进行跨域支持的配置,而且通过业务配置也能解决问题,这里不进行延展的探讨。两种方式我都可以接受。

阅读此文
post @ 2019-04-13

Algorithm

Add Two Numbers

给定两个非空正整数链表,除了0外,链表不以0结尾,数字被反向存储在链表中,每个节点的两个数相加,产生新的链表并返回。

Example:

Input: $(2->4->3)+(5->6->4)$

Output:$7->0->8$

Explanation:$342+465=807$.

最直接的思路,就是递归的去加。但是当我写代码的时候,发现写不出来😂。只能硬着头皮,先一位一位的加,然后找规律,提取递归方法,然后不停的提交测试,最终是通过了。感觉丑陋无比,回头看也完全看不懂的代码跑出来的结果还是让我有些惊喜的。my solution

Detail

Runtime: 2 ms, faster than 97.70% of Java online submissions for Add Two Numbers.

Memory Usage: 46 MB, less than 55.21% of Java online submissions for Add Two Numbers.

我实现的方法时间复杂度就是$O(n)$,$n$就是两个链表的最大长度。空间复杂度应该也是一样的,应该是因为我的代码时间过多的中间变量的创建导致的内存占用较大。

官网解题思路

official solution 解读

参考官方的解题思路获得的启发,问题过来千万别着急想着解决它,先分析问题中的所有条件,以及可能涉及的信息,提炼解题模型,然后在写代码。代码还是要多看,多写,多感受,然后总结自己的套路。

Review

文章来源耗子叔、极客时间左耳听风专栏79讲推荐文章

另外,也推荐一下 [JVM Anatomy Park JVM][]解剖公园,这是一个系列的文章,每篇文章都不长,但是都很精彩,带你一点一点地把 JVM 中的一些技术解开。

极客时间版权所有: https://time.geekbang.org/column/article/10216

Q: Java 是否对循环中的锁也进行了锁粗化优化呢 (lock coarsening optimizations)

我们知道:

1
2
3
4
5
6
synchronized (obj) {
// statements 1
}
synchronized (obj) {
// statements 2
}

Convert into :

1
2
3
4
synchronized (obj) {
// statements 1
// statements 2
}

那么:

1
2
3
4
5
for (...) {
synchronized (obj) {
// something
}
}

是否会被优化为:

1
2
3
4
5
synchronized (this) {
for (...) {
// something
}
}

我们可以使用JMH(Java Microbenchmark Harness),进行验证。

官方推荐通过Maven构建测试项目。然后运行的时候不要Debug模式运行,先mvn clean installrun
读到这里,下面的内容对于我来说就有些难度了,不过我也不陷入技术的无限深入了,这里我明确两个目标:学习常见技术英文单词,找到问题的答案即可。

我自己的实验结果:

Running at i7,16GB CPU,macOS 10.14.4,JDK 1.8.0_171

1
2
Benchmark                     Mode  Samples     Score  Score error  Units
o.s.MyBenchmark.testMethod avgt 5 5423.944 59.737 ns/op

仅仅从这串数字中,我们无法获得答案,我们需要内部发生了什么。

cmpxchg compare-and-sets又见到它了,在小马哥的视频中我也听过一次。

既然有缘稍微查询下资料,了解一下。这是一个cpu提供的原子性操作指令。Java 中很多实现都是基于这个指令。

进行-prof perfasm:mergeMargin=1000 的操作时,发现自建的项目中,无法设置了,这时回头参考了文中连接的 JMH 官方地址。继续进行实验。这一步要将热点代码附近的操作打印出来。

这个实验没跑出来,继续,留给以后的自己

实验结论就是循环被拆成了4个小循环。接下来验证是不是因为循环的展开带来的性能提升,通过设置-XX:LoopUnrollLimit=1排除干扰。

我的实验结果:

1
2
Benchmark               Mode  Cnt      Score     Error  Units
weekTwo.LockRoach.test avgt 5 21607.139 ± 288.376 ns/op

确实性能上有4倍的差距。再次通过-prof perfasm:mergeMargin=1000 查看返回内容是否知识一个循环。

实验过后,得证。

结论

HotSpot 不会对整个循环进行锁粗化,通过进行循环展开的方式,为常态的锁粗化奠定基础。循环展开后就是多个连续的加锁-解锁序列,如此获得了性能上的收益,并且避免了过度粗化。

Tips

在 mac 上使用 VM function 部署 Centos 7 时,希望固定 ip,在网上搜了好久,没有找到解决办法,最后在进行 Nginx+Keepalived 的高可用实验的时候,发现可以通过 Keepalived 达到固定 ip 的效果。并且网上很多配置检测 Nginx 是否存在的脚本,我这都不生效,最终找到了一个这里分享一份我的配置。

keepalived.conf ,nginx-check.sh

Share

在进行 ARTS 任务过程中,翻阅了耗子叔的博客,他的座右铭让我深有感触。

芝兰生于深谷,不以无人而不芳
君子修身养德,不以穷困而改志

可能是工作的压力,技术的恐慌,学习的迷茫。我看到这句话的时候,真的哭了,似乎真的忘记了来时的自己,毕业3年多了,为了生活,为了工作,身边的同时换了一批又一批,多少次仅仅因为钱的问题迷失自我,多少次因为虚荣放纵了自我。人人都想成为君子,但是真的很难。

回想自己进入这个行业是为了什么呢,好像也没有想为了什么,仅仅是为了毕业后能有一份工作。3年应该是一个节点吧,虽然现在承担着技术负责人的工作,但是个人觉得技术的成长才是我前10年的重点,不想分心太多去处理管理、沟通、协调资源等问题上。

如果你很有责任心,那么最后什么事都会落在你的头上,当然这也会带给你技术上的飞速成长。

看着耗子叔的座右铭,想想现在的自己:

非芝兰,蒹葭一束,慕芝兰之芳。然随风浮荡,望江波荡漾,思绪万千。
受诱于富贵享乐,奋进之心不坚。念他人之才,苦寒之气不受。
古人之语,惊醒梦中之人,君当以“千磨万击还坚韧,任尔东南西北风”。
阅读此文
post @ 2019-04-05

人人都知道学习的重要性,但是能坚持下来的人寥寥无几,加油。

Algorithm

之前有刷过一阵,但是因为自我放纵,停了快一个月了。这里把之前的刷题记录整理一下。

Reverse Linked ListTwo Sum

作为开始,回顾一下之前的题,下周正式进入状态。🍺。

个人觉得算法相关的问题主要是思维、还有一些套路,每次回头看都会有新的收获,坚持下去就是胜利。

Review

文章来源耗子叔、极客时间左耳听风专栏79讲推荐文章

introduction-to-java-bytecode这篇文章图文并茂地向你讲述了 Java 字节码的一些细节,是一篇很不错的入门文章。

极客时间版权所有: https://time.geekbang.org/column/article/10216

My Way

为什么要了解ta呢?

作者以一次性能优化检测为例子,检测的代码没有通过检入版本控制系统 (checked into a version control)。上线后,本地的改动代码被删除了。过了几个月,想要修改这段代码,但是找不到了(代码是你的财富,保管好ta们)。只能通过反编译获取代码了,但是反编译软件并不是很完美有很多bug。作者基于自己对于字节码文件的了解,选择了直接修改字节码文件来完成任务。

因为JVM的存在,我们学习字节码可以适用于所有Java支持的平台,因为ta的代码的中间表现(an intermediate representationof the code),并不是底层CPU的实际可执行代码。并且Oracle 记录了所有的指令

有一个句子我翻译成了**破釜沉舟**(Desperate times call for desperate measures),不知道合不合适。// TODO 确认

JVM Data Types

祭上一张整理的图片

Java-Data-Types

这还有一个整理的不错的文章JVM数据类型

Stack-Based Architecture

图片来自芋大的知识星球JDK7-JVM内存模型

JDK8中用Metaspace完全替代了 Method Area

本文的作者应该是以JDK7之前的HotSpot为上下文介绍的Runtime Data Area。因为文中方法区的图片, string constants pool还在里面,在《深入理解Java虚拟机》中2.2.5 方法区有这样一句描述:

在目前已经发布的 JDK1.7 的 HotSpot 中,已经把原本放在永久带的字符串常量池移出。

关于方法区,此处让我无限懵逼。

这里通过搜寻各种资料,我要强行给自己定义一波。

方法区是JVM的一种规范,不同版本的虚拟机可以有自己的实现,HotSpot在JDK8以后,将累的元数据放到了本地堆内存(native heap),这块区域叫做metaSpace,它就是方法区的实现。Java8中使用了元空间替代了永久代。$方法区->永久代->Metasapce$不要纠结名字了,都指一个地方。以后就Metaspace了。

元空间使用本地内存,java.lang.OutOfMemoryError: PermGen space这个异常将不复存在取而代之的是java.lang.OutOfMemoryError: Metaspace

不进行扩展了。时刻提醒自己不要被技术诱惑。这会让你陷入深渊,最后从入门到放弃。

这块每次看都有新感受,作为菜鸟的我,每次也是晕晕乎乎的看了一遍又一遍,然后又没有任何印象,我发现文字的记录并不直观,还是需要自己按照程序的执行去走一遍。

java-memory-model

longdouble占用两个本地变量的位置。操作数栈用来记录方法中还行过程中的中间值,或者将参数推送给方法已备调用。

Bytecode Explored

正在执行方法的stack frame中,指令可以将值推送或者弹出operand stack,并将值存储到Local Varialbe Table指定位置中。

使用javap对字节码文件进行解读

1
javap -v xxxx.class

执行的解析:

invoke-sequence

Method Invocations

承接上例,该例中,将计算方法单独抽取,观察方法的调用在字节码文件中的不同表现。

不同于上例的点:

  • $iadd -> invokestatic$
  • 占据3个字节,$ 6->9$,包含了两个额外的字节来构造对调用的方法的引用,
  • 它是calc方法的符号引用,从常量池中解析出来的

此处依然一脸懵逼。翻译出来的话让我升天了。后两点需要特别调查一下。

#2对应常量池中的编号,使用javap -verbose xxxx来查看常量池。缓解了我对第三点的懵逼。

第二点的疑问,为什么要用两个额外的字节?//TODO确认 暂时以规范来理解。留给以后的自己来解决。

第二个例子的执行解析就不画图了,文字解析:

invokestaticstack中创建一个新的帧,然后顺序执行Code下的指令。

iload_0将局部变量表中的第一个int推送到栈顶。

i2d 栈顶的int转化为double

ldc2_wlongdouble型常量值从常量池中推送至栈顶(宽索引)。

invokestatic执行静态方法,返回的值将放到调用者的栈顶。

………,后面的就不解释了,这些我们不必记忆,只需了解即可,真用的时候,能找到它们的翻译字典就行了。

Instance Creations

如题此例我们探寻,类的创建在字节码文件中的表现。

newdupinvokespecial

创建过程解析:

new创建了一个Point,dup复制了一个Pointreference,现在我们有两个Point的引用了。然后将1、2压入栈顶,执行invokespecial,初始化类,对象在heap中分配内存,初始化结束后,操作数栈中的前三位弹出,只剩下最开始new创建的引用了,但是这时Point已经初始化完成了。

invokevirtual根据对象的实际类型进行分配。如果本例中Point有一个子类SpecialPoint,并且重写了方法area,那么就会调用子类中重写的方法。

The Other Way Around

通过字节码指令确定问题,我们不必了解他们的全部调用流程,只需要关注我们的问题点就可以了。

Conclusion

由于字节码指定集的简单性以及在生成指令时几乎没有进行编译器优化,有时候,反汇编类文件来检查代码是一种更好的方法。

耐心很重要,读下来肯定会有收获,可以用翻译,但是一定要🤔。

Tips

最近一周确实工作中没有什么可以算作技术技巧的东西,这里就整理一下之前的一些笔记吧。

Git配置多个SSH-Key

场景有多个git账号时,如公司使用github,自己使用alicode,这是需要配置两个SSH-key。

那么通过在~/.ssh目录下新建config文件来解决。

1
2
3
4
5
6
7
# 配置文件参数
# Host : Host可以看作是一个你要识别的模式,对识别的模式,进行配置对应的的主机名和ssh文件(可以直接填写ip地址)
# HostName : 要登录主机的主机名(建议与Host一致)
# User : 登录名(如gitlab的username)
# IdentityFile : 指明上面User对应的identityFile路径
# Port: 端口号(如果不是默认22号端口则需要指定)
# PreferredAuthentications publickey (固定)

Share

As the beginning,Why I’am here and do the boring job.It’s for life? for my confusion?

Do I like this jop?Sometimes.榨干了我的英文词汇量了,😂。

ARTS对于share的定义:

1
Share:主要是为了建立你的影响力,能够输出价值观。分享一篇有观点和思考的技术文章。

这个其实挺难的,构思文章、落笔、有价值、思考。确实没有什么准备,第一周围绕着这句话展开自己的一些想法谈谈吧,也为以后的Share找准方向。

  1. 输出价值观

每个人的环境大不相同,但是我们会有很多共同的问题,比如: “程序员的中年危机”、“技术是否要转管理”、“新技术的引用”(需要自己和同事的加班,然后公司并不认可)、“跳槽”、“面试准备”(自己准备面试,面试他人)、“选择的艺术-权衡”、“程序员的基本素质”、“工作、学习计划”、哲学、理想、星星月亮等等。

  1. 技术文章

“踩坑记录”、“技术学习”、“技术管理”、“技术选型”等等。

不刻意要求篇幅,但是要求有清晰的提纲,最好有实践有验证过程。一定会存在错误的观点,留给以后的自己或者看到的人来帮忙review,不要陷入无用的争论。

阅读此文

leetcode刷题笔记-Reverse Linked List

自己的想法

  1. 遍历Linked List,通过list的add.(index,val)方法,将数据放入list。
  2. 然后,遍历list封装Linked。
    bingo代码实现

    运行结果

    1
    Runtime: 3 ms, faster than 2.86% of Java online submissions for Reverse Linked List.

结果分析: 弱爆了

参考官方解决方法

迭代法[Accepted]

  1. 蹩脚的翻译思路解析

    已有链表 1->2->3->null
    当你遍历里列表时,将当前节点的下一个指针,指向前一个元素。

    理解:比如当前节点是11的下一个指针指向的是2,前一个元素是null,经过这个操作后,当前节点就变成了1->null

    因为节点没有指向前一个节点的引用,所以需要预先准备一个来存储它。
    在更改引用之前,你还需要另一个指针来存储下一个节点。最后返回新的引用
    示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public ListNode reverseList(ListNode head) {
    ListNode prev = null; // step 1
    ListNode curr = head; // step 2
    while (curr != null) {// step 3
    ListNode nextTemp = curr.next;// step 4
    curr.next = prev; // step 5
    prev = curr;// step 6
    curr = nextTemp; //step 7
    }
    return prev;
    }

    代码执行解析:

    已知条件 head = 1->2->3->null

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    * while第一次执行:
    * head = 1,2,3
    * step 1 prev = null
    * step 2 prev = null; curr = 1,2,3
    * step 3 prev = null; curr = 1,2,3
    * step 4 prev = null; curr = 1,2,3; nextTemp = 2,3
    * step 5 prev = null; curr = 1; nextTemp = 2,3
    * step 6 prev = 1; curr = 1; nextTemp = 2,3
    * step 7 prev = 1; curr = 2,3;
    * while第二次执行:
    * step 4 prev = 1; curr = 2,3;nextTemp =3
    * step 5 prev = 1; curr = 2,1;nextTemp =3
    * step 6 prev = 2,1;curr = 2,1;nextTemp =3
    * step 7 prev = 2,1;curr = 3;
    * while第三次执行:
    * step 4 prev = 2,1;curr = 3;nextTemp =null
    * step 5 prev = 2,1;curr = 3,2,1;nextTemp=null
    * step 6 prev = 3,2,1;curr=3,2,1;nextTemp=null;
    * step 7 prev = 3,2,1;curr=null
    * 跳出循环
    * return prev

递归[Accepted]

  1. 蹩脚的翻译思路解析

    递归的版本稍微有些复杂,解题的关键是反向。假设剩余的列表已经被反转,前面的部分如何解决呢?假设list是:

    假设节点nk+1 到 n m 已经被反转了,当前循环处在nk节点。

    我们想要nk+1‘s 的下一个节点是nk

    所以nk.next.next = nk

    对于第一个节点,要注意的是它的下一个节点指向Ø。如果忘记这个,那么你会得倒一个循环链表,如果使用长度2的链表集合测试代码,那么复现这个bug。

    1
    2
    3
    4
    5
    6
    7
    public ListNode reverseList(ListNode head) {
    if (head == null || head.next == null) return head;
    ListNode p = reverseList(head.next);
    head.next.next = head;
    head.next = null;
    return p;
    }

    代码执行解析:对于这个方法,笔者有些疑惑,等找到答案后,再来完善。

    翻阅讨论区的时候,我找到了一个更容易理解的方案。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public  ListNode reverseList4(ListNode head) {
    return reverseListInt(head, null);
    }
    private ListNode reverseListInt(ListNode head, ListNode newHead) {
    if (head == null) {
    return newHead;
    }
    ListNode next = head.next;
    head.next = newHead;
    return reverseListInt(next, head);
    }

    总结:

    以我个人的能力而言,理解这个问题,并能找出自己的解决方案。但是没有最优解的能力,参考了官方提供的例子和讨论区的答案,增加了见识。代码实现能力未增加,但是见识、意识有所长进。也许进步就需要这样积累吧。

阅读此文
post @ 2018-12-04

本地与远端关联

本地已经通过git init初始化过项目

具体操作步骤

  1. 本地初始化

    1
    git init
  2. 添加文件到暂存区

    1
    2
    git add .
    git commit -m 'feat: desc'
  3. 添加远程仓库

    1
    git remote add origin ***(your project https or ssh link)
    • 通过https方式无需配置ssh-key,提交代码的时候需要输入对应托管平台的账号和密码
    • 通过ssh-key方式需要配置公钥

    • 关于https方式和ssh-key方式更多的内容,我会在后续的整理中给出,这里留下一个入口标记[关于https和ssh-key的介绍]
  4. 本地仓库与远程仓库关联

    1
    git branch --set-upstream-to=origin/master master
  5. 拉取远程数据

    1
    git pull --rebase
    • 关于rebase命令,我会在后续的整理中给出,入口标记[rebase]
  6. 上传代码
    1
    git push

本地未初始化过

操作步骤

  1. 在工作目录(workspace)下,直接通过命令完成
    1
    git clone ***(your project https or ssh link)

移除与远端的关联

1
git remote remove origin

新建分支并同步到远端

  1. 在当前分支上新建分支

    1
    git branch test
  2. 切换到test分支

    1
    git checkout test

    小贴士:1、2合并的命令

    1
    git checkout -b test
  3. 提交到远端

    1
    git push origin test

删除分支

ps:不可删除,当前所在的工作分支。

删除一个已经终止的分支,个人理解是所有在这个分支的操作已经merge到master上了

1
git branch -d 分支名称

删除一个代码未merge到master上的分支

1
git branch -D 分支名称

删除远程分支

1
git push origin :分支名称

查看分支

  1. 查看所有远程分支

    1
    git branch -r
  2. 查看远程和本地所有分支

    1
    git branch -a
  3. 查看本地分支

    1
    git branch
  4. 查看本地分支和远程分支映射关系

    1
    git branch -vv

在输出结果中,前面带* 的是当前分支

.gitignore未生效

三部曲

1
2
3
git rm -r --cached .
git add .
git commit -m 'update .gitignore'

.gitignore中未配置忽略规则,确莫名其妙的忽略了一些文件

前置判断可以执行

1
git status --ignored

通过输出查看忽略的文件是否在 Ignored files:

如果存在

临时解决

1
git add -f 文件名称

彻底解决

  1. 查看配置信息

    1
    git config --list
  2. 在列表中看下是否有如下配置

    1
    core.excludesfile=/Users/***/.gitignore_global

    找到这个文件,查看其忽略规则。
    工作中遇到的问题,都是这个文件捣鬼,未发现其他情况。

  3. 执行ignore未生效三部曲

    不想commit,切换到其他分支

    使用stash命令,使用这个命令首先要知道如何管理它。

  4. 查看当前stash列表

    1
    git stash list
  5. 删除某一个stash

    1
    git stash drop stash@{0} (这个[stash@{0}],通过 git stash list 获取)
  6. 查看stash的修改内容

    1
    git show stash@{0}
  7. 应用任意一次修改,不论你在那个分支上进行的stash,其他分支都可以共享这个stash。

    1
    git apply stash@{0}
  8. 弹出最近一次stash

    1
    git stash pop
  9. 清空stash栈

    1
    git stash clear

清楚上述命令后,可以稍微安心的使用stash了。

1
2
3
4
5
6
7
git stash save "stash描述" (建议使用这种方式)
```
# stash drop|clear 后悔了
## drop
1. drop后有类似如下的提示
``` sh
Dropped refs/stash@{0} (f9b6c81bf239a874840ab729870e49f79cbff958)
  1. 根据f9b6c81bf239a874840ab729870e49f79cbff958
    1
    2
    git show f9b6c81bf239a874840ab729870e49f79cbff958 查看并确认修改内容
    git merge f9b6c81bf239a874840ab729870e49f79cbff958 恢复

clear

  1. 找到clear操作的id

    1
    git fsck --lost-found

    输出如下

    1
    2
    3
    4
    5
    6
    Checking object directories: 100% (256/256), done.
    dangling commit 095a68ed29ac9e09f88fb9e6e6bd6d4ff45a8d45
    dangling commit 1a10d5b7d7a7e1f43b07933b5a8e2f8e61763286
    dangling blob 30c413b2d803da68c710e101198545aad8ba6555
    dangling commit 44facef732da848b74e477e5b6f608669d4a1ee2
    dangling tree 475646ae455bb20e4b9b69e3bb4860a95f34dd01
  2. 查看并确认

    1
    git show 095a68ed29ac9e09f88fb9e6e6bd6d4ff45a8d45
  3. 恢复

    1
    git merge  095a68ed29ac9e09f88fb9e6e6bd6d4ff45a8d45

合并多个commit

查看提交日志确定合并哪些commit

1
git log(可以下载一个git可视化查看的插件 gitk\gitg等)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
commit f5ec76eb3929e229a993c9a3e689e591398302ec
Author: ******
Date: Tue Dec 11 09:44:07 2018 +0800

doc: doc1

commit e9c2e91b943dfd53ca7a979bddcb6e94e01b800f
Author: ******
Date: Tue Dec 11 09:35:07 2018 +0800

doc:doc

commit ac1c9497a2a90d5ac2fb9cdc3fd57888c9548cbe
Author: ******
Date: Mon Dec 10 16:16:22 2018 +0800

feat: leetcode算法链表反转

我要将最后二次的合并

1
git rebase -i HEAD~2

进入编辑页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pick e9c2e91 doc:doc
pick f5ec76e doc: doc1

# Rebase ac1c949..f5ec76e onto ac1c949 (2 commands)
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

根据Commands提示 选择s进行合并

1
2
pick e9c2e91 doc:doc
s f5ec76e doc: doc1

esc:wq进入message编辑页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# This is a combination of 2 commits.
# This is the 1st commit message:

doc:doc

# This is the commit message #2:

doc: doc1

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Tue Dec 11 09:35:07 2018 +0800
#
# interactive rebase in progress; onto ac1c949
# Last commands done (2 commands done):
# pick e9c2e91 doc:doc
# s f5ec76e doc: doc1
# No commands remaining.
# You are currently editing a commit while rebasing branch 'master' on 'ac1c949'.
#
# Changes to be committed:
# modified: RocketMQ.md
# new file: SwapNodesInPairs.md

修改message

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# This is a combination of 2 commits.
doc: 合并doc
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# Date: Tue Dec 11 09:35:07 2018 +0800
#
# interactive rebase in progress; onto ac1c949
# Last commands done (2 commands done):
# pick e9c2e91 doc:doc
# s f5ec76e doc: doc1
# No commands remaining.
# You are currently editing a commit while rebasing branch 'master' on 'ac1c949'.
#
# Changes to be committed:
# modified: RocketMQ.md
# new file: SwapNodesInPairs.md

esc:wq保存退出。

1
2
3
4
5
[detached HEAD d30215f] doc: 合并doc
Date: Tue Dec 11 09:35:07 2018 +0800
2 files changed, 108 insertions(+)
create mode 100644 SwapNodesInPairs.md
Successfully rebased and updated refs/heads/master.

获取远端分支

1
2
git pull <远程库名> <远程分支名>:<本地分支名>
git pull origin dev:local_dev

通过tag,实现版本的管理

  1. 创建标签

    1
    git tag -a v1.0.0 -m 'release 1.0.0 version'
  2. 删除标签

    1
    git tag -d v1.0.0
  3. 显示tag信息

    1
    git show v1.0.0
  4. 给指定的commit打标签

    1
    git tag -a v0.0.9 ac1c9497a2a90d5ac2fb9cdc3fd57888c9548cbe -m 'commit tag'
  5. 发布标签

    1
    2
    git push origin v1.0.0 //指定发布
    git push origin --tags //发布所有
  6. 查看tag列表

    1
    git tag
  7. 根据tag创建分支

    1
    git branch 分支名称 tag名称

commit内容写错,重新编辑

最近一次

  1. 如果未push,可以重新编辑,执行命令后会进入vim编辑器

    1
    git commit --amend
    • 关于vim基本使用,我会在后续的整理中给出,入口标记[关于vim]
  2. 如果已经push了,在上一步的基础上执行下面的命令

    1
    git push origin master --force
    • 强制更新有风险,使用需谨慎!在你强制更新的时候,如果别人也commit了代码,将会被你的强制更新覆盖!

任意一次、多次提交修改

  1. 第一步

    1
    git rebase -i HEAD~3

    或者 通过需要修改的父commit的id进行rebase,通过git log命令,commit后面那个。

    1
    git reabse -i commitid(需要修改的父commitid)

    *为什么有父子关系,我们在后续整理中给出解释git数据结构

  2. 执行命令后进入,进入vim编辑器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    pick 9a15ece doc: edit1
    pick 4181834 doc: edit2

    # Rebase ff8c74d..4181834 onto ff8c74d (2 commands)
    #
    # Commands:
    # p, pick = use commit
    # r, reword = use commit, but edit the commit message
    # e, edit = use commit, but stop for amending
    # s, squash = use commit, but meld into previous commit
    # f, fixup = like "squash", but discard this commit's log message
    # x, exec = run command (the rest of the line) using shell
    # d, drop = remove commit
    #
    # These lines can be re-ordered; they are executed from top to bottom.
    #
    # If you remove a line here THAT COMMIT WILL BE LOST.
    #
    # However, if you remove everything, the rebase will be aborted.
    #
    # Note that empty commits are commented out

    根据提示,将需要修改的地方,将pick修改成r或者reword,下面步骤中忽略#Rebase以下的注释

    1
    2
    pick 9a15ece doc: edit1
    r 4181834 doc: edit2

    保存退出:wq,进入下一步操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    doc: edit2

    # Please enter the commit message for your changes. Lines starting
    # with '#' will be ignored, and an empty message aborts the commit.
    #
    # Date: Tue Dec 4 15:54:03 2018 +0800
    #
    # interactive rebase in progress; onto ff8c74d
    # Last commands done (2 commands done):
    # pick 9a15ece doc: edit1
    # r 4181834 doc: edit2
    # No commands remaining.
    # You are currently editing a commit while rebasing branch 'master' on 'ff8c74d'.
    #
    # Changes to be committed:
    # modified: test.txt

    doc: edit2修改即可。:wq保存退出。

  3. 如果想要与远端同步执行 最近一次提交步骤2 即可

Git操作——删除untracked files

删除 untracked files

1
git clean -f

连 untracked 的目录也一起删掉

1
git clean -fd
阅读此文
⬆︎TOP