macOS 配置 golang 运行环境

存档下 macOS 下配置 golang 的过程。

第一步,通过 Homebrew 安装 golang 包

$ brew update
$ brew install go
$ go version
go version go1.12.1 darwin/amd64

第二步,配置 golang 工作环境

这里我们先要创建一个工作目录,后续所有工作都会在这个目录下展开:

$ mkdir -p /Users/kodango/Documents/Code/Go

配置 $GOPATH 等环境变量,这个操作是必须的,否则 go 命令运行的时候不知道去哪里寻找待执行的文件:

$ grep GO ~/.bash_profile
export GOPATH=$HOME/Documents/Code/Go    # 上面创建的工作目录
export GOBIN=$GOPATH/bin
export PATH=$PATH:$GOBIN

接下来按照官网的建议,在工作目录下创建几个字目录:

$ mkdir -p $GOPATH $GOPATH/src $GOPATH/pkg $GOPATH/bin

第三步,测试是否工作正常

在 $GOPATH/src 目录下创建 hello.go 文件:

kodango -> ~/Documents/Code/Go/src/github.com/kodango/hello
$ ls -l
total 8
-rw-r--r--  1 kodango  staff  86  5 18 09:26 hello.go

写一个简单的 Hello world 示例代码:

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello, world\n")
}

使用 go run 命令运行代码:

kodango -> ~/Documents/Code/Go/src/github.com/kodango/hello
$ go run hello.go
Hello, world

或者先用 go install 将编译好的执行文件安装到 $GOBIN 目录。因为之前已经把 $GOBIN 添加到系统 $PATH,可以直接输入 hello 运行。

kodango -> ~/Documents/Code/Go/src/github.com/kodango/hello
$ go install hello.go
$ ls -l "$GOBIN"
total 4120
-rwxr-xr-x  1 kodango  staff  2108040  5 18 09:30 hello
$ hello
Hello, world

浅谈 Word 文档结构

近期工作中遇到一个增加导出为 Word 格式的需求,因此花了点时间仔细了解了下 docx 格式,发现原来一篇 Word 背后有如此复杂的结构。本文主要介绍 docx 文件的结构,但是 pptx、xlsx 的原理应该是类似的。

docx 格式的奥秘

对于 docx 文件大部分人都停留在这是一种 Office 的文件格式,可以用 Word 软件打开,并没有深入去了解下它内部的组成结构是怎么样的。实际上 docx 是一个压缩文件(Zip 格式),可以用 Zip 软件进行解压。

解压之后可以看到,它是有一系列 XML 文件组成:

这就是 docx 文件的奥秘。

OOXML 规范介绍

这些 XML 的用处、每个 XML 文件的定义格式、Zip 目录结构等,都是在 OOXML 规范定义的非常清楚,下面是 OOXML 官网对此的介绍:

Office Open XML, also known as OpenXML or OOXML, is an XML-based format for office documents, including word processing documents, spreadsheets, presentations, as well as charts, diagrams, shapes, and other graphical material. The specification was developed by Microsoft and adopted by ECMA International as ECMA-376 in 2006. A second version was released in December, 2008, and a third version of the standard released in June, 2011. The specification has been adopted by ISO and IEC as ISO/IEC 29500.

由此可见,word/ppt/excel 等几种格式的组织结构都是在同一个规范下定义的,他们的原理以及解析的方法都一样。OOXML 是微软公司在 2006 年公布的规范。类似的还有 Open Document Format (ODF),它是 OpenOffice.org 开源软件所使用的规范。

从官网介绍可以看出 OOXML 规范主要包括两个方面,第一个显然是每个 XML 文件的语法定义,类似 markdown 语言的语法描述一样,word 文件的格式、样式、内容布局等等都是和 XML 里面的 TAG 一一对应。第二个,就是对文件的组织结构的定义,前面看到 docx 实际上是一个 Zip 文件,解压缩出来的目录结构是有规则的,这个通过 Open Packaging Conventions (OPC) 定义,具体可以查看 Anatomy of a WordProcessingML File

查看全文

如何删除文件中的重复行

有时候删除文件里的重复行是一个很常见的需求,这个用 shell 命令有很多处理方法。

第一种方案是用 sort 命令的 -u 参数:

$ sort -u input.txt > output.txt

第二种方案是用 awk 命令,它的关键在于用一个字典来保存记录:

$ awk '!seen[$0]++' input.txt > output.txt

这和第一种方案的区别在于,即使文件中重复行不连续,依然可以删除。

第三种方案是用 sed 命令,但是其实不大推荐,它相比第一种方案复杂多,而且很容易写错:

$ sort -n input.txt | sed '$!N;/^\(.*\)\n\1$/!P;D' > output.txt

这种方案里面用到了 sed 的高级命令(N、P、D),相关介绍可以参考之前写的文章 Sed and awk 笔记之 sed 篇:高级命令(一)

