chenDoInG 想法太多而书读太少

macOS定时任务命令launchctl

macOS定时任务命令:launchctl

macOS通过launchctl命令加载plist配置文件来管理定时任务

launchctl是一个统一的服务管理框架,可以启动、停止和管理守护进程、应用程序、进程和脚本等。 launchctl是通过配置文件来指定执行周期和任务的。

plist存放目录

  • /Library/LaunchDaemons –>只要系统启动了,哪怕用户不登陆系统也会被执行

  • /Library/LaunchAgents –>当用户登陆系统后才会被执行

  • ~/Library/LaunchAgents 由用户自己定义的任务项

  • /System/Library/LaunchAgents 由Mac OS X为用户定义的任务项

  • /System/Library/LaunchDaemons 由Mac OS X定义的守护进程任务项

plist标签说明

1、Label:对应的需要保证全局唯一性;
2、Program:要运行的程序;
3、ProgramArguments:命令语句
4、StartCalendarInterval:运行的时间,单个时间点使用dict,多个时间点使用 array <dict>
5、StartInterval:时间间隔,与StartCalendarInterval使用其一,单位为秒
6、StandardInPath、StandardOutPath、StandardErrorPath:标准的输入输出错误文件,这里建议不要使用 .log 作为后缀,会打不开里面的信息。

launchctl命令

# 加载任务, -w选项会将plist文件中无效的key覆盖掉,建议加上
$ launchctl load -w com.xx.xx.plist

# 删除任务
$ launchctl unload -w com.xx.xx.plist

# 查看任务列表, 使用 grep '任务部分名字' 过滤
$ launchctl list | grep 'com.xx.xx'

# 开始任务
$ launchctl start  com.xx.xx.plist

# 结束任务
$ launchctl stop   com.xx.xx.plist

修改任务之后必须先unload,然后在load,才能生效。load之后,用start可以立即执行来测试任务是否正确

实战

一、编写一个启动脚本run.sh

cd ~/Library/LaunchAgents
vi run.sh
mkdir log
#!/bin/sh

# 进入launch.py程序所在目录
cd /Users/chenDoInG/Library/LaunchAgents &&
# 记录一下开始时间
echo `date` >> log/run.log &&
# 执行python脚本(注意前面要指定python运行环境/usr/bin/python,根据自己的情况改变)
/usr/local/bin/python3 test.py
# 运行完成
echo 'finish' >> log/run.log

!!!脚本很多会涉及相对路径问题,所以使用启动脚本,先进入程序所在目录可以解决大部分错误

二、编写python脚本test.py

#!/usr/local/bin/python3
# -*- coding:utf-8 -*-

import datetime

def add(path, content):
    with open(path,'a') as f:
        f.write(content + '\n')

path = '/Users/chenDoInG/Library/LaunchAgents/log/run.log'

now_time = "{}".format(datetime.datetime.now())
add(path, now_time)

跟run.sh一样 python环境需要自己的情况改变

三、测试下脚本是否可以运行

./run.sh
cat log/run.log

如果提示permission denied

执行

chmod 754 run.sh

如果授权完还提示operation not permitted

重启电脑,重启过程中按住command+R,进入保护模式

打开terminal终端,输入

csrutil disable

退出终端,重启

四、编写plist

vi com.test.plist

填写

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <!-- 名称,要全局唯一 -->
    <key>Label</key>
    <string>com.test.unique</string>

    <!-- 脚本任务 -->
    <key>Program</key>
    <string>/Users/chenDoInG/Library/LaunchAgents/run.sh</string>

    <!-- 指定要运行的时间 -->
    <key>StartCalendarInterval</key>
    <array>
        <dict>
                <key>Minute</key>
                <integer>40</integer>
                <key>Hour</key>
                <integer>14</integer>
        </dict>
        <dict>
                <key>Minute</key>
                <integer>30</integer>
                <key>Hour</key>
                <integer>08</integer>
        </dict>
    </array>

    <!--开机启动-->

    <!-- 错误输出 -->
    <key>StandardErrorPath</key>
    <string>/Users/chenDoInG/Library/LaunchAgents/log/run-error</string>
</dict>
</plist>

这里用到是两组指定时间运行14.40和8.30,如果要用时间间隔,讲整体替换成

<!-- 运行间隔,与StartCalenderInterval使用其一,单位为秒 -->
    <key>StartInterval</key>
    <integer>30</integer>

五、启动launctl

launchctl load -w com.test.plist
tail -f log/run*

Substring with Concatenation of All Words

翻译自N00tc0d3r(请自行翻墙)

学习下技术文章到底应该怎么写,对比于百度搜索出来的各种解题思路,我只能呵呵!

题目:

