clojure API学习(3) 字符和字符串操作

注:本文基于jdk1.6,clojure1.2

字符串连接str

    严格来说,str并非仅用于连接字符串,实际上针对任何对象,它都能获取到对象的toString()并进行连接。

    这样说起来有些费解,看看实际的例子就明了了:

user> (str "a" \b :c)
"ab:c"
user> (import '(java.util Date))
java.util.Date
user> (str (Date.))
"Fri Dec 09 11:05:29 CST 2011"
user> (str 1 "x" 2 2r101)
"1x25"
user> (apply str ["a" 3 5 0x24])
"a3536"
user> (str 53M "gjj" 'symbol)
"53gjjsymbol"

   如上所示,我们几乎能够采用str来连接任何类型的对象,包含了关键字、符号、字符串、字符、数字等。下面

让我们来看看它的源码:

(defn str
  "With no args, returns the empty string. With one arg x, returns
  x.toString().  (str nil) returns the empty string. With more than
  one arg, returns the concatenation of the str values of the args."
  {:tag String
   :added "1.0"}
  ([] "")
  ([^Object x]
   (if (nil? x) "" (. x (toString))))
  ([x & ys]
     ((fn [^StringBuilder sb more]
          (if more
            (recur (. sb  (append (str (first more)))) (next more))
            (str sb)))
      (new StringBuilder ^String (str x)) ys)))

    说明写得很清楚,如果无参或者参数为nil时,str返回一个空字符串;如果是一个参数,则返回此参数的字符串值

。如果不止一个参数,则把所有参数的字符串值顺次连接起来并返回。

    ([] "")代表无参返回空字符串;

    ([^Object x]
   (if (nil? x) "" (. x (toString))))这是一个参数时,如果为nil返回空字符串,否则则调用参数的toString方法并返

回。这儿我们注意到出现了一个之前没有见过的东西^Object,这是啥玩意儿呢?其实很简单,这是参数的类型声明,

意即传入的x类型声明为Object。这个Object又是从哪儿来的呢?这是因为在clojure中使用java.lang下面的类是无须

import的,所以Object其实是java.lang.Object。

   附注:在clojure1.3中,这一段是 (^String[^Object x]
   (if (nil? x) "" (. x (toString))))这个样子的,多了个^String,我们可以看出,这实际上是返回值的类型声明。

   而多参数的处理部分,毫无疑问暴露出了lisp程序可读性的缺陷,实际上是个(函数 参数 参数)的简单结构,但是众多

的括号相当具备迷惑性。函数的部分是个匿名函数:

(fn [^StringBuilder sb more]
          (if more
            (recur (. sb  (append (str (first more)))) (next more))
            (str sb)))

    这个匿名函数接收两个参数,一个是类型声明为StringBuilder的sb,另一个是more,从函数体来看这应该是个

集合。而函数体是个二元判断,如果集合more不为空,则将more中的第一个元素和sb连接起来,并将这个连接的

结果以及more剩下的元素作为参数进行函数递归,直到more中的元素都被sb连接完毕为止。

    实际上,我们可以看出,这个匿名函数中的递归有两种类型,一种是匿名函数本身的递归;一种是str函数本身的

递归,而且str递归还分别调用了一个参数和两个参数的函数体。

    而(函数 参数 参数)结构中,两个参数分别是(new StringBuilder ^String (str x))和ys,毫无疑问,

(new StringBuilder ^String (str x))构建了一个新的StringBuilder对象,对应匿名函数中的sb,ys则是个集合,

对应匿名函数中的more。

字符串连接打印print-str

    看见上面这个长长的名字大概也明白了,print-str函数的作用就是将参数的字符串值连接起来并进行打印,并将

连接的结果作为返回值。

    调用一下试试:

user> (print-str (Date.) 53 86M 2r111)
"# 53 86M 7"
user> (apply print-str [1 \a 5.0 "b"])
"1 a 5.0 b"

    不过我们注意到,上面执行的情形和一般打印到命令控制台似乎有所不同,让我们来看看源码:

(defn print-str
  "print to a string, returning it"
  {:tag String
   :added "1.0"}
  [& xs]
    (with-out-str
     (apply print xs)))

    结构很简单,接收任意数目的参数,并将其打印出来。不过,with-out-str是什么东西呢?我们看看:

(defmacro with-out-str
  "Evaluates exprs in a context in which *out* is bound to a fresh
  StringWriter.  Returns the string created by any nested printing
  calls."
  {:added "1.0"}
  [& body]
  `(let [s# (new java.io.StringWriter)]
     (binding [*out* s#]
       ~@body
       (str s#))))

    这是一个宏,接收表达式集合body作为参数。它建立了一个java.io.StringWriter类型的临时绑定s,然后将

标准输出的目的改为了s而不是默认的命令控制台。接下来再执行body,在body中的任何打印就会输出到s里面,

最后将s的字符串值作为返回值。关于io的具体操作在之后的文章会讨论到。

    让我们从代码执行的情况来观察一下不同点:

user> (apply print [7 4 \a "z" 58M])
7 4 a z 58M                    ;这是print的副作用(side-effect)
nil                                 ;print的返回值
user> (with-out-str(apply print [7 4 \a "z" 58M]))
"7 4 a z 58M"                 ;返回的字符串,注意空格分隔正是print的风格
user> (class (apply print [7 4 \a "z" 58M]))
7 4 a z 58M                    ;采用了class之后,仍然会打印出来,这正是副作用的典型特征
nil                                 ;(class nil)的返回值
user> (class (with-out-str(apply print [7 4 \a "z" 58M])))
java.lang.String              ;采用了class之后,就没有任何打印信息了
user> (class nil)
nil

字符串连接打印换行println-str

    这个的作用实际上和字符串连接打印是一样的,唯一的差别就在于它会在连接完成的字符串末尾加上一个换行符,

让我们看看执行情况:

user> (println-str  (Date.) 53 86M 2r111)
"# 53 86M 7\n"
user> (apply println-str [1 \a 5.0 "b"])
"1 a 5.0 b\n"

    如上所见,跟print-str的结果基本一致,就多了一个“\n”换行符,让我们看看源码是否也相同:

(defn println-str
  "println to a string, returning it"
  {:tag String
   :added "1.0"}
  [& xs]
    (with-out-str
     (apply println xs)))

    毫无意外一样的结构,这儿就不多做解释了。

pr-str

    pr、print、println和prn的区别在io的文章中会详细评说,这儿我们先看看例子吧:

user> (pr-str (Date.) "nice" "5" 4 2r101 54M)
"# \"nice\" \"5\" 4 5 54M"
user> (apply pr-str [1 \1 "1" 1M 2r1 0x1])
"1 \\1 \"1\" 1M 1 1"

   从例子中我们可以明显地看出pr和print的不同来,字符串的“”和字符的\都被通过转义字符打印出来了。

让我们看看源码:

(defn pr-str
  "pr to a string, returning it"
  {:tag String
   :added "1.0"}
  [& xs]
    (with-out-str
     (apply pr xs)))

   依然是同样的结构。

prn-str

   似乎不用废话了,例子:

user> (prn-str (Date.) "nice" "5" 4 2r101 54M)
"# \"nice\" \"5\" 4 5 54M\n"
user> (apply prn-str [1 \1 "1" 1M 2r1 0x1])
"1 \\1 \"1\" 1M 1 1\n"

    源码:

(defn prn-str
  "prn to a string, returning it"
  {:tag String
   :added "1.0"}
  [& xs]
  (with-out-str
   (apply prn xs)))

string?字符串判断

     判断是否字符串,用法简单:

user> (string? :keyword)
false
user> (string? 54M)
false
user> (string? "")
true

    源码如下:

(def
 ^{:arglists '([x])
   :doc "Return true if x is a String"
   :added "1.0"}
 string? (fn string? [x] (instance? String x)))

    我们看到这里面采用了instance?函数,那我们接着看看这个instance?函数:

(def
 ^{:arglists '([^Class c x])
   :doc "Evaluates x and tests if it is an instance of the class
    c. Returns true or false"
   :added "1.0"}
 instance? (fn instance? [^Class c x] (. c (isInstance x))))

    这样我们就清晰了,在string?函数中,instance?函数接收了两个参数,一个是java.lang.String,一个是

需要判断的对象x,然后调用String Class的isInstance方法来判断x是否为字符串类型:

String.class.isInstance(x);
 

char?字符判断

    判断是否字符:

user> (char? \c)
true
user> (char? 54M)
false
user> (char? (first [\a 1 "c"]))
true
user> (char? (last "abcd"))
true

    看看源码:

(def
 ^{:arglists '([x])
   :doc "Return true if x is a Character"
   :added "1.0"}
 char? (fn char? [x] (instance? Character x)))

    结构和string?基本一致。

 

下面的api都是clojure.string下面的,需要引入(以下都是基于1.3版本的api)

user> (require 'clojure.string)
nil

blank?判断字符串是否为空

    用来判断字符串是否为空,看看用法:

user> (clojure.string/blank? nil)
true
user> (clojure.string/blank? "")
true
user> (clojure.string/blank? "   ")
true
user> (clojure.string/blank? " , ,")
false
user> (apply clojure.string/blank? ["1"])
false

    从中我们可以看出,nil、空字符串以及只包含空格的字符串都会被认定为空。让我们看看源码:

(defn blank?
  "True if s is nil, empty, or contains only whitespace."
  {:added "1.2"}
  [^CharSequence s]
  (if s
    (loop [index (int 0)]
      (if (= (.length s) index)
        true
        (if (Character/isWhitespace (.charAt s index))
          (recur (inc index))
          false)))
    true))

    说明性文字很清晰,nil、空字符串以及只包含空格的字符串都会被认定为空。1.2版本开始有这个方法。

    字符串被声明为了java.lang.CharSequence类型,首先判断s是否为nil,如果是nil,则直接返回true;

然后遍历s,任意字符不是空格则返回false。这个递归是很有意思的,咋一看,好像是通过尾递归遍历了s

的每个字符来判断是否空格;实际上是仅在首字符为空格时开始递归,并且在遇到非空格字符时结束递归

并返回false。

capitalize

    capitalize函数是将一个字符串的首字母置为大写,其他字母置为小写,看看例子:

user> (clojure.string/capitalize "aBBBB")
"Abbbb"
user> (clojure.string/capitalize "ABCD")
"Abcd"

    然后我们看看源码:

(defn ^String capitalize
  "Converts first character of the string to upper-case, all other
  characters to lower-case."
  {:added "1.2"}
  [^CharSequence s]
  (let [s (.toString s)]
    (if (< (count s) 2)
      (.toUpperCase s)
      (str (.toUpperCase (subs s 0 1))
           (.toLowerCase (subs s 1))))))

    源码中首先把s声明为了java.lang.CharSequence类型,然后再调用它的toString方法把s转换成了String对象

,老实说,我是不太明白为啥要这么做。猜想是因为java.lang.CharSequence是个接口,它有三个实现类分别是

String、StringBuilder和StringBuffer。这样capitalize函数就可以接收这三个类型的参数,然后再转换为String

来处理。处理过程基本没啥说得,如果s的长度小于2,则直接调用s的toUpperCase方法并返回;如果s的长度大于

等于2,取出s的第一个字符转为大写,取出s的其他字符转为小写,然后把它们连接起来并返回。

escape

    escape是个针对字符串中特定字符作条件替换的函数,它接收两个参数,第一个参数是待处理的字符串s,第二个

参数是替换函数cmap:

(escape s cmap)

    cmap接收s的每个字符作为参数,如果cmap返回nil,则s中对应的字符就不被改变;如果cmap返回值不为nil,则

s中对应的字符被替换为cmap的返回值。严格来说,其实并不是s本身被改变,而是escape的返回值和s比较起来的改变

。让我们来看看例子:

user> (clojure.string/escape "abacadae" (fn [ch]
					    (if (= ch \a)
						\v
						nil)))
"vbvcvdve"
user> (clojure.string/escape "abacadae" (fn [ch]
					    (if (= ch \g)
						\v
						nil)))
"abacadae"
user> (clojure.string/escape "abacadae" (fn [ch]
					    (if (= ch \a)
						"*"
						nil)))
"*b*c*d*e"

    看例子应该比较清晰了,让我们看看源码:

(defn ^String escape
  "Return a new string, using cmap to escape each character ch
   from s as follows:
   
   If (cmap ch) is nil, append ch to the new string.
   If (cmap ch) is non-nil, append (str (cmap ch)) instead."
  {:added "1.2"}
  [^CharSequence s cmap]
  (loop [index (int 0)
         buffer (StringBuilder. (.length s))]
    (if (= (.length s) index)
      (.toString buffer)
      (let [ch (.charAt s index)]
        (if-let [replacement (cmap ch)]
          (.append buffer replacement)
          (.append buffer ch))
        (recur (inc index) buffer)))))

    源码应该相当清晰了,创建一个StringBuilder对象buffer,遍历s,取出每个字符ch,将ch传入cmap,如果cmap

的返回值是nil,调用buffer的append方法把ch添加到buffer;如果cmap的返回值不为nil,调用buffer的append方法

把cmap的返回值添加到buffer;s遍历完成,调用buffer的toString方法返回字符串。

    这儿我们发现一个以前没见过的东西:if-let,看起来作用似乎就等于if+let,那实际上是怎样的呢,让我们来

看看源码:

(defmacro if-let
  "bindings => binding-form test

  If test is true, evaluates then with binding-form bound to the value of 
  test, if not, yields else"
  {:added "1.0"}
  ([bindings then]
   `(if-let ~bindings ~then nil))
  ([bindings then else & oldform]
   (assert-args if-let
          ;判断绑定是否为vector以及else后面没有其他方法体,
          ;判断结果和后面的说明性文字组成一个boolen-string对
          ;传递给assert-args      
          (and (vector? bindings) (nil? oldform)) "a vector for its binding"  
           ;判断绑定中仅一个键值对
          (= 2 (count bindings)) "exactly 2 forms in binding vector")    
   (let [form (bindings 0) tst (bindings 1)]
     `(let [temp# ~tst]
        (if temp#
          (let [~form temp#]
            ~then)
          ~else)))))

    哗,好大一个macro!不过整体结构我们已经很熟悉了,首先是说明性文字,绑定结构代表了两部分,一是

if的test,二是let的binding;如果test为true,则用绑定的值来执行then部分;如果test为false,则执行else

部分。

    然后是宏的主体,分成两大部分,两参数部分和三参数部分。其中两参数部分直接加了个nil参数调用的三

参数部分。然后我们又看到一个奇特的东西assert-args。我第一反应这玩意儿应该属于test命名空间,事实证明

我是错的。这是clojre.core里面的private宏:

(defmacro ^{:private true} assert-args [fnname & pairs]   
     ;pairs是数量不定的boolen-string对
  `(do (when-not ~(first pairs)           ;如果参数合法(true),则不抛异常
         (throw (IllegalArgumentException.  ;参数不合法(false),抛出异常,并封装信息
                  ~(str fnname " requires " (second pairs)))))
     ~(let [more (nnext pairs)]            ;如果还有更多的boolen-string对
        (when more
          (list* `assert-args fnname more)))))  ;通过list*来递归

    这个宏从命名和方法体来看都很明显,是判断某个函数的参数是否合法。里面有两个地方是之前我们没有见过的,

一是nnext,其实这就相当于连续执行两次next;二是list*,这个就更简单了,就是把后面的所有参数组装成一个

list,用代码来说明一下:

user> (nnext [1 2 3 4 5])
(3 4 5)
user> (nnext '(\a \b \c \d \e \f))
(\c \d \e \f)
user> (list* '+ 1 2 3 (seq [4 5 6]))
(+ 1 2 3 4 5 6)

    需要注意的地方是list*总会把最后一个参数作为seq来解析。

join

    join函数是将一个集合中的所有元素连接为一个字符串,同时也可以在所有元素中添加上连接符,用法:

(join coll)
(join separator coll)

    示例代码如下:

user> (clojure.string/join [\a 1 "c"])
"a1c"
user> (clojure.string/join 1  [34M 1 "c" :key])
"34111c1:key"
user> (import '(java.util Date))
java.util.Date
user> (clojure.string/join \*  [34M (Date.) "c" :key])
"34*Thu Dec 15 17:36:39 CST 2011*c*:key"

   让我们看看源码:

(defn ^String join
  "Returns a string of all elements in coll, separated by
   an optional separator.  Like Perl's join."
  {:added "1.2"}
  ([coll]
     (apply str coll))
  ([separator [x & more]]
     (loop [sb (StringBuilder. (str x))
            more more
            sep (str separator)]
       (if more
         (recur (-> sb (.append sep) (.append (str (first more))))
                (next more)
                sep)
         (str sb)))))

    这段代码的逻辑非常清晰,需要注意的只有join的返回值被声明为^String,还有就是->这个东西,其实

它的含义很简单,就是连续调用,比如:

user> (-> (StringBuilder. "a") (.append "b") (.append "c"))
#
user> (-> (StringBuilder. "a") (.append "b") (.toString))
"ab"

    等同于:

StringBuilder sb = new StringBuilder("a");
sb.append("b").append("c");

    严格来说,->的作用是把第一个表达式的结果放到第二个表达式的第一参数位来执行,然后把第二表达式的

结果放到第三表达式的第一参数位来执行。(函数 第一参数位 第二参数位)

user> (-> (StringBuilder. "a") (.append "b"))
#
user> (.append (StringBuilder. "a") "b")
#
user> (-> (* 4 4) (/ 2) (/ 2))
4
user> (/ (/ (* 4 4) 2) 2)
4

lower-case

    看见名字大概就知道了,这个是用于把字符串转换为小写:

user> (clojure.string/lower-case "ABCDEFG")
"abcdefg"
user> (clojure.string/lower-case "aBcDeFg")
"abcdefg"

    看看源码:

(defn ^String lower-case
  "Converts string to all lower-case."
  {:added "1.2"}
  [^CharSequence s]
  (.. s toString toLowerCase))

   这儿我们注意到..这个东西,其实它也是链式调用,等同于s.toString().toLowerCase.

replace

    这个看名字也明白了,可以替换字符串中的任意字符,看看例子:

user> (clojure.string/replace "abacadae" \a \*)
"*b*c*d*e"
user> (clojure.string/replace "abacadae" "a" "-")
"-b-c-d-e"
user> (import '(java.util.regex Pattern))
java.util.regex.Pattern
user> (clojure.string/replace "abacadae" (Pattern/compile "[^abc]") "-")
"abaca-a-"
user> (clojure.string/replace "abacadae" (Pattern/compile "[^abc]")
			      (fn [ch]
				  (str ch "*")))
"abacad*ae*"

    如上可见,replace接受3个参数:一是被源字符串s,二是替换的条件match,三是替换的具体对象replacement

。其中match/replacement可以是char/char,也可以是string/string,还可以是Pattern/fn或者Pattern/string。

嗯,为了防止有同学不明白。这儿注明一下,Pattern初始化的参数[^abc]是个正则判定,意思是除了abc之外的

所有元素。Pattern的api

    让我们看看源码:

user> (source clojure.string/replace)
(defn ^String replace
  "Replaces all instance of match with replacement in s.

   match/replacement can be:

   string / string
   char / char
   pattern / (string or function of match).

   See also replace-first."
  {:added "1.2"}
  [^CharSequence s match replacement]
  (let [s (.toString s)]
    (cond 
     (instance? Character match) (.replace s ^Character match ^Character replacement)
     (instance? CharSequence match) (.replace s ^CharSequence match ^CharSequence replacement)
     (instance? Pattern match) (if (instance? CharSequence replacement)
                                 (.replaceAll (re-matcher ^Pattern match s)
                                              (.toString ^CharSequence replacement))
                                 (replace-by s match replacement))
     :else (throw (IllegalArgumentException. (str "Invalid match arg: " match))))))

    这简直是一篇短文,不过结构还是非常清晰的,针对三种不同的参数类型分别进行处理;具体的处理

基本上都是调用了java方法。其中有两个东西比较陌生,一是re-matcher,这个比较简单,就是产生一个

java.util.regex.Matcher的实例,源码如下:

(defn re-matcher
  "Returns an instance of java.util.regex.Matcher, for use, e.g. in
  re-find."
  {:tag java.util.regex.Matcher
   :added "1.0"}
  [^java.util.regex.Pattern re s]
    (. re (matcher s)))

    对Pattern的API熟悉的同学一看就明白了,其实等同于:

 Pattern p = Pattern.compile("a*b");
 Matcher m = p.matcher("aaaaab");

    这个样子。二是replace-by,这个东西又是一个private函数,位于clojure.string中:

(defn- replace-by
  [^CharSequence s re f]
  (let [m (re-matcher re s)]
    (let [buffer (StringBuffer. (.length s))]
      (loop []
        (if (.find m)
          (do (.appendReplacement m buffer (f (re-groups m)))
              (recur))
          (do (.appendTail m buffer)
              (.toString buffer)))))))

    其中主要也以调用Matcher的java方法为主,遍历字符串,根据正则表达式替换掉相应的内容。

    顺便说一下,这个东西在1.2之前是位于clojure.contrib.string里面的,可以看看:

user> (require 'clojure.contrib.string)
nil
user> (source clojure.contrib.string/replace-by)
(defn replace-by
  "Replaces all matches of re in s with the result of 
  (f (re-groups the-match))."
  {:deprecated "1.2"}
  [re f ^String s]
  (let [m (re-matcher re s)]
    (let [buffer (StringBuffer. (.length s))]
      (loop []
        (if (.find m)
          (do (.appendReplacement m buffer (f (re-groups m)))
              (recur))
          (do (.appendTail m buffer)
              (.toString buffer)))))))

    结构基本是一致的。

replace-first

    这个和replace不同的地方在于它只替换第一个符合条件的元素,看看例子:

user> (use '[clojure.string :only (replace-first)])
nil
user> (replace-first "adcdedg" \d \y)
"aycdedg"
user> (replace-first "adcdadg" "ad" "ooo")
"ooocdadg"
user> (replace-first "adcdadg" #"[^ad]" "ooo")
"adooodadg"
user> (replace-first "adcdadg" #"[^ad]"
		     (fn [re]
			 (str "*" re "*")))
"ad*c*dadg"

    看看源码:

user> (source replace-first)
(defn ^String replace-first
  "Replaces the first instance of match with replacement in s.

   match/replacement can be:

   char / char
   string / string
   pattern / (string or function of match).

   See also replace-all."
  {:added "1.2"}
  [^CharSequence s match replacement]
  (let [s (.toString s)]
    (cond
     (instance? Character match)
     (replace-first-char s match replacement)
     (instance? CharSequence match)
     (.replaceFirst s (Pattern/quote (.toString ^CharSequence match))
                    (.toString ^CharSequence replacement))
     (instance? Pattern match)
     (if (instance? CharSequence replacement)
       (.replaceFirst (re-matcher ^Pattern match s)
                      (.toString ^CharSequence replacement))
       (replace-first-by s match replacement))
     :else (throw (IllegalArgumentException. (str "Invalid match arg: " match))))))

    结构和replace一致,包含了两个private函数:

(defn- replace-first-by
  [^CharSequence s ^Pattern re f]
  (let [m (re-matcher re s)]
    (let [buffer (StringBuffer. (.length s))]
      (if (.find m)
        (let [rep (f (re-groups m))]
          (.appendReplacement m buffer rep)
          (.appendTail m buffer)
          (str buffer))))))

(defn- replace-first-char
  [^CharSequence s ^Character match replace]
  (let [s (.toString s)
        i (.indexOf s (int match))]
    (if (= -1 i)
      s
      (str (subs s 0 i) replace (subs s (inc i))))))

    功能都不复杂,在此就不详解了。

reverse

    反转字符串的顺序:

user> (clojure.string/reverse "123456a")
"a654321"

    看看源码:

user> (source clojure.string/reverse)
(defn ^String reverse
  "Returns s with its characters reversed."
  {:added "1.2"}
  [^CharSequence s]
  (.toString (.reverse (StringBuilder. s))))

    直接调用的java方法。

split

    根据正则表达式分割字符串:

user> (clojure.string/split "a-b-c-d-e-f" #"-")
["a" "b" "c" "d" "e" "f"]
user> (clojure.string/split "a-b-c-d-e-f" #"-" -1)
["a" "b" "c" "d" "e" "f"]
user> (clojure.string/split "a-b-c-d-e-f" #"-" 3)
["a" "b" "c-d-e-f"]
user> (class (clojure.string/split "a-b-c-d-e-f" #"-" 3))
clojure.lang.PersistentVector

    需要注意的是,split返回值并非字符串,而是vector。

    我猜测源码又是调用了java方法,来看看吧:

user> (source clojure.string/split)
(defn split
  "Splits string on a regular expression.  Optional argument limit is
  the maximum number of splits. Not lazy. Returns vector of the splits."
  {:added "1.2"}
  ([^CharSequence s ^Pattern re]
     (LazilyPersistentVector/createOwning (.split re s)))
  ([ ^CharSequence s ^Pattern re limit]
     (LazilyPersistentVector/createOwning (.split re s limit))))

    嗯,不出所料,但是对结果进行了封装,我们会在集合篇中讲到LazilyPersistentVector这个东西。

split-lines

    根据换行符来分割字符串:

user> (clojure.string/split-lines "a big \n pig")
["a big " " pig"]
user> (clojure.string/split-lines "a big pig")
["a big pig"]
user> (clojure.string/split-lines "a big \r pig")
["a big \r pig"]
user> (clojure.string/split-lines "a big \r\n pig")
["a big " " pig"]

    看看源码:

user> (source clojure.string/split-lines)
(defn split-lines
  "Splits s on \\n or \\r\\n."
  {:added "1.2"}
  [^CharSequence s]
  (split s #"\r?\n"))

    其实还是调用的split函数。

trim

    去除字符串开始和末尾的空格:

user> (clojure.string/trim " a b c   d    e ")
"a b c   d    e"
user> (clojure.string/trim " nice to meet you         ")
"nice to meet you"

    中间的空格被忽视了,看看源码:

user> (source clojure.string/trim)
(defn ^String trim
  "Removes whitespace from both ends of string."
  {:added "1.2"}
  [^CharSequence s]
  (.. s toString trim))

    直接调用了java方法。

trim-newline

   去除字符串中末尾的所有换行符,注意,有空格分隔的是去不掉的:

user> (clojure.string/trim-newline "\n a \n b \n c \n d \n")
"\n a \n b \n c \n d "
user> (clojure.string/trim-newline "\n a \n b \n c d")
"\n a \n b \n c d"
user> (clojure.string/trim-newline "\n a \n b \n c d \n \r")
"\n a \n b \n c d \n "
user> (clojure.string/trim-newline "\n a \n b \n c d \n \n")
"\n a \n b \n c d \n "
user> (clojure.string/trim-newline "\n a \n b \n c d \n\n\r")
"\n a \n b \n c d "

    看看源码:

user> (source clojure.string/trim-newline)
(defn ^String trim-newline
  "Removes all trailing newline \\n or return \\r characters from
  string.  Similar to Perl's chomp."
  {:added "1.2"}
  [^CharSequence s]
  (loop [index (.length s)]
    (if (zero? index)
      ""
      (let [ch (.charAt s (dec index))]
        (if (or (= ch \newline) (= ch \return))
          (recur (dec index))
          (.. s (subSequence 0 index) toString))))))

     一个索引递减的递归,找到末尾所有换行符之前的索引,分割字符串并返回。

triml

    仅去除字符串开端的空格:

user> (clojure.string/triml "   a b c   ")
"a b c   "

    看看源码,应该比trim要复杂一点:

user> (source clojure.string/triml)
(defn ^String triml
  "Removes whitespace from the left side of string."
  {:added "1.2"}
  [^CharSequence s]
  (loop [index (int 0)]
    (if (= (.length s) index)
      ""
      (if (Character/isWhitespace (.charAt s index))
        (recur (inc index))
        (.. s (subSequence index (.length s)) toString)))))

    果然,因为没有现成的java方法可调用了。逻辑很简单,索引递增的递归,找到非空格字符则根据这个字符

的索引分割字符串并返回。

trimr

   仅去除字符串末端的空格:

user> (clojure.string/trimr "   a b c   ")
"   a b c"

    这种方法真的有必要吗?我猜源码跟triml差不多,只不过是索引递减的递归,来看看:

(defn ^String trimr
  "Removes whitespace from the right side of string."
  {:added "1.2"}
  [^CharSequence s]
  (loop [index (.length s)]
    (if (zero? index)
      ""
      (if (Character/isWhitespace (.charAt s (dec index)))
        (recur (dec index))
        (.. s (subSequence 0 index) toString)))))

   料中了。

upper-case

   将字符串转为大写:

user> (clojure.string/upper-case "a1b2cdefz")
"A1B2CDEFZ"

    多半又是直接调用了java方法:

user> (source clojure.string/upper-case)
(defn ^String upper-case
  "Converts string to all upper-case."
  {:added "1.2"}
  [^CharSequence s]
  (.. s toString toUpperCase))
 

 

 

你可能感兴趣的:(java,Clojure,原创)