这里有一个小技巧,如果遇到比较复杂的 sed 命令拿捏不准的化,可以用 sedsed 这个脚本来调试执行,看每一步执行的情况。

使用方法也很简单,比如调试上面这个 sed 命令,命令执行结果会展示每一步执行后模式空间和保持空间的内容,一目了然:

$ sort -n input.txt | python sedsed.py -d '$!N;/^\(.*\)\n\1$/!P;D'

团队招聘贴研发岗

新财年开始了,团队招聘继续放开,欢迎有兴趣的同学/朋友投递简历过来,当然也欢迎纯交流。简历投递的邮箱见文章最下方。

团队介绍

我们来自于阿里云智能事业群,专注与企业级的内容管理中台的开发,通过标准化、智能化和数据化为产品团队提供专业、高效的内容管理解决方案,帮助产品更好的面对全球用户。

岗位职责

1、负责阿里云智能内容管理平台和一站式知识库系统的研发工作,包括但不限于文档生命周期管理、全方位数据化运营以及智能推荐和搜索;
2、对所负责的系统进行良好规划和设计,使系统体系化并具有前瞻性,在保证稳定性的基础上更高效支撑业务发展;
3、要深入理解业务,技术驱动产品发展,可以结合业务进行技术创新,持续优化平台的用户体验,提升内容管理效率;

招聘条件

JAVA开发专家(P7,杭州):
1、JAVA基础扎实,熟悉网络编程、多线程并发编程,熟悉分布式存储、缓存、关系型数据库;
2、三年以上Java系统开发经验,精通面向对象设计,对项目中接触过的开源框架、技术产品、算法等有针对性的进行过深入学习,了解其底层机制和原理;
3、熟练掌握Linux,具备系统调试、性能调优等技能,疑难问题有较强的分析与排查能力;
4、具备优秀的团队协作精神,能利用自身技术能力提升团队整体研发效率,提高团队影响力;
5、对技术有持续的热情,个性乐观开朗,逻辑性强,善于和各种背景的人合作;
6、对云计算或云计算产品有认知有实践经验者优先;

简历投递

本岗位持续开放,感兴趣的小伙伴们请发送简历至 dangoakachan@gmail.com,或者官网投递

谈谈近况

好久没有更新文章,博客都快长草了。距离上次更新文章已经两年多了,惭愧。

谈谈近况吧,现在已经从运维转回做开发了,算是和五年的运维生涯告一段落了。做开发的日子,相对轻松了不少,至少不需要半夜三更被叫起,或者一天到晚收报警短信,生活质量提升不少。其实我们这个行业发展是非常迅速的,之前一直在做 iaas 测的运维,那时容器也没现在这么火。但是,无论怎么样,运维依然是很重要的一个岗位,或者说是产品不可分割的一部分,最完美的就是产品设计之初就已经考虑到可运维性,现在看随着技术的发展以及基础设施的持续演化,之前梦寐以求的事情,未来也许会成为大家眼里的“基本”能力。

现在的团队负责内容平台的开发,基本算是换了一个全新的轨道,但是随着做的不断深入,发现这里面包含的内容、可以做的东西还是非常多的,我们的目标是要打造一个专业的内容管理平台。眼下离目标很远,但是那种看着自己的孩子慢慢长大的感觉,还是觉得非常值得,现在的团队规模并不大,但是我们还是非常缺人,如果大家有兴趣,可以私下交流。

新版 Firefox 试玩

记不清楚从什么时候开始使用 Firefox 的,用的第一个版本应该是 v3.6,当时觉得 Firefox 可定制性很强,可以按照自己的喜好折腾主题、样式、功能,也就逐渐迷上了火狐浏览器。也是大概在那期间(11年),自己开始业余时间学习 JavaScript,动手写一些 Greasemonkey 的脚本拓展网站功能。不过很遗憾,因为觉得 Firefox 的扩展入门比较难(后来开发 Chrome 扩展就发现非常容易),也就一直没有机会折腾一个扩展玩玩。

每一个曾经的 “Firefox” 粉应该都折腾过用户样式(Stylish)、油猴脚本(GreaseMonkey)、手势操作(FireGestures)、拖拽( Drag to go?)、下载以及标签定制这些扩展吧,这些也是当时 Firefox 受到大家追捧的很重要一个原因,几乎整个界面和功能都可以定制。当时甚至一度沉迷 Vimperator(以及Pentadactyl),打算不用鼠标,全键盘操作浏览器。

不过后来 Google Chrome 出现了,因为它非常简单、好用、启动快,而且自己也越来越不喜欢折腾,就叛变了,一直到换成 Mac 系统。现在主力是默认的浏览器 Safari,iMac、MacBook、iPhone、iPad 多个设备上基本可以保持一致的体验,开箱即用,Safari 我基本也没安装扩展,甚至广告过滤的扩展也没安装。