​ Substring with Concatenation of All Words

	You are given a string, S, and a list of words, L, that are all of the same length. Find all starting indices of substring(s) in S that is a concatenation of each word in L exactly once and without any intervening characters.

	For example, given:
	S: "barfoothefoobarman"
	L: ["foo", "bar"]

	You should return the indices: [0,9].
	(order does not matter).
	
	举例:给定一个字符串S,和一个包含m个单词的词组L,每个单词长度为k,如果字符串S里包含由词组L中所有单词组成的字符串子串,返回该子串的起始位置

解题思路:

基本想法是遍历字符串S的每个字符,判断当前字符是否是词组L中某个单词的开头

###怎么判断当前字符是某个单词的开头?

因为我们知道词组L中的所有单词都是固定长度的,所以从字符串S的结点i开始,我们将字符串S截取成m段字符串子串(后面都直接叫子串),这里m是词组L中单词的总数,然后我们就可以判断每个子串是否是词组L中的单词和是否重复

###怎么判断每个子串是集合L中的单词?

最简单的方法是拿子串和词组L中的每个单词比较。但是对于每个子串,时间复杂度是O(m),当我们有特别多的子串或者词组中单词总数特别多时,时间开销代价特别高。实际上的时间复杂度是O(k*m) = O(m),这里k是单词的长度。这意味着如果单词的长度不是特别短的话,时间开销的代价会更高。

我们可以将词组L的单词存储在一个hashTable里,而hashTable搜索的时间复杂度是O(1)

什么样的hashTable?怎么判断重复子串?如果词组L本身包含重复单词?

我们创建了一个包含词组L中所有单词的hashTable。每当有子串匹配上单词时,我们将该单词从这个hashTable移除。我们使用HashMap<String,Integer>来替代HashMap<String,Boolean>以便于统计词组L中每个单词的出现次数。因此每当有子串匹配上单词时,我们将HashMap中该词组的次数减一直到次数为零时,我们将该单词从HashMap中移除。

现在我们有了一个大体上的思路:

  • 创建一个包含词组L中所有单词和出现次数的hashTable
  • 对于字符串S中的每个字符,判断是否是某个单词的组成部分。如果满足以下的所有条件,将字符的位置记录到结果list里。

    • 后续所有长度为k的子串都是词组L中的单词。
    • 词组L中的每个单词仅出现一次。

这个算法的复杂度是什么?

判断每个字符的时间复杂度是O(k*M)=O(m),我们需要判断(n-k*m)次,所以总的时间复杂度是O((n-k*m)*m)。因为我们创建了一个hashTable,所以空间复杂度是取决于词组L的单词总数,也就是O(k*m) = O(m).

public ArrayList<Integer> findSubstring(String S, String[] L) {  
  
  			ArrayList<Integer> indices = new ArrayList<Integer>();  
        if (L.length == 0) return indices;  

        int total = L.length, wordLen = L[0].length();  

        // store the words and frequencies in a hash table  
        HashMap<String, Integer> words = new HashMap<String, Integer>();  
        for (String s : L) {  
          if (words.containsKey(s)) {  
              words.put(s, words.get(s)+1);  
          } else {  
              words.put(s, 1);  
          }  
        }  

        // find concatenations  
        for (int i=0; i <= S.length() - total*wordLen; ++i) {  
           // check if it is a concatenation   
          HashMap<String, Integer> target = new HashMap<String, Integer>(words);  
          for (int j = i; j <= S.length() - wordLen && !target.isEmpty(); j+=wordLen) {  
              String sub = S.substring(j, j+wordLen);  
              if (!target.containsKey(sub)) break;  
              if (target.get(sub) > 1) {  // reduce the frequency
                target.put(sub, target.get(sub)-1);  
              } else {  // remove the word if only one left
                target.remove(sub);  
              }
          }  
          if (target.isEmpty()) {  
              indices.add(i);  
          }  
        }  

      	return indices; 
}  
 	

观察上面的算法,我们发现对于每个子串都有被重复检查。我们有必要重复检查每个字符后的所有子串么?答案是“没必要”。

我们可以使用和KMP字符串匹配算法相似的的策略:

提前计算下一个可能是子串开头的地方,而不是从头开始计算。

回顾题目我们在查找一个由词组L中所有单词组成的字符串并且单词的长度是固定的。为了覆盖所有的可能性,我们可以从字符串S的任意字符S[0,1,…,k-1]开始搜索所有的结果。也就是我们需要检查0,k,2k,…,n-1;然后1,k+1,2k+1,…,n-1;直到k-1,2k-1,3k-1,…,n-1。

从字符串S的第一个字符(begin = 0),截取一个长度为k的字符串子串,

  • 如果该子串不是词组L中的单词,搜索下一个子串(begin + = k);
  • 如果该子串是词组L中的单词但是已经被搜索过了(比如,重复的单词),向后移动begin直到重复的单词从当前的计算窗口移除了
  • 否则,将该子串加入当前集合。如果我们计算完所有的单词(例如,得到一个正确的结果),向后移动begin检查下一个字符串子串

现在这个算法的复杂度是什么?

我们检查每个字符串最多两次,一次将字符串加到集合中,另外一次将字符串从集合中移除。因此时间复杂度是O(n),这里n是字符串的长度。检查每个字符串是否是词组L中的单词,复杂度O(k)=O(1)。所以实际上的时间复杂度是O(n*k) = O(n)。

private void addWord(String w, HashMap<String, Integer> words) {  
if (words.containsKey(w)) {  
     		words.put(w, words.get(w)+1);  
   		} else {  
     		words.put(w, 1);  
   		}  
 	}  

 	private void removeWord(String w, HashMap<String, Integer> words) {  
   		if (!words.containsKey(w)) return;  
   		if (words.get(w) > 1) {  
     		words.put(w, words.get(w)-1);  
   		} else {  
     		words.remove(w);  
   		}  
 	}  

 	private int slideWindow(String S, int begin, int wordLen, HashMap<String, Integer> words) {  
   		String old = S.substring(begin, begin+wordLen);  
   		addWord(old, words);  
   		return begin+wordLen;  
 	}  

 	public ArrayList<Integer> findSubstring(String S, String[] L) {  
   		ArrayList<Integer> indices = new ArrayList<Integer>();  
   		if (L.length == 0) return indices;  

   		int total = L.length, wordLen = L[0].length();  

   		// store the words and frequencies in a hash table  
   		HashMap<String, Integer> expectWords = new HashMap<String, Integer>();  
   		for (String w : L) {  
     		addWord(w, expectWords);  
   		}  

   		// find concatenations  
   		for (int i=0; i < wordLen; ++i) {  
     		// check if there are any concatenations  
     		int count = 0;  
     		HashMap<String, Integer> collectWords = new HashMap<String, Integer>(expectWords);  
     		for (int j = i, begin = i; j <= S.length() - (total-count)*wordLen && begin <= S.length() - total*wordLen;) {  
      			String sub = S.substring(j, j+wordLen);  
       			if (!expectWords.containsKey(sub)) { // if not an expect word, reset  
         			begin = j + wordLen;  
         			j = begin;  
         			count = 0;  
         		collectWords.putAll(expectWords);  
       			} else if (!collectWords.containsKey(sub)) { // if duplicate, forward begin by 1  
         			begin = slideWindow(S, begin, wordLen, collectWords);  
       			} else {  
         			removeWord(sub, collectWords);  
         			j += wordLen;  
         			++count;  
         			if (collectWords.isEmpty()) {  
           				indices.add(begin);  
           				begin = slideWindow(S, begin, wordLen, collectWords);  
           				--count;  
         			}  
       			}  
     		}  
   		}  

   		return indices;  
 		}  	
 	

优化:原来的解法使用map的putAll浪费的大量的时间,以下是我优化后的,减少了大量的map复制时间(leetCode里面结果从59ms->17ms)

	public ArrayList<Integer> findSubstring(String s, String[] words) {
    ArrayList<Integer> indices = new ArrayList<>();
    int total, wordLen;
    if ((total = words.length) == 0 || (wordLen = words[0].length()) == 0 || s.length() < wordLen) return indices;

    // store the words and frequencies in a hash table
    HashMap<String, Integer> expectWords = new HashMap<>();
    for (String w : words) {
        addWord(w, expectWords);
    }

    HashMap<String, Integer> check = new HashMap<>();

    // find concatenations
    for (int i = 0; i < wordLen; i++) {
        // check if there are any concatenations
        check.clear();
        int begin = i, cursor = i;
        while (begin <= s.length() - total * wordLen) {
            String sub = s.substring(cursor, cursor + wordLen);
            if (!expectWords.containsKey(sub)) {
                begin = cursor + wordLen;
                cursor = begin;
                check.clear();
            } else if (Objects.equals(check.get(sub), expectWords.get(sub))) {
                // if duplicate, forward begin
                removeWord(s.substring(begin, begin + wordLen), check);
                begin += wordLen;
            } else {
                addWord(sub, check);
                cursor += wordLen;
                if (cursor - begin == total * wordLen) {
                    indices.add(begin);
                    begin += wordLen;
                    cursor = begin;
                    check.clear();
                }
            }
        }
    }

    return indices;
}

review myself

吾日三省吾身

转载自褪墨

我用了一年的时间证明了通过不断反省是可以改正错误的,我总结了这一年的实践经验,对反省系统进行了优化,使其更合理、更有条理,最终写成此文。

一、为什么要自我反省

曾子曰:吾日三省吾身,为人谋而不忠乎?与朋友交而不信乎?传不习乎?——《论语·学而》 曾子说:“我每天都多次反省自身:替人家谋虑是否不够尽心?和朋友交往是否不够诚信?老师传授的知识是否复习了呢?”