下午看到新闻说 Firefox 57 发布了,而且有人说速度比以前快多了,就下载了试玩了一下午,却是非常流畅。不过今非昔比,终究不是以前熟悉的 3.6 了。

New Firefox:

右键菜单体验很好,直接把前进、后退、刷新的功能放进来了,很方便。

Leetcode 3. Longest Substring Without Repeating Characters

继续做把第三题的解题思路放上来。第三题的目的是求字符串内最长的不重复子串,两个要求非常清晰。下面是题目的描述:

Given a string, find the length of the longest substring without repeating characters.

Examples:

Given "abcabcbb", the answer is "abc", which the length is 3.

Given "bbbbb", the answer is "b", with the length of 1.

Given "pwwkew", the answer is "wke", with the length of 3. Note that the answer must be a substring, "pwke" is a subsequence and not a substring.

思路:

假设子串的初始下标为 i,结束下标为 j。i 和 j 的初始值均为 0。j 开始一次往后遍历,如果这个字符串没有重复字符,那么 j 会一直走到最后,此时最长子串就是字符串本身。实际情况没有那么理想,我们主要分析这类场景。当处于 j 位置的字符和子串之前的字符重复(假设位置为 k),那么就应该停下,并且更新最长子串的长度(假设最长长度为 max),j 继续往后走,同时 i 的位置需要挪到重复字符之后,即 k+1 处。直到遍历到字符串的最后。

比如用第一个例子作为说明,初始状态 i = j = 0,最长子串长度 max = 0:

  1. j=0 处的字符是 a,无重复,当前子串长度为 1;
  2. j=1 处的字符是 b,无重复,当前子串长度为 2;
  3. j=2 处的字符是 b,无重复,当前子串长度为 3;
  4. j=3 处的字符是 a,发现重复,更新 max = 3,i = 0+1,当前子串长度为 3;
  5. j=4 处的字符是 b,发现重复,max 不变,i = 1+1,当前子串长度为 3;
  6. j=5 处的字符是 c,发现重复,max 不变,i = 2+1,当前子串长度为 3;
  7. j=6 处的字符是 b,发现重复,max 不变,i = 4+1,当前子串长度为 2;
  8. j=7 处的字符是 b,发现重复,max 不变,i = 6+1,当前子串长度为 1;

因此,"abcabcbb" 的最长子串长度是 3。

接下来我们写上代码:

func lengthOfLongestSubstring(s string) int {
	n := len(s)

	if n <= 1 {
		return n
	}

	tbl := make(map[byte]int, n)
	lo, longest, curr := 0, 0, 0

	for hi := 0; hi < n; hi++ {
		if k, ok := tbl[s[hi]]; ok && k >= lo {
			lo = k + 1
		}

		tbl[s[hi]] = hi
		curr = hi - lo + 1

		if curr > longest {
			longest = curr
		}
	}

	return longest
}

完整的代码参见这里,以及测试代码

LeetCode Problem 2 - Add Two Numbers

继续第二道题目,下面是问题描述:

You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.

You may assume the two numbers do not contain any leading zero, except the number 0 itself.

Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 0 -> 8

这个问题比较简单,基本上解题思路是比较清晰的。输入是两个链表,链表的元素都是单个数字(0-9),要求将两个列表的相应节点数字相加,并作为结果链表返回。

这个题咋看可以马上开始解答,但是在此之前还是有一些需要注意的地方。第一点是,题目并没有说明链表的长度,所以 A 和 B 两个链表可能不一定相同长度,那么如果一个链表更长,那么相加怎么处理呢?这里就考虑直接返回即可,相当于+0。第二点是,如果相加溢出怎么处理,其实题目的例子里面已经很清晰了,溢出会发生进位,依次向后处理。第三点是,如果最后一位发生进位呢,这点容易被遗忘,需要新增一个节点。

下面是具体的代码:

// Problem 2. Add Two Numbers
// URL: https://leetcode.com/problems/add-two-numbers/tabs/description

// ListNode defines a singly-linked list
type ListNode struct {
	Val  int
	Next *ListNode
}

func addTwoNumbers(l1 *ListNode, l2 *ListNode) *ListNode {
	head := &ListNode{0, nil} // sentinel node
	carry := 0                // carray bit

	for tail := head; l1 != nil || l2 != nil || carry != 0; tail = tail.Next {
		sum := carry

		if l1 != nil {
			sum += l1.Val
			l1 = l1.Next
		}

		if l2 != nil {
			sum += l2.Val
			l2 = l2.Next
		}

		tail.Next, carry = &ListNode{sum % 10, nil}, sum/10
	}

	return head.Next // discard sentinel node
}

这里在链表加了一个哨兵节点,主要是为了处理方便。完整的代码参见这里,以及测试代码