(一)为了不再痛苦

我们都曾因为过去的错误而困扰着、痛苦着、追悔莫及着,无论是对他人,还是对自己的造成伤害。为了避免重蹈覆辙,我们需要不断反省自己。为了不再痛苦,我们需要记住痛苦与错误。我们要从中找出解决办法,不再犯错。

(二)勿忘初衷,坚持自我

一旦做了违背初衷的事情,你就该去反省自己,这样可以及时把你从偏离的轨道中拉回来。反省可以让你忠于自己,你会时刻记着自己的最初。不要成为自己讨厌的那个自己,如果不进行自我反省,我们终有一天会随着时间的流逝被周围影响而失去最初的自我。我想,再也没有什么比这更可怕的了。

不断地自我反省,不断地改正缺点,不断地完善自己,一点一滴成长起来。

二、我反省哪些方面?

曾子从“为人谋而不忠乎?”、“与朋友交而不信乎?”、“传不习乎?”这三个方面来反省自己。

而我则是从以下方面来反省自己:

  1. 日常生活中的“言”:礼貌,谦虚,不语言攻击他人,说话有没有中伤他人,是否说了不该说或是不恰当的话,是否使用了不恰当的语气、态度或是词语等。
  2. 日常生活中的“行”:坚守道德,行为在安全范畴,遵守法律法规,遵从价值观,遵从人生准则,在他人需要帮助的时候有没有伸出援手,不应该做什么而去做了,应该做些什么而没有去做等。
  3. 其他需要改进的地方:在日常生活中有哪些地方都需要去改进,可以做得更好的?

三、如何建立反省系统

(一)首先要明白自己要反省哪些方面

你要用记录哪些方面,完全取决于你自己,你应该自己决定要去反省些什么。最好是写下来。

(二)收集

收集是反省的第一步,你需要收集你做得不好的地方,这样才能知道哪些地方是需要改进。

我是随时觉察自己的一言一行,因此我并没有固定的反省时间,犯错误后总是可以很快意识到,之后会将错误的内容立刻记录下来。使用纸笔记录或是笔记类软件都可以。

如果你不能很快意识到自己的错误,那么就在晚上来回想当天自己所做有什么地方是需要改进吧。

(三)整理与反省

如果你没有进行反省,那么记录就会变得毫无价值。

晚上的时候我会将所记录的错误整理到笔记类软件里,每整理一条就会反省自己,我会问自己四个问题,并将问题答案写下来。

四个问题:

  1. 这个做法为什么是不正确的或是不恰当的?
  2. 我为什么会犯错(我心里怎么想的)?
  3. 正确的做法是什么?
  4. 对于第二点所写,如何阻止自己再犯或是说服自己?

回答问题时要直视内心深处的自己,不要逃避,写出真正的答案,只有这样你才能发现问题的源头是什么。

举个例子:

12月12日

闯红灯:	

	1. 这个做法为什么是不正确的或是不恰当的?
		a. 给生命安全带来一定风险
		b. 违反道路交通安全法
		c. 如果出事故会给家庭带来一定负担,会让他人伤心
	2. 我为什么会犯错(我心里怎么想的)
		a. 同行人都闯红灯了,自己不跟着走不好意思
		b. 抱着侥幸心理:不会被车撞
	3. 正确的做法是什么
		a. 不在特殊情况下不能闯红灯,不管同行人是否闯红灯,都应该等待绿灯
	4. 对于第二点所写,如何阻止自己再犯或是说服自己?
		a. 生命安全大于一切,包括“不好意思”,无须理会他人是否怎么做,生命第一。
		b. 在安全方面抱着侥幸心理,不可取,代价巨大

每一次反省,都是对自己的警醒。这个反省系统不能百分之百地立刻阻止你再犯,但是通过反省与回顾,你会对错误有更深的了解,会逐渐减少错误的发生,直至零。

在开始的时候,我记录了有好多错误,后来就减少到几个星期犯一次错误,再后来就是几个月一次。

如果已记录的错误再犯,不要重新写日期与内容,而是在已记录错误后用括号,在括号内写入再犯日期。同时,如果对四个问题有新的看法就新添在问题答案之后。这样做的好处是减少了日后回顾所需要的时间成本。

举个例子:

12月12日

闯红灯 (12月14日、12月16日)

(此处省略……)
也许会有人问,我忘记了此前已经有记录了怎么办。如果你忘记了,那一定是你没有回顾。

(四)回顾

每周我会固定一个时间来回顾所有的错误,为了不忘记那些错误,为了不放过可以让自己变得更好的机会。一次次地提醒自己,什么才是正确的。

四、其他

(一)重要的是你要学会正确面对错误

一旦意识到自己犯错误,就看成是一个学习成长的机会,而不是深陷其中。

你要明白人无完人,有些错误在所难免的,但也有些错误是绝对不能犯的(比如犯罪)。你要学会原谅自己,不要对自己过度苛刻,每个人都会犯错误,都会遭遇挫折。既然错误无法避免,更重要的就是我们如何应对错误,以及避免再犯。

意识到错误的过程中应该是自然而随心的,是一种很放松的状态。不要过度紧张,不要精神绷紧,随心出发。

(二)反省系统基于什么?

什么事情是正确的,什么事情是错误的。什么事情是该做的,什么事情是不该做的。这一切是否需要记录,都基于你的价值观。

五、结语

很多值得我们去学习的文言文,譬如“见贤思齐焉,见不贤而内自省也”,对我也是影响至今。

看见有德行或才干的人就要想着向他学习,看见没有德行的人,自己的内心就要反省是否有和他一样的错误。

学习古人的智慧。取其精华,去其糟粕。

Exhortations to Study

荀子《劝学》及翻译

君子曰:学不可以已。

  青,取之于蓝,而青于蓝;冰,水为之,而寒于水。木直中绳,輮以为轮,其曲中规,虽有槁暴,不复挺者,輮使之然也。故木受绳则直,金就砺则利,君子博学而日参省乎己,则知明而行无过矣。故不登高山,不知天之高也;不临深溪,不知地之厚也;不闻先王之遗言,不知学问之大也。干、越、夷、貉之子,生而同声,长而异俗,教使之然也。诗曰:「嗟尔君子,无恒安息。靖共尔位,好是正直。神之听之,介尔景福。」神莫大于化道,福莫长于无祸。

  吾尝终日而思矣,不如须臾之所学也。吾尝跂而望矣,不如登高之博见也。登高而招,臂非加长也,而见者远;顺风而呼,声非加疾也,而闻者彰。假舆马者,非利足也,而致千里;假舟楫者,非能水也,而绝江河。君子生非异也,善假于物也。

  南方有鸟焉,名曰蒙鸠,以羽为巢,而编之以发,系之苇苕,风至苕折,卵破子死。巢非不完也,所系者然也。西方有木焉,名曰射干,茎长四寸,生于高山之上,而临百仞之渊,木茎非能长也,所立者然也。蓬生麻中,不扶而直;白沙在涅,与之俱黑。兰槐之根是为芷,其渐之滫,君子不近,庶人不服。其质非不美也,所渐者然也。故君子居必择乡,游必就士,所以防邪辟而近中正也。

  物类之起,必有所始。荣辱之来,必象其德。肉腐出虫,鱼枯生蠹。怠慢忘身,祸灾乃作。强自取柱,柔自取束。邪秽在身,怨之所构。施薪若一,火就燥也,平地若一,水就溼也。草木畴生,禽兽群焉,物各从其类也。是故质的张,而弓矢至焉;林木茂,而斧斤至焉;树成荫,而众鸟息焉。酰酸,而蚋聚焉。故言有招祸也,行有招辱也,君子慎其所立乎! 

  积土成山,风雨兴焉;积水成渊,蛟龙生焉;积善成德,而神明自得,圣心备焉。故不积跬步,无以至千里;不积小流,无以成江海。骐骥一跃,不能十步;驽马十驾,功在不舍。锲而舍之,朽木不折;锲而不舍,金石可镂。蚓无爪牙之利,筋骨之强,上食埃土,下饮黄泉,用心一也。蟹六跪而二螯,非蛇鳝之穴无可寄托者,用心躁也。是故无冥冥之志者,无昭昭之明;无惛惛之事者,无赫赫之功。行衢道者不至,事两君者不容。目不能两视而明,耳不能两听而聪。螣蛇无足而飞,鼫鼠五技而穷。

《诗》曰:“尸鸠在桑,其子七兮。淑人君子,其仪一兮。其仪一兮,心如结兮!”故君子结于一也。

  昔者瓠巴鼓瑟,而流鱼出听;伯牙鼓琴,而六马仰秣。故声无小而不闻,行无隐而不形 。玉在山而草润,渊生珠而崖不枯。为善不积邪?安有不闻者乎?

  学恶乎始?恶乎终?曰:其数则始乎诵经,终乎读礼;其义则始乎为士,终乎为圣人,真积力久则入,学至乎没而后止也。故学数有终,若其义则不可须臾舍也。为之,人也;舍之,禽兽也。故书者,政事之纪也;诗者,中声之所止也;礼者,法之大分,类之纲纪也。故学至乎礼而止矣。夫是之谓道德之极。礼之敬文也,乐之中和也,诗书之博也,春秋之微 也,在天地之间者毕矣。君子之学也,入乎耳,箸乎心,布乎四体,形乎动静。端而言,蝡而动,一可以为法则。小人之学也,入乎耳,出乎口。口耳之间则四寸耳。曷足以美七尺之躯哉!古之学者为己,今之学者为人。君子之学也美其身,小人之学也以为禽犊。故不问而告谓之傲,问一而告 二谓之囋。傲,非也,囋、非也;君子如向矣。

  君子之学也,入乎耳,着乎心,布乎四体,形乎动静。端而言,蝡而动,一可以为法则。小人之学也,入乎耳,出乎口;口耳之间,则四寸耳,曷足以美七尺之躯哉!古之学者为己,今之学者为人。君子之学也,以美其身;小人之学也,以为禽犊。故不问而告谓之傲,问一而告二谓之囋。傲、非也,囋、非也;君子如向矣。

  学莫便乎近其人。礼乐法而不说,诗书故而不切,春秋约而不速。方其人之习君子之说,则尊以遍矣,周于世矣。故曰:学莫便乎近其人。

  学之经莫速乎好其人,隆礼次之。上不能好其人,下不能隆礼,安特将学杂识志,顺诗书而已耳。则末世穷年,不免为陋儒而已。将原先王,本仁义,则礼正其经纬蹊径也。若挈裘领,诎五指而顿之,顺者不可胜数也。不道礼宪,以诗书为之,譬之犹以指测河也,以戈舂黍也,以锥餐壶也,不可以得之矣。故隆礼,虽未明,法士也;不隆礼,虽察辩,散儒也。

  问楛者,勿告也;告楛者,勿问也;说楛者,勿听也。有争气者,勿与辩也。故必由其道至,然后接之;非其道则避之。故礼恭,而后可与言道之方;辞顺,而后可与言道之理;色从而后可与言道之致。故未可与言而言,谓之傲;可与言而不言,谓之隐;不观气色而言,谓瞽。故君子不傲、不隐、不瞽,谨顺其身。诗曰:’匪交匪舒,天子所予。’此之谓也。

  百发失一,不足谓善射;千里蹞步不至,不足谓善御;伦类不通,仁义不一,不足谓善学。学也者,固学一之也。一出焉,一入焉,涂巷之人也;其善者少,不善者多,桀纣盗跖也;全之尽之,然后学者也。

  君子知夫不全不粹之不足以为美也,故诵数以贯之,思索以通之,为其人以处之,除其害者以持养之。使目非是无欲见也,使口非是无欲言也,使心非是无欲虑也。及至其致好之也,目好之五色,耳好之五声,口好之五味,心利之有天下。是故权利不能倾也,群众不能移也,天下不能荡也。生乎由是,死乎由是,夫是之谓德操。德操然后能定,能定然后能应。能定能应,夫是之谓成人。天见其明,地见其光,君子贵其全也。

译文

  君子说:学习是不可以停止的。

靛青是从蓝草里提取的,可是比蓝草的颜色更深;冰是水凝结而成的,却比水还要寒冷。木材直得符合拉直的墨绳,用煣的工艺把它制成车轮,(那么)木材的弯度(就)合乎圆的标准了,即使再干枯了,(木材)也不会再挺直,是因为经过加工,使它成为这样的。所以木材用墨线量过,再经辅具加工就能取直,刀剑等金属制品在磨刀石上磨过就能变得锋利,君子广泛地学习,而且每天检查反省自己,那么他就会聪明机智,而行为就不会有过错了。

  所以,不登上高山,就不知天多么高;不面临深涧,就不知道地多么厚;不懂得先代帝王的遗教,就不知道学问的博大。干越夷貉之人,刚生下来啼哭的声音是一样的,而长大后风俗习性却不相同,这是教育使之如此。《诗经》上说:“你这个君子啊,不要总是贪图安逸。恭谨对待你的本职,爱好正直的德行。神明听到这一切,就会赐给你洪福祥瑞。”精神修养没有比受道德薰陶感染更大了,福分没有比无灾无祸更长远了。

  我曾经整天思索,(却)不如片刻学到的知识(多);我曾经踮起脚远望,(却)不如登到高处看得广阔。登到高处招手,胳膊没有比原来加长,可是别人在远处也看见;顺着风呼叫,声音没有比原来加大,可是听的人听得很清楚。借助车马的人,并不是脚走得快,却可以行千里,借助舟船的人,并不是能游水,却可以横渡江河。君子的本性跟一般人没什么不同,(只是君子)善于借助外物罢了。

  南方有一种叫“蒙鸠”的鸟,用羽毛作窝,还用毛发把窝编结起来,把窝系在嫩芦苇的花穗上,风一吹苇穗折断,鸟窝就坠落了,鸟蛋全部摔烂。不是窝没编好,而是不该系在芦苇上面。西方有种叫“射干”的草,只有四寸高,却能俯瞰百里之遥,不是草能长高,而是因为它长在了高山之巅。蓬草长在麻地里,不用扶持也能挺立住,白沙混进了黑土里,就再不能变白了,兰槐的根叫香艾,一但浸入臭水里,君子下人都会避之不及,不是艾本身不香,而是被浸泡臭了。所以君子居住要选择好的环境,交友要选择有道德的人,才能够防微杜渐保其中庸正直。

  事情的发生都是有起因的,荣辱的降临也与德行相应。肉腐了生蛆,鱼枯死了生虫,懈怠疏忽忘记了做人准则就会招祸。太坚硬物体易断裂,太柔弱了又易被束缚,与人不善会惹来怨恨,干柴易燃,低洼易湿,草木丛生,野兽成群,万物皆以类聚。所以靶子设置好了就会射来弓箭,树长成了森林就会引来斧头砍伐,树林繁茂荫凉众鸟就会来投宿,醋变酸了就会惹来蚊虫,所以言语可能招祸,行为可能受辱,君子为人处世不能不保持谨慎。

  堆积土石成了高山,风雨就从这里兴起了;汇积水流成为深渊,蛟龙就从这儿产生了;积累善行养成高尚的品德,自然会心智澄明,也就具有了圣人的精神境界。所以不积累一步半步的行程,就没有办法达到千里之远;不积累细小的流水,就没有办法汇成江河大海。骏马一跨跃,也不足十步远;劣马拉车走十天,(也能走得很远,)它的成功就在于不停地走。(如果)刻几下就停下来了,(那么)腐烂的木头也刻不断。(如果)不停地刻下去,(那么)金石也能雕刻成功。蚯蚓没有锐利的爪子和牙齿,强健的筋骨,却能向上吃到泥土,向下可以喝到泉水,这是由于它用心专一啊。螃蟹有六条腿,两个蟹钳,(但是)如果没有蛇、鳝的洞穴它就无处存身,这是因为它用心浮躁啊。

  因此没有刻苦钻研的心志,学习上就不会有显著成绩;没有埋头苦干的实践,事业上就不会有巨大成就。在歧路上行走达不到目的地,同时事奉两个君主的人,两方都不会容忍他。眼睛不能同时看两样东西而看明白,耳朵不能同时听两种声音而听清楚。螣蛇没有脚但能飞,鼫鼠有五种本领却还是没有办法。《诗》上说:“布谷鸟筑巢在桑树上,它的幼鸟儿有七只。善良的君子们,行为要专一不偏邪。行为专一不偏邪,意志才会如磐石坚。”所以君子的意志坚定专一。

  古有瓠巴弹瑟,水中鱼儿也浮出水面倾听,伯牙弹琴,拉车的马会停食仰头而听。所以声音不会因为微弱而不被听见,行为不会因为隐秘而不被发现。宝玉埋在深山,草木就会很润泽,珍珠掉进深渊,崖岸就不会干枯。行善可以积累,哪有积善成德而不被广为传诵的呢?

  学习究竟应从何入手又从何结束呢?答:按其途径而言,应该从诵读《诗》、《书》等经典入手到《礼经》结束;就其意义而言,则从做书生入手到成为圣人结束。真诚力行,这样长期积累,必能深入体会到其中的乐趣,学到死方能后已。所以学习的教程虽有尽头,但进取之愿望却不可以有片刻的懈怠。毕生好学才成其为人,反之又与禽兽何异?《尚书》是政事的记录;《诗经》是心声之归结;《礼经》是法制的前提、各种条例的总纲,所以要学到《礼经》才算结束,才算达到了道德之顶峰。《礼经》敬重礼仪,《乐经》讲述中和之声,《诗经》《尚书》博大广阔,《春秋》微言大义,它们已经将天地间的大学问都囊括其中了。

  君子学习,是听在耳里,记在心里,表现在威仪的举止和符合礼仪的行动上。一举一动,哪怕是极细微的言行,都可以垂范于人。小人学习是从耳听从嘴出,相距不过四寸而已,怎么能够完美他的七尺之躯呢?古人学习是自身道德修养的需求,现在的人学习则只是为了炫耀于人。君子学习是为了完善自我,小人学习是为了卖弄和哗众取宠,将学问当作家禽、小牛之类的礼物去讨人好评。所以,没人求教你而去教导别人叫做浮躁;问一答二的叫罗嗦;浮躁罗嗦都是不对的,君子答问应象空谷回音一般,不多不少、恰到好处。

  学习没有比亲近良师更便捷的了。《礼经》、《乐经》有法度但嫌疏略;《诗经》、《尚书》古朴但不切近现实;《春秋》隐微但不够周详;仿效良师学习君子的学问,既崇高又全面,还可以通达世理。所以说学习没有比亲近良师更便捷的了。

  崇敬良师是最便捷的学习途径,其次就是崇尚礼仪了。若上不崇师,下不尚礼,仅读些杂书,解释一下《诗经》《尚书》之类,那么尽其一生也不过是一介浅陋的书生而已。要穷究圣人的智慧,寻求仁义的根本,从礼法入手才是能够融会贯通的捷径。就像弯曲五指提起皮袍的领子,向下一顿,毛就完全顺了。如果不究礼法,仅凭《诗经》《尚书》去立身行事,就如同用手指测量河水,用戈舂黍米,用锥子到饭壶里取东西吃一样,是办不到的。所以,尊崇礼仪,即使对学问不能透彻明了,不失为有道德有修养之士;不尚礼仪,即使明察善辩,也不过是身心散漫无真实修养的浅陋儒生而已。

  如果有人前来向你请教不合礼法之事,不要回答;前来诉说不合礼法之事,不要去追问;在你面前谈论不合礼法之事,不要去参与;态度野蛮好争意气的,别与他争辩。所以,一定要是合乎礼义之道的,才给予接待;不合乎礼义之道的,就回避他;因此,对于恭敬有礼的人,才可与之谈道的宗旨;对于言辞和顺的人,才可与之谈道的内容;态度诚恳的,才可与之论及道的精深义蕴。所以,跟不可与之交谈的交谈,那叫做浮躁;跟可与交谈的不谈那叫怠慢;不看对方回应而随便谈话的叫盲目。因此,君子不可浮躁,也不可怠慢,更不可盲目,要谨慎地对待每位前来求教的人。《诗经》说:“不浮躁不怠慢才是天子所赞许的。”说的就是这个道理。

  射出的百支箭中有一支不中靶,就不能算是善射;驾驭车马行千里的路程,只差半步而没能走完,这也不能算是善驾;对伦理规范不能融会贯通、对仁义之道不能坚守如一,当然也不能算是善学。学习本是件很需要专心至致的事情,学一阵又停一阵那是市井中的普通人。好的行为少而坏的行为多,桀、纣、拓就是那样的人。能够全面彻底地把握所学的知识,才算得上是个学者。

  君子知道学得不全不精就不算是完美,所以诵读群书以求融会贯通,用思考和探索去理解,效仿良师益友来实践,去掉自己错误的习惯性情来保持养护。使眼不是正确的就不想看、耳不是正确的就不想听,嘴不是正确的就不想说,心不是正确的就不愿去思虑。等达到完全醉心于学习的理想境地,就如同眼好五色,耳好五声,嘴好五味那样,心里贪图拥有天下一样。如果做到了这般地步,那么,在权利私欲面前就不会有邪念,人多势众也不会屈服的,天下万物都不能动摇信念。活着是如此,到死也不变。这就叫做有德行、有操守。有德行和操守,才能做到坚定不移,有坚定不移然后才有随机应对。能做到坚定不移和随机应对,那就是成熟完美的人了。到那时天显现出它的光明,大地显现出它的广阔,君子的可贵则在于他德行的完美无缺

Learning at Effect Java 43

返回零长度的数组或者集合,而不是null

常态:当数组或者集合为空时,返回一个null

private final List<Cheese> cheesesInStock = …

public Cheese[] getCheeses(){
	if (CheesesInStock.size() == 0 )
		return null;
}

这样做的缺点是:任何调用getCheeses()方法的程序员都必须判断cheeses是不是null,假如某个程序员粗心大意没有判断就会抛出空指针异常(这个好像也是常态,至今我只见过的少部分程序员会判断大部分的输入值)

调用者代码:
	Cheese[] cheeses = shop.getCheeses();
	if( cheeses != null && Arrays.asList(cheeses).contains(Cheese.STILTON))
		…

改进方案:返回一个空数组或者空集合

private final List<Cheese> cheesesInStock = …

private static final Cheese[] EMPTY_CHEESES = new Cheese[0];
public Cheese[] getCheeses(){
	return CheesesInStock.toArray(EMPTY_CHEESES);
}

或者

public List<Cheese> getCheeses(){
	if(cheesesInStock.isEmpty)
		return Collections.emptyList();
	else
		return new ArrayList<Cheese>(cheesesInStock);
}

调用者现在的代码:
	Cheese[] cheeses = shop.getCheeses();
	if(Arrays.asList(cheeses).contains(Cheese.STILTON))
		…
不用再做额外的判断了

Tips:没看这篇文章之前,我写的方法基本上都是返回null,然后调用的地方全都加上判断xxx != null,看完这个以后发现原来我们写的代码可以让别人用的更舒服,只要我们多做一点点^_^

心得:好代码不是写的算法思想多好,只要优化一点点,让别人做的更少一些,就可以让代码思路更清晰,阅读起来迅速理解代码意图。不会给别人带来额外的负担,这样就是好代码