《Go语言圣经》学习笔记 第五章函数

《Go语言圣经》学习笔记 第五章 函数


目录

  1. 函数声明
  2. 递归
  3. 多返回值
  4. 匿名函数
  5. 可变参数
  6. Deferred函数
  7. Panic异常
  8. Recover捕获异常

注:学习《Go语言圣经》笔记,PDF点击下载,建议看书。
Go语言小白学习笔记,书上的内容照搬,大佬看了勿喷,以后熟悉了会总结成自己的读书笔记。


  1. 函数可以让我们将一个语句序列打包为一个单元, 然后可以从程序中其它地方多次调用。 函数的机制可以让我们将一个大的工作分解为小的任务, 这样的小任务可以让不同程序员在不同时间、 不同地方独立完成。 一个函数同时对用户隐藏了其实现细节。 由于这些因素, 对于任何编程语言来说, 函数都是一个至关重要的部分。
  2. 我们已经见过许多函数了。 现在, 让我们多花一点时间来彻底地讨论函数特性。 本章的运行示例是一个网络蜘蛛, 也就是web搜索引擎中负责抓取网页部分的组件, 它们根据抓取网页中的链接继续抓取链接指向的页面。 一个网络蜘蛛的例子给我们足够的机会去探索递归函数、匿名函数、 错误处理和函数其它的很多特性。

1. 函数声明

  1. 函数声明包括函数名、 形式参数列表、 返回值列表( 可省略) 以及函数体。
    在这里插入图片描述
  2. 形式参数列表描述了函数的参数名以及参数类型。 这些参数作为局部变量, 其值由参数调用者提供。 返回值列表描述了函数返回值的变量名以及类型。 如果函数返回一个无名变量或者没有返回值, 返回值列表的括号是可以省略的。 如果一个函数声明不包括返回值列表, 那么函数体执行完毕后, 不会返回任何值。 在hypot函数中
    《Go语言圣经》学习笔记 第五章函数_第1张图片
  3. x和y是形参名,3和4是调用时的传入的实数, 函数返回了一个float64类型的值。 返回值也可以像形式参数一样被命名。 在这种情况下, 每个返回值被声明成一个局部变量, 并根据该返回值的类型, 将其初始化为0。 如果一个函数在声明时, 包含返回值列表, 该函数必须以 return语句结尾, 除非函数明显无法运行到结尾处。 例如函数在结尾时调用了panic异常或函数中存在无限循环。
  4. 正如hypot一样, 如果一组形参或返回值有相同的类型, 我们不必为每个形参都写出参数类型。 下面2个声明是等价的:
    在这里插入图片描述
  5. 下面, 我们给出4种方法声明拥有2个int型参数和1个int型返回值的函数.blank identifier(译者注: 即下文的_符号)可以强调某个参数未被使用。
    《Go语言圣经》学习笔记 第五章函数_第2张图片
  6. 函数的类型被称为函数的标识符。 如果两个函数形式参数列表和返回值列表中的变量类型一一对应, 那么这两个函数被认为有相同的类型和标识符。 形参和返回值的变量名不影响函数标识符也不影响它们是否可以以省略参数类型的形式表示。
  7. 每一次函数调用都必须按照声明顺序为所有参数提供实参( 参数值) 。 在函数调用时, Go语言没有默认参数值, 也没有任何方法可以通过参数名指定形参, 因此形参和返回值的变量名对于函数调用者而言没有意义。
  8. 在函数体中, 函数的形参作为局部变量, 被初始化为调用者提供的值。 函数的形参和有名返回值作为函数最外层的局部变量, 被存储在相同的词法块中。
  9. 实参通过值的方式传递, 因此函数的形参是实参的拷贝。 对形参进行修改不会影响实参。 但是, 如果实参包括引用类型, 如指针, slice(切片)、 map、 function、 channel等类型, 实参可能会由于函数的简介引用被修改。
  10. 你可能会偶尔遇到没有函数体的函数声明, 这表示该函数不是以Go实现的。 这样的声明定义了函数标识符
    在这里插入图片描述

2. 递归

  1. 函数可以是递归的, 这意味着函数可以直接或间接的调用自身。 对许多问题而言, 递归是一种强有力的技术, 例如处理递归的数据结构。 在4.4节, 我们通过遍历二叉树来实现简单的插入排序, 在本章节, 我们再次使用它来处理HTML文件。

  2. 下文的示例代码使用了非标准包 golang.org/x/net/html , 解析HTML。 golang.org/x/… 目录下存储了一些由Go团队设计、 维护, 对网络编程、 国际化文件处理、 移动平台、 图像处理、 加密解密、 开发者工具提供支持的扩展包。 未将这些扩展包加入到标准库原因有二, 一是部分包仍在开发中, 二是对大多数Go语言的开发者而言, 扩展包提供的功能很少被使用。

  3. 例子中调用golang.org/x/net/html的部分api如下所示。 html.Parse函数读入一组bytes.解析后, 返回html.node类型的HTML页面树状结构根节点。 HTML拥有很多类型的结点如text( 文本) ,commnets( 注释) 类型, 在下面的例子中, 我们 只关注< name key=‘value’ >形式的结点。

  4. golang.org/x/net/html
    《Go语言圣经》学习笔记 第五章函数_第3张图片

  5. main函数解析HTML标准输入, 通过递归函数visit获得links( 链接) , 并打印出这些links:
    《Go语言圣经》学习笔记 第五章函数_第4张图片

  6. visit函数遍历HTML的节点树, 从每一个anchor元素的href属性获得link,将这些links存入字符串数组中, 并返回这个字符串数组。
    《Go语言圣经》学习笔记 第五章函数_第5张图片

  7. 为了遍历结点n的所有后代结点, 每次遇到n的孩子结点时, visit递归的调用自身。 这些孩子结点存放在FirstChild链表中。

  8. 让我们以Go的主页( golang.org) 作为目标, 运行findlinks。 我们以fetch( 1.5章) 的输出作为findlinks的输入。 下面的输出做了简化处理。
    《Go语言圣经》学习笔记 第五章函数_第6张图片

  9. 注意在页面中出现的链接格式, 在之后我们会介绍如何将这些链接, 根据根路径(https://golang.org ) 生成可以直接访问的url。

  10. 在函数outline中, 我们通过递归的方式遍历整个HTML结点树, 并输出树的结构。 在outline内部, 每遇到一个HTML元素标签, 就将其入栈, 并输出。

  11. gopl.io/ch5/outline
    《Go语言圣经》学习笔记 第五章函数_第7张图片

  12. 有一点值得注意: outline有入栈操作, 但没有相对应的出栈操作。 当outline调用自身时, 被调用者接收的是stack的拷贝。 被调用者的入栈操作, 修改的是stack的拷贝, 而不是调用者的stack,因对当函数返回时,调用者的stack并未被修改。

  13. 下面是 https://golang.org 页面的简要结构:
    《Go语言圣经》学习笔记 第五章函数_第8张图片

  14. 正如你在上面实验中所见, 大部分HTML页面只需几层递归就能被处理, 但仍然有些页面需要深层次的递归。

  15. 大部分编程语言使用固定大小的函数调用栈, 常见的大小从64KB到2MB不等。 固定大小栈会限制递归的深度, 当你用递归处理大量数据时, 需要避免栈溢出; 除此之外, 还会导致安全性问题。 与相反,Go语言使用可变栈, 栈的大小按需增加(初始时很小)。 这使得我们使用递归时不必考虑溢出和安全问题。


3. 多返回值

  1. 在Go中, 一个函数可以返回多个值。 我们已经在之前例子中看到, 许多标准库中的函数返回2个值, 一个是期望得到的返回值, 另一个是函数出错时的错误信息。 下面的例子会展示如何编写多返回值的函数。

  2. 下面的程序是findlinks的改进版本。 修改后的findlinks可以自己发起HTTP请求, 这样我们就不必再运行fetch。 因为HTTP请求和解析操作可能会失败, 因此findlinks声明了2个返回值: 链接列表和错误信息。 一般而言, HTML的解析器可以处理HTML页面的错误结点, 构造出HTML页面结构, 所以解析HTML很少失败。 这意味着如果findlinks函数失败了, 很可能是由于I/O的错误导致的。

  3. gopl.io/ch5/findlinks2

    package main
    
    import (
    	"fmt"
    	"html"
    	"net/http"
    	"os"
    )
    
    func main() {
    	for _, url := range os.Args[1:] {
    		links, err := findLinks(url)
    		if err != nil {
    			fmt.Fprintf(os.Stderr, "findlinks2: %v\n", err)
    			continue
    		}
    		for _, link := range links {
    			fmt.Println(link)
    		}
    	}
    }
    
    // findLinks performs an HTTP GET request for url, parses the
    // response as HTML, and extracts and returns the links.
    
    func findLinks(url string) ([]string, error) {
    	resp, err := http.Get(url)
    	if err != nil {
    		return nil, err
    	}
    	if resp.StatusCode != http.StatusOK {
    		resp.Body.Close()
    		return nil, fmt.Errorf("getting %s: %s", url, resp.Status)
    	}
    	doc, err := html.Parse(resp.Body)
    	resp.Body.Close()
    	if err != nil {
    		return nil, fmt.Errorf("parsing %s as HTML: %v", url, err)
    	}
    	return visit(nil, doc), nil
    }
    
    
  4. 在findlinks中, 有4处return语句, 每一处return都返回了一组值。 前三处return, 将http和html包中的错误信息传递给findlinks的调用者。 第一处return直接返回错误信息, 其他两处通过fmt.Errorf( §7.8) 输出详细的错误信息。 如果findlinks成功结束, 最后的return语句将一组解析获得的连接返回给用户。

  5. 在finallinks中, 我们必须确保resp.Body被关闭, 释放网络资源。 虽然Go的垃圾回收机制会回收不被使用的内存, 但是这不包括操作系统层面的资源, 比如打开的文件、 网络连接。 因此我们必须显式的释放这些资源。

  6. 调用多返回值函数时, 返回给调用者的是一组值, 调用者必须显式的将这些值分配给变量:
    在这里插入图片描述

  7. 如果某个值不被使用, 可以将其分配给blank identifier:
    在这里插入图片描述

  8. 一个函数内部可以将另一个有多返回值的函数作为返回值, 下面的例子展示了与findLinks有相同功能的函数, 两者的区别在于下面的例子先输出参数:
    《Go语言圣经》学习笔记 第五章函数_第9张图片

  9. 当你调用接受多参数的函数时, 可以将一个返回多参数的函数作为该函数的参数。 虽然这很少出现在实际生产代码中, 但这个特性在debug时很方便, 我们只需要一条语句就可以输出所有的返回值。 下面的代码是等价的:
    在这里插入图片描述

  10. 准确的变量名可以传达函数返回值的含义。 尤其在返回值的类型都相同时, 就像下面这样:
    《Go语言圣经》学习笔记 第五章函数_第10张图片

  11. 虽然良好的命名很重要, 但你也不必为每一个返回值都取一个适当的名字。 比如, 按照惯例, 函数的最后一个bool类型的返回值表示函数是否运行成功, error类型的返回值代表函数的错误信息, 对于这些类似的惯例, 我们不必思考合适的命名, 它们都无需解释。

  12. 如果一个函数将所有的返回值都显示的变量名, 那么该函数的return语句可以省略操作数。 这称之为bare return。
    《Go语言圣经》学习笔记 第五章函数_第11张图片

  13. 按照返回值列表的次序, 返回所有的返回值, 在上面的例子中, 每一个return语句等价于:
    在这里插入图片描述

  14. 当一个函数有多处return语句以及许多返回值时, bare return 可以减少代码的重复, 但是使得代码难以被理解。 举个例子, 如果你没有仔细的审查代码, 很难发现前2处return等价于return 0,0,err( Go会将返回值 words和images在函数体的开始处, 根据它们的类型, 将其初始化为0) , 最后一处return等价于 return words, image, nil。 基于以上原因, 不宜过度使用bare return。


4. 错误

  1. 在Go中有一部分函数总是能成功的运行。 比如strings.Contains和strconv.FormatBool函数,对各种可能的输入都做了良好的处理, 使得运行时几乎不会失败, 除非遇到灾难性的、 不可预料的情况, 比如运行时的内存溢出。 导致这种错误的原因很复杂, 难以处理, 从错误中恢复的可能性也很低。
  2. 还有一部分函数只要输入的参数满足一定条件, 也能保证运行成功。 比如time.Date函数, 该函数将年月日等参数构造成time.Time对象, 除非最后一个参数( 时区) 是nil。 这种情况下会引发panic异常。 panic是来自被调函数的信号, 表示发生了某个已知的bug。 一个良好的程序永远不应该发生panic异常。
  3. 对于大部分函数而言, 永远无法确保能否成功运行。 这是因为错误的原因超出了程序员的控制。 举个例子, 任何进行I/O操作的函数都会面临出现错误的可能, 只有没有经验的程序员才会相信读写操作不会失败, 即时是简单的读写。 因此, 当本该可信的操作出乎意料的失败后, 我们必须弄清楚导致失败的原因。
  4. 在Go的错误处理中, 错误是软件包API和应用程序用户界面的一个重要组成部分, 程序运行失败仅被认为是几个预期的结果之一。
  5. 对于那些将运行失败看作是预期结果的函数, 它们会返回一个额外的返回值, 通常是最后一个, 来传递错误信息。 如果导致失败的原因只有一个, 额外的返回值可以是一个布尔值, 通常被命名为ok。 比如, cache.Lookup失败的唯一原因是key不存在, 那么代码可以按照下面的方式组织:
    《Go语言圣经》学习笔记 第五章函数_第12张图片
  6. 通常, 导致失败的原因不止一种, 尤其是对I/O操作而言, 用户需要了解更多的错误信息。 因此, 额外的返回值不再是简单的布尔类型, 而是error类型。
  7. 内置的error是接口类型。 我们将在第七章了解接口类型的含义, 以及它对错误处理的影响。现在我们只需要明白error类型可能是nil或者non-nil。 nil意味着函数运行成功, non-nil表示失败。 对于non-nil的error类型,我们可以通过调用error的Error函数或者输出函数获得字符串类型的错误信息。
    在这里插入图片描述
  8. 通常, 当函数返回non-nil的error时, 其他的返回值是未定义的(undefined),这些未定义的返回值应该被忽略。 然而, 有少部分函数在发生错误时, 仍然会返回一些有用的返回值。 比如,当读取文件发生错误时, Read函数会返回可以读取的字节数以及错误信息。 对于这种情况,正确的处理方式应该是先处理这些不完整的数据, 再处理错误。 因此对函数的返回值要有清晰的说明, 以便于其他人使用。
  9. 在Go中, 函数运行失败时会返回错误信息, 这些错误信息被认为是一种预期的值而非异常( exception) , 这使得Go有别于那些将函数运行失败看作是异常的语言。 虽然Go有各种异常机制, 但这些机制仅被使用在处理那些未被预料到的错误, 即bug, 而不是那些在健壮程序中应该被避免的程序错误。 对于Go的异常机制我们将在5.9介绍。
  10. Go这样设计的原因是由于对于某个应该在控制流程中处理的错误而言, 将这个错误以异常的形式抛出会混乱对错误的描述, 这通常会导致一些糟糕的后果。 当某个程序错误被当作异常处理后, 这个错误会将堆栈根据信息返回给终端用户, 这些信息复杂且无用, 无法帮助定位错误。
  11. 正因此, Go使用控制流机制( 如if和return) 处理异常, 这使得编码人员能更多的关注错误处理。

1. 错误处理策略

  1. 当一次函数调用返回错误时, 调用者有应该选择何时的方式处理错误。 根据情况的不同, 有很多处理方式, 让我们来看看常用的五种方式。
  2. 首先, 也是最常用的方式是传播错误。 这意味着函数中某个子程序的失败, 会变成该函数的失败。 下面, 我们以5.3节的findLinks函数作为例子。 如果findLinks对http.Get的调用失败,findLinks会直接将这个HTTP错误返回给调用者:
    《Go语言圣经》学习笔记 第五章函数_第13张图片
  3. 当对html.Parse的调用失败时, findLinks不会直接返回html.Parse的错误, 因为缺少两条重要信息: 1、 错误发生在解析器; 2、 url已经被解析。 这些信息有助于错误的处理, findLinks会构造新的错误信息返回给调用者:
    《Go语言圣经》学习笔记 第五章函数_第14张图片
  4. fmt.Errorf函数使用fmt.Sprintf格式化错误信息并返回。 我们使用该函数前缀添加额外的上下文信息到原始错误信息。 当错误最终由main函数处理时, 错误信息应提供清晰的从原因到后果的因果链。
  5. 由于错误信息经常是以链式组合在一起的, 所以错误信息中应避免大写和换行符。 最终的错误信息可能很长, 我们可以通过类似grep的工具处理错误信息( 译者注: grep是一种文本搜索工具) 。
  6. 编写错误信息时, 我们要确保错误信息对问题细节的描述是详尽的。 尤其是要注意错误信息表达的一致性, 即相同的函数或同包内的同一组函数返回的错误在构成和处理方式上是相似的。
  7. 以OS包为例, OS包确保文件操作( 如os.Open、 Read、 Write、 Close) 返回的每个错误的描述不仅仅包含错误的原因( 如无权限, 文件目录不存在) 也包含文件名, 这样调用者在构造新的错误信息时无需再添加这些信息。
  8. 一般而言, 被调函数f(x)会将调用信息和参数信息作为发生错误时的上下文放在错误信息中并返回给调用者, 调用者需要添加一些错误信息中不包含的信息, 比如添加url到html.Parse返回的错误中。
  9. 让我们来看看处理错误的第二种策略。 如果错误的发生是偶然性的, 或由不可预知的问题导致的。 一个明智的选择是重新尝试失败的操作。 在重试时, 我们需要限制重试的时间间隔或重试的次数, 防止无限制的重试。
  10. gopl.io/ch5/wait
    《Go语言圣经》学习笔记 第五章函数_第15张图片
  11. 如果错误发生后, 程序无法继续运行, 我们就可以采用第三种策略: 输出错误信息并结束程序。 需要注意的是, 这种策略只应在main中执行。 对库函数而言, 应仅向上传播错误, 除非该错误意味着程序内部包含不一致性, 即遇到了bug, 才能在库函数中结束程序。
    《Go语言圣经》学习笔记 第五章函数_第16张图片
  12. 调用log.Fatalf可以更简洁的代码达到与上文相同的效果。 log中的所有函数, 都默认会在错误信息之前输出时间信息。
    在这里插入图片描述
  13. 长时间运行的服务器常采用默认的时间格式, 而交互式工具很少采用包含如此多信息的格式。
    在这里插入图片描述
  14. 我们可以设置log的前缀信息屏蔽时间信息, 一般而言, 前缀信息会被设置成命令名。
    在这里插入图片描述
  15. 第四种策略: 有时, 我们只需要输出错误信息就足够了, 不需要中断程序的运行。 我们可以通过log包提供函数
    《Go语言圣经》学习笔记 第五章函数_第17张图片
  16. 或者标准错误流输出错误信息。
    在这里插入图片描述
  17. log包中的所有函数会为没有换行符的字符串增加换行符。
  18. 第五种, 也是最后一种策略: 我们可以直接忽略掉错误.
    《Go语言圣经》学习笔记 第五章函数_第18张图片
  19. 尽管os.RemoveAll会失败, 但上面的例子并没有做错误处理。 这是因为操作系统会定期的清理临时目录。 正因如此, 虽然程序没有处理错误, 但程序的逻辑不会因此受到影响。 我们应该在每次函数调用后, 都养成考虑错误处理的习惯, 当你决定忽略某个错误时, 你应该在清晰的记录下你的意图。
  20. 在Go中, 错误处理有一套独特的编码风格。 检查某个子函数是否失败后, 我们通常将处理失败的逻辑代码放在处理成功的代码之前。 如果某个错误会导致函数返回, 那么成功时的逻辑代码不应放在else语句块中, 而应直接放在函数体中。 Go中大部分函数的代码结构几乎相同, 首先是一系列的初始检查, 防止错误发生, 之后是函数的实际逻辑。

2. 文件结尾错误( EOF)

  1. 函数经常会返回多种错误, 这对终端用户来说可能会很有趣, 但对程序而言, 这使得情况变得复杂。 很多时候, 程序必须根据错误类型, 作出不同的响应。 让我们考虑这样一个例子:从文件中读取n个字节。 如果n等于文件的长度, 读取过程的任何错误都表示失败。 如果n小于文件的长度, 调用者会重复的读取固定大小的数据直到文件结束。 这会导致调用者必须分别处理由文件结束引起的各种错误。 基于这样的原因, io包保证任何由文件结束引起的读取失败都返回同一个错误——io.EOF, 该错误在io包中定义:
    《Go语言圣经》学习笔记 第五章函数_第19张图片
  2. 调用者只需通过简单的比较, 就可以检测出这个错误。 下面的例子展示了如何从标准输入中读取字符, 以及判断文件结束。 ( 4.3的chartcount程序展示了更加复杂的代码)
    《Go语言圣经》学习笔记 第五章函数_第20张图片
  3. 因为文件结束这种错误不需要更多的描述, 所以io.EOF有固定的错误信息——“EOF”。 对于其他错误, 我们可能需要在错误信息中描述错误的类型和数量, 这使得我们不能像io.EOF一样采用固定的错误信息。 在7.11节中, 我们会提出更系统的方法区分某些固定的错误值。

5. 函数值

  1. 在Go中, 函数被看作第一类值( first-class values) : 函数像其他值一样, 拥有类型, 可以被赋值给其他变量, 传递给函数, 从函数返回。 对函数值( function value) 的调用类似函数调用。 例子如下:
    《Go语言圣经》学习笔记 第五章函数_第21张图片
  2. 函数类型的零值是nil。 调用值为nil的函数值会引起panic错误:
    在这里插入图片描述
  3. 函数值可以与nil比较:
    《Go语言圣经》学习笔记 第五章函数_第22张图片
  4. 但是函数值之间是不可比较的, 也不能用函数值作为map的key。
  5. 函数值使得我们不仅仅可以通过数据来参数化函数, 亦可通过行为。 标准库中包含许多这样的例子。 下面的代码展示了如何使用这个技巧。 strings.Map对字符串中的每个字符调用add1函数, 并将每个add1函数的返回值组成一个新的字符串返回给调用者。
    《Go语言圣经》学习笔记 第五章函数_第23张图片
  6. 5.2节的findLinks函数使用了辅助函数visit,遍历和操作了HTML页面的所有结点。 使用函数值, 我们可以将遍历结点的逻辑和操作结点的逻辑分离, 使得我们可以复用遍历的逻辑, 从而对结点进行不同的操作。
  7. gopl.io/ch5/outline2
    《Go语言圣经》学习笔记 第五章函数_第24张图片
  8. 该函数接收2个函数作为参数, 分别在结点的孩子被访问前和访问后调用。 这样的设计给调用者更大的灵活性。 举个例子, 现在我们有startElemen和endElement两个函数用于输出HTML元素的开始标签和结束标签 ...
    《Go语言圣经》学习笔记 第五章函数_第25张图片
  9. 上面的代码利用fmt.Printf的一个小技巧控制输出的缩进。 %s 中的 * 会在字符串之前填充一些空格。 在例子中,每次输出会先填充 depth2 数量的空格, 再输出"", 最后再输出HTML标签。
  10. 如果我们像下面这样调用forEachNode:
    在这里插入图片描述
  11. 与之前的outline程序相比, 我们得到了更加详细的页面结构:
    《Go语言圣经》学习笔记 第五章函数_第26张图片

6. 匿名函数

  1. 拥有函数名的函数只能在包级语法块中被声明, 通过函数字面量( function literal) , 我们可绕过这一限制, 在任何表达式中表示一个函数值。 函数字面量的语法和函数声明相似, 区别在于func关键字后没有函数名。 函数值字面量是一种表达式, 它的值被称为匿名函数( anonymous function) 。

  2. 函数字面量允许我们在使用函数时, 再定义它。 通过这种技巧, 我们可以改写之前对strings.Map的调用:
    在这里插入图片描述

  3. 更为重要的是, 通过这种方式定义的函数可以访问完整的词法环境( lexical environment) ,这意味着在函数中定义的内部函数可以引用该函数的变量, 如下例所示:

  4. gopl.io/ch5/squares
    《Go语言圣经》学习笔记 第五章函数_第27张图片

  5. 函数squares返回另一个类型为 func() int 的函数。 对squares的一次调用会生成一个局部变量x并返回一个匿名函数。 每次调用时匿名函数时, 该函数都会先使x的值加1, 再返回x的平方。 第二次调用squares时, 会生成第二个x变量, 并返回一个新的匿名函数。 新匿名函数操作的是第二个x变量。

  6. squares的例子证明, 函数值不仅仅是一串代码, 还记录了状态。 在squares中定义的匿名内部函数可以访问和更新squares中的局部变量, 这意味着匿名函数和squares中, 存在变量引用。 这就是函数值属于引用类型和函数值不可比较的原因。 Go使用闭包( closures) 技术实现函数值, Go程序员也把函数值叫做闭包。

  7. 通过这个例子, 我们看到变量的生命周期不由它的作用域决定: squares返回后, 变量x仍然隐式的存在于f中。

  8. 接下来, 我们讨论一个有点学术性的例子, 考虑这样一个问题: 给定一些计算机课程, 每个课程都有前置课程, 只有完成了前置课程才可以开始当前课程的学习; 我们的目标是选择出一组课程, 这组课程必须确保按顺序学习时, 能全部被完成。 每个课程的前置课程如下:

  9. gopl.io/ch5/toposort
    《Go语言圣经》学习笔记 第五章函数_第28张图片

  10. 这类问题被称作拓扑排序。 从概念上说, 前置条件可以构成有向图。 图中的顶点表示课程,边表示课程间的依赖关系。 显然, 图中应该无环, 这也就是说从某点出发的边, 最终不会回到该点。 下面的代码用深度优先搜索了整张图, 获得了符合要求的课程序列。
    《Go语言圣经》学习笔记 第五章函数_第29张图片

  11. 当匿名函数需要被递归调用时, 我们必须首先声明一个变量( 在上面的例子中, 我们首先声明了 visitAll) , 再将匿名函数赋值给这个变量。 如果不分成两部, 函数字面量无法与visitAll绑定, 我们也无法递归调用该匿名函数。
    《Go语言圣经》学习笔记 第五章函数_第30张图片

  12. 在topsort中, 首先对prereqs中的key排序, 再调用visitAll。 因为prereqs映射的是切片而不是更复杂的map, 所以数据的遍历次序是固定的, 这意味着你每次运行topsort得到的输出都是一样的。 topsort的输出结果如下:
    《Go语言圣经》学习笔记 第五章函数_第31张图片

  13. 让我们回到findLinks这个例子。 我们将代码移动到了links包下, 将函数重命名为Extract, 在第八章我们会再次用到这个函数。 新的匿名函数被引入, 用于替换原来的visit函数。 该匿名函数负责将新连接添加到切片中。 在Extract中, 使用forEachNode遍历HTML页面, 由于Extract只需要在遍历结点前操作结点, 所以forEachNode的post参数被传入nil。

  14. gopl.io/ch5/links

    // Package links provides a link-extraction function.
    package links
    
    import (
    	"fmt"
    	"net/http"
    
    	"golang.org/x/net/html"
    )
    
    // Extract makes an HTTP GET request to the specified URL, parses
    // the response as HTML, and returns the links in the HTML document.
    func Extract(url string) ([]string, error) {
    	resp, err := http.Get(url)
    	if err != nil {
    		return nil, err
    	}
    	if resp.StatusCode != http.StatusOK {
    		resp.Body.Close()
    		return nil, fmt.Errorf("getting %s: %s", url, resp.Status)
    	}
    
    	doc, err := html.Parse(resp.Body)
    	resp.Body.Close()
    	if err != nil {
    		return nil, fmt.Errorf("parsing %s as HTML: %v", url, err)
    	}
    
    	var links []string
    	visitNode := func(n *html.Node) {
    		if n.Type == html.ElementNode && n.Data == "a" {
    			for _, a := range n.Attr {
    				if a.Key != "href" {
    					continue
    				}
    				link, err := resp.Request.URL.Parse(a.Val)
    				if err != nil {
    					continue // ignore bad URLs
    				}
    				links = append(links, link.String())
    			}
    		}
    	}
    	forEachNode(doc, visitNode, nil)
    	return links, nil
    }
    
  15. 上面的代码对之前的版本做了改进, 现在links中存储的不是href属性的原始值, 而是通过resp.Request.URL解析后的值。 解析后, 这些连接以绝对路径的形式存在, 可以直接被http.Get访问。

  16. 网页抓取的核心问题就是如何遍历图。 在topoSort的例子中, 已经展示了深度优先遍历, 在网页抓取中, 我们会展示如何用广度优先遍历图。 在第8章, 我们会介绍如何将深度优先和广度优先结合使用。

  17. 下面的函数实现了广度优先算法。 调用者需要输入一个初始的待访问列表和一个函数f。 待访问列表中的每个元素被定义为string类型。 广度优先算法会为每个元素调用一次f。 每次f执行完毕后, 会返回一组待访问元素。 这些元素会被加入到待访问列表中。 当待访问列表中的所有元素都被访问后, breadthFirst函数运行结束。 为了避免同一个元素被访问两次, 代码中维护了一个map。

  18. gopl.io/ch5/findlinks3
    《Go语言圣经》学习笔记 第五章函数_第32张图片

  19. 就像我们在章节3解释的那样, append的参数“f(item)…”, 会将f返回的一组元素一个个添加到worklist中。

  20. 在我们网页抓取器中, 元素的类型是url。 crawl函数会将URL输出, 提取其中的新链接, 并将这些新链接返回。 我们会将crawl作为参数传递给breadthFirst。
    《Go语言圣经》学习笔记 第五章函数_第33张图片

  21. 为了使抓取器开始运行, 我们用命令行输入的参数作为初始的待访问url。
    《Go语言圣经》学习笔记 第五章函数_第34张图片

  22. 让我们从 https://golang.org 开始, 下面是程序的输出结果:
    《Go语言圣经》学习笔记 第五章函数_第35张图片

  23. 当所有发现的链接都已经被访问或电脑的内存耗尽时, 程序运行结束。

1. 警告: 捕获迭代变量

  1. 本节, 将介绍Go词法作用域的一个陷阱。 请务必仔细的阅读, 弄清楚发生问题的原因。 即使是经验丰富的程序员也会在这个问题上犯错误。
  2. 考虑这个样一个问题: 你被要求首先创建一些目录, 再将目录删除。 在下面的例子中我们用函数值来完成删除操作。 下面的示例代码需要引入os包。 为了使代码简单, 我们忽略了所有的异常处理。
    《Go语言圣经》学习笔记 第五章函数_第36张图片
  3. 你可能会感到困惑, 为什么要在循环体中用循环变量d赋值一个新的局部变量, 而不是像下面的代码一样直接使用循环变量dir。 需要注意, 下面的代码是错误的。
    《Go语言圣经》学习笔记 第五章函数_第37张图片
  4. 问题的原因在于循环变量的作用域。 在上面的程序中, for循环语句引入了新的词法块, 循环变量dir在这个词法块中被声明。 在该循环中生成的所有函数值都共享相同的循环变量。 需要注意, 函数值中记录的是循环变量的内存地址, 而不是循环变量某一时刻的值。 以dir为例,后续的迭代会不断更新dir的值, 当删除操作执行时, for循环已完成, dir中存储的值等于最后一次迭代的值。 这意味着, 每次对os.RemoveAll的调用删除的都是相同的目录。
  5. 通常, 为了解决这个问题, 我们会引入一个与循环变量同名的局部变量, 作为循环变量的副本。 比如下面的变量dir, 虽然这看起来很奇怪, 但却很有用。
    《Go语言圣经》学习笔记 第五章函数_第38张图片
  6. 这个问题不仅存在基于range的循环, 在下面的例子中, 对循环变量i的使用也存在同样的问题:
    《Go语言圣经》学习笔记 第五章函数_第39张图片
  7. 如果你使用go语句( 第八章) 或者defer语句( 5.8节) 会经常遇到此类问题。 这不是go或defer本身导致的, 而是因为它们都会等待循环结束后, 再执行函数值。

7. 可变参数

  1. 参数数量可变的函数称为为可变参数函数。 典型的例子就是fmt.Printf和类似函数。 Printf首先接收一个的必备参数, 之后接收任意个数的后续参数。
  2. 在声明可变参数函数时, 需要在参数列表的最后一个参数类型之前加上省略符号“…”, 这表示该函数会接收任意数量的该类型参数。
  3. gopl.io/ch5/sum
    《Go语言圣经》学习笔记 第五章函数_第40张图片
  4. sum函数返回任意个int型参数的和。 在函数体中,vals被看作是类型为[] int的切片。 sum可以接收任意数量的int型参数:
    在这里插入图片描述
  5. 在上面的代码中, 调用者隐式的创建一个数组, 并将原始参数复制到数组中, 再把数组的一个切片作为参数传给被调函数。 如果原始参数已经是切片类型, 我们该如何传递给sum? 只需在最后一个参数后加上省略符。 下面的代码功能与上个例子中最后一条语句相同。
    6.
  6. 虽然在可变参数函数内部, …int 型参数的行为看起来很像切片类型, 但实际上, 可变参数函数和以切片作为参数的函数是不同的。
    《Go语言圣经》学习笔记 第五章函数_第41张图片
  7. 可变参数函数经常被用于格式化字符串。 下面的errorf函数构造了一个以行号开头的, 经过格式化的错误信息。 函数名的后缀f是一种通用的命名规范, 代表该可变参数函数可以接收Printf风格的格式化字符串。
    《Go语言圣经》学习笔记 第五章函数_第42张图片
  8. interfac{}表示函数的最后一个参数可以接收任意类型, 我们会在第7章详细介绍。

8. Deferred函数

  1. 在findLinks的例子中, 我们用http.Get的输出作为html.Parse的输入。 只有url的内容的确是HTML格式的, html.Parse才可以正常工作, 但实际上, url指向的内容很丰富, 可能是图片,纯文本或是其他。 将这些格式的内容传递给html.parse, 会产生不良后果。
  2. 下面的例子获取HTML页面并输出页面的标题。 title函数会检查服务器返回的Content-Type字段, 如果发现页面不是HTML, 将终止函数运行, 返回错误。
  3. gopl.io/ch5/title1
    《Go语言圣经》学习笔记 第五章函数_第43张图片
  4. 下面展示了运行效果:
    《Go语言圣经》学习笔记 第五章函数_第44张图片
  5. resp.Body.close调用了多次, 这是为了确保title在所有执行路径下( 即使函数运行失败) 都关闭了网络连接。 随着函数变得复杂, 需要处理的错误也变多, 维护清理逻辑变得越来越困难。 而Go语言独有的defer机制可以让事情变得简单。
  6. 你只需要在调用普通函数或方法前加上关键字defer, 就完成了defer所需要的语法。 当defer语句被执行时, 跟在defer后面的函数会被延迟执行。 直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行, 不论包含defer语句的函数是通过return正常结束, 还是由于panic导致的异常结束。 你可以在一个函数中执行多条defer语句, 它们的执行顺序与声明顺序相反。
  7. defer语句经常被用于处理成对的操作, 如打开、 关闭、 连接、 断开连接、 加锁、 释放锁。 通过defer机制, 不论函数逻辑多复杂, 都能保证在任何执行路径下, 资源被释放。 释放资源的defer应该直接跟在请求资源的语句后。 在下面的代码中, 一条defer语句替代了之前的所有resp.Body.Close
  8. gopl.io/ch5/title2
    《Go语言圣经》学习笔记 第五章函数_第45张图片
  9. 在处理其他资源时, 也可以采用defer机制, 比如对文件的操作:
  10. io/ioutil
    《Go语言圣经》学习笔记 第五章函数_第46张图片
  11. 或是处理互斥锁( 9.2章)
    《Go语言圣经》学习笔记 第五章函数_第47张图片
  12. 调试复杂程序时, defer机制也常被用于记录何时进入和退出函数。 下例中的bigSlowOperation函数, 直接调用trace记录函数的被调情况。 bigSlowOperation被调时,trace会返回一个函数值, 该函数值会在bigSlowOperation退出时被调用。 通过这种方式, 我们可以只通过一条语句控制函数的入口和所有的出口, 甚至可以记录函数的运行时间, 如例子中的start。 需要注意一点: 不要忘记defer语句后的圆括号, 否则本该在进入时执行的操作会在退出时执行, 而本该在退出时执行的, 永远不会被执行。
  13. gopl.io/ch5/trace
    《Go语言圣经》学习笔记 第五章函数_第48张图片
  14. 每一次bigSlowOperation被调用, 程序都会记录函数的进入, 退出, 持续时间。 ( 我们用time.Sleep模拟一个耗时的操作)
    《Go语言圣经》学习笔记 第五章函数_第49张图片
  15. 我们知道, defer语句中的函数会在return语句更新返回值变量后再执行, 又因为在函数中定义的匿名函数可以访问该函数包括返回值变量在内的所有变量, 所以, 对匿名函数采用defer机制, 可以使其观察函数的返回值。
  16. 以double函数为例:
    在这里插入图片描述
  17. 我们只需要首先命名double的返回值, 再增加defer语句, 我们就可以在double每次被调用时, 输出参数以及返回值。
    《Go语言圣经》学习笔记 第五章函数_第50张图片
  18. 可能doulbe函数过于简单, 看不出这个小技巧的作用, 但对于有许多return语句的函数而言,这个技巧很有用。
  19. 被延迟执行的匿名函数甚至可以修改函数返回给调用者的返回值:
    《Go语言圣经》学习笔记 第五章函数_第51张图片
  20. 在循环体中的defer语句需要特别注意, 因为只有在函数执行完毕后, 这些被延迟的函数才会执行。 下面的代码会导致系统的文件描述符耗尽, 因为在所有文件都被处理之前, 没有文件会被关闭。
    《Go语言圣经》学习笔记 第五章函数_第52张图片
  21. 一种解决方法是将循环体中的defer语句移至另外一个函数。 在每次循环时, 调用这个函数。
    《Go语言圣经》学习笔记 第五章函数_第53张图片
  22. 下面的代码是fetch( 1.5节) 的改进版, 我们将http响应信息写入本地文件而不是从标准输出流输出。 我们通过path.Base提出url路径的最后一段作为文件名。
  23. gopl.io/ch5/fetch
    《Go语言圣经》学习笔记 第五章函数_第54张图片
  24. 对resp.Body.Close延迟调用我们已经见过了, 在此不做解释。 上例中, 通过os.Create打开文件进行写入, 在关闭文件时, 我们没有对f.close采用defer机制, 因为这会产生一些微妙的错误。 许多文件系统, 尤其是NFS, 写入文件时发生的错误会被延迟到文件关闭时反馈。 如果没有检查文件关闭时的反馈信息, 可能会导致数据丢失, 而我们还误以为写入操作成功。 如果io.Copy和f.close都失败了, 我们倾向于将io.Copy的错误信息反馈给调用者, 因为它先于f.close发生, 更有可能接近问题的本质。

9. Panic异常

  1. Go的类型系统会在编译时捕获很多错误, 但有些错误只能在运行时检查, 如数组访问越界、空指针引用等。 这些运行时错误会引起painc异常。
  2. 一般而言, 当panic异常发生时, 程序会中断运行, 并立即执行在该goroutine( 可以先理解成线程, 在第8章会详细介绍) 中被延迟的函数( defer 机制) 。 随后, 程序崩溃并输出日志信息。 日志信息包括panic value和函数调用的堆栈跟踪信息。 panic value通常是某种错误信息。 对于每个goroutine, 日志信息中都会有与之相对的, 发生panic时的函数调用堆栈跟踪信息。 通常, 我们不需要再次运行程序去定位问题, 日志信息已经提供了足够的诊断依据。 因此, 在我们填写问题报告时, 一般会将panic异常和日志信息一并记录。
  3. 不是所有的panic异常都来自运行时, 直接调用内置的panic函数也会引发panic异常; panic函数接受任何值作为参数。 当某些不应该发生的场景发生时, 我们就应该调用panic。 比如, 当程序到达了某条逻辑上不可能到达的路径:
    《Go语言圣经》学习笔记 第五章函数_第55张图片
  4. 断言函数必须满足的前置条件是明智的做法, 但这很容易被滥用。 除非你能提供更多的错误信息, 或者能更快速的发现错误, 否则不需要使用断言, 编译器在运行时会帮你检查代码。
    《Go语言圣经》学习笔记 第五章函数_第56张图片
  5. 虽然Go的panic机制类似于其他语言的异常, 但panic的适用场景有一些不同。 由于panic会引起程序的崩溃, 因此panic一般用于严重错误, 如程序内部的逻辑不一致。 勤奋的程序员认为任何崩溃都表明代码中存在漏洞, 所以对于大部分漏洞, 我们应该使用Go提供的错误机制,而不是panic, 尽量避免程序的崩溃。 在健壮的程序中, 任何可以预料到的错误, 如不正确的输入、 错误的配置或是失败的I/O操作都应该被优雅的处理, 最好的处理方式, 就是使用Go的错误机制。
  6. 考虑regexp.Compile函数, 该函数将正则表达式编译成有效的可匹配格式。 当输入的正则表达式不合法时, 该函数会返回一个错误。 当调用者明确的知道正确的输入不会引起函数错误时, 要求调用者检查这个错误是不必要和累赘的。 我们应该假设函数的输入一直合法, 就如前面的断言一样: 当调用者输入了不应该出现的输入时, 触发panic异常。
  7. 在程序源码中, 大多数正则表达式是字符串字面值( string literals) , 因此regexp包提供了包装函数regexp.MustCompile检查输入的合法性。
    《Go语言圣经》学习笔记 第五章函数_第57张图片
  8. 包装函数使得调用者可以便捷的用一个编译后的正则表达式为包级别的变量赋值:
    在这里插入图片描述
  9. 显然, MustCompile不能接收不合法的输入。 函数名中的Must前缀是一种针对此类函数的命名约定, 比如template.Must( 4.6节)
    《Go语言圣经》学习笔记 第五章函数_第58张图片
  10. 当f(0)被调用时, 发生panic异常, 之前被延迟执行的的3个fmt.Printf被调用。 程序中断执行后, panic信息和堆栈信息会被输出( 下面是简化的输出) :
    《Go语言圣经》学习笔记 第五章函数_第59张图片
  11. 我们在下一节将看到, 如何使程序从panic异常中恢复, 阻止程序的崩溃。
  12. 为了方便诊断问题, runtime包允许程序员输出堆栈信息。 在下面的例子中, 我们通过在main函数中延迟调用printStack输出堆栈信息。
    《Go语言圣经》学习笔记 第五章函数_第60张图片
  13. printStack的简化输出如下( 下面只是printStack的输出, 不包括panic的日志信息) :
    《Go语言圣经》学习笔记 第五章函数_第61张图片
  14. 将panic机制类比其他语言异常机制的读者可能会惊讶, runtime.Stack为何能输出已经被释放函数的信息? 在Go的panic机制中, 延迟函数的调用在释放堆栈信息之前。

10. Recover捕获异常

  1. 通常来说, 不应该对panic异常做任何处理, 但有时, 也许我们可以从异常中恢复, 至少我们可以在程序崩溃前, 做一些操作。 举个例子, 当web服务器遇到不可预料的严重问题时, 在崩溃前应该将所有的连接关闭; 如果不做任何处理, 会使得客户端一直处于等待状态。 如果web服务器还在开发阶段, 服务器甚至可以将异常信息反馈到客户端, 帮助调试。
  2. 如果在deferred函数中调用了内置函数recover, 并且定义该defer语句的函数发生了panic异常, recover会使程序从panic中恢复, 并返回panic value。 导致panic异常的函数不会继续运行, 但能正常返回。 在未发生panic时调用recover, recover会返回nil。
  3. 让我们以语言解析器为例, 说明recover的使用场景。 考虑到语言解析器的复杂性, 即使某个语言解析器目前工作正常, 也无法肯定它没有漏洞。 因此, 当某个异常出现时, 我们不会选择让解析器崩溃, 而是会将panic异常当作普通的解析错误, 并附加额外信息提醒用户报告此错误。
    《Go语言圣经》学习笔记 第五章函数_第62张图片
  4. deferred函数帮助Parse从panic中恢复。 在deferred函数内部, panic value被附加到错误信息中; 并用err变量接收错误信息, 返回给调用者。 我们也可以通过调用runtime.Stack往错误信息中添加完整的堆栈调用信息。
  5. 不加区分的恢复所有的panic异常, 不是可取的做法; 因为在panic之后, 无法保证包级变量的状态仍然和我们预期一致。 比如, 对数据结构的一次重要更新没有被完整完成、 文件或者网络连接没有被关闭、 获得的锁没有被释放。 此外, 如果写日志时产生的panic被不加区分的恢复, 可能会导致漏洞被忽略。
  6. 虽然把对panic的处理都集中在一个包下, 有助于简化对复杂和不可以预料问题的处理, 但作为被广泛遵守的规范, 你不应该试图去恢复其他包引起的panic。 公有的API应该将函数的运行失败作为error返回, 而不是panic。 同样的, 你也不应该恢复一个由他人开发的函数引起的panic, 比如说调用者传入的回调函数, 因为你无法确保这样做是安全的。
  7. 有时我们很难完全遵循规范, 举个例子, net/http包中提供了一个web服务器, 将收到的请求分发给用户提供的处理函数。 很显然, 我们不能因为某个处理函数引发的panic异常, 杀掉整个进程; web服务器遇到处理函数导致的panic时会调用recover, 输出堆栈信息, 继续运行。
  8. 这样的做法在实践中很便捷, 但也会引起资源泄漏, 或是因为recover操作, 导致其他问题。
  9. 基于以上原因, 安全的做法是有选择性的recover。 换句话说, 只恢复应该被恢复的panic异常, 此外, 这些异常所占的比例应该尽可能的低。 为了标识某个panic是否应该被恢复, 我们可以将panic value设置成特殊类型。 在recover时对panic value进行检查, 如果发现panicvalue是特殊类型, 就将这个panic作为errror处理, 如果不是, 则按照正常的panic进行处理( 在下面的例子中, 我们会看到这种方式) 。
  10. 下面的例子是title函数的变形, 如果HTML页面包含多个</code>, 该函数会给调用者返回一个错误( error) 。 在soleTitle内部处理时, 如果检测到有多个<code><title></code>, 会调用panic, 阻止函数继续递归, 并将特殊类型bailout作为panic的参数。<br> <a href="http://img.e-com-net.com/image/info8/0c102993640a4fcd83e6e581a88c274b.jpg" target="_blank"><img src="http://img.e-com-net.com/image/info8/0c102993640a4fcd83e6e581a88c274b.jpg" alt="《Go语言圣经》学习笔记 第五章函数_第63张图片" width="650" height="665" style="border:1px solid black;"></a></li> <li>在上例中, deferred函数调用recover, 并检查panic value。 当panic value是bailout{}类型时,deferred函数生成一个error返回给调用者。 当panic value是其他non-nil值时, 表示发生了未知的panic异常, deferred函数将调用panic函数并将当前的panic value作为参数传入; 此时, 等同于recover没有做任何操作。 ( 请注意: 在例子中, 对可预期的错误采用了panic, 这违反了之前的建议, 我们在此只是想向读者演示这种机制。 )</li> <li>有些情况下, 我们无法恢复。 某些致命错误会导致Go在运行时终止程序, 如内存不足。</li> </ol> </div> </div> </div> </div> </div> <!--PC和WAP自适应版--> <div id="SOHUCS" sid="1277314277471961088"></div> <script type="text/javascript" src="/views/front/js/chanyan.js"></script> <!-- 文章页-底部 动态广告位 --> <div class="youdao-fixed-ad" id="detail_ad_bottom"></div> </div> <div class="col-md-3"> <div class="row" id="ad"> <!-- 文章页-右侧1 动态广告位 --> <div id="right-1" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_1"> </div> </div> <!-- 文章页-右侧2 动态广告位 --> <div id="right-2" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_2"></div> </div> <!-- 文章页-右侧3 动态广告位 --> <div id="right-3" class="col-lg-12 col-md-12 col-sm-4 col-xs-4 ad"> <div class="youdao-fixed-ad" id="detail_ad_3"></div> </div> </div> </div> </div> </div> </div> <div class="container"> <h4 class="pt20 mb15 mt0 border-top">你可能感兴趣的:(Go)</h4> <div id="paradigm-article-related"> <div class="recommend-post mb30"> <ul class="widget-links"> <li><a href="/article/1950231509906550784.htm" title="25-1-2019" target="_blank">25-1-2019</a> <span class="text-muted">树藤与海岛呢</span> <div>hello八月来报道了今天看到了一篇文章就只想记下那两句话:良田千顷不过一日三餐广夏万间只睡卧榻三尺大概的意思就是要珍惜当下不要等来不及的时候才珍惜分享今天的两餐最近没有时间运动呢下个月补回好了说完了哈哈goodnight图片发自App图片发自App</div> </li> <li><a href="/article/1950224618606358528.htm" title="Aop +反射 实现方法版本动态切换" target="_blank">Aop +反射 实现方法版本动态切换</a> <span class="text-muted"></span> <div>需求分析在做技术选型的时候一直存在着两个声音,mongo作为数据库比较mysql好,mysql做为该数据比mongo好。当然不同数据库都有有着自己的优势,我们在做技术选型的时候无非就是做到对数据库的扬长避短。mysql最大的优势就是支持事务,事务的五大特性保证的业务可靠性,随之而来的就是事务会产生的问题:脏读、幻读、不可重复度,当然我们也会使用不同的隔离级别来解决。(最典型的业务问题:银行存取钱)</div> </li> <li><a href="/article/1950218818781507584.htm" title="【异常】使用 LiteFlow 框架时,提示错误ChainDuplicateException: [chain name duplicate] chainName=categoryChallenge" target="_blank">【异常】使用 LiteFlow 框架时,提示错误ChainDuplicateException: [chain name duplicate] chainName=categoryChallenge</a> <span class="text-muted">本本本添哥</span> <a class="tag" taget="_blank" href="/search/002/1.htm">002</a><a class="tag" taget="_blank" href="/search/-/1.htm">-</a><a class="tag" taget="_blank" href="/search/%E8%BF%9B%E9%98%B6%E5%BC%80%E5%8F%91%E8%83%BD%E5%8A%9B/1.htm">进阶开发能力</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div>一、报错内容Causedby:com.yomahub.liteflow.exception.ChainDuplicateException:[chainnameduplicate]chainName=categoryChallengeatcom.yomahub.liteflow.parser.helper.ParserHelper.lambda$null$0(ParserHelper.java:1</div> </li> <li><a href="/article/1950216926445760512.htm" title="Matrix-Breakout 2 Morpheus靶场解题过程" target="_blank">Matrix-Breakout 2 Morpheus靶场解题过程</a> <span class="text-muted"></span> <div>信息收集目标探测靶机目标很明显就是61.139.2.141了扫描开放端口发现22、80、81访问端口主机访问80翻译一下,并没有发现什么审查源代码发现里面有一张图片,下载下来看看是否有图片的隐写wgethttp://61.139.2.141/trinity.jpegstegoveritas-itrinity.jpeg-o/home/kali/Desktop/11分解后发现什么都没有,里面的keep</div> </li> <li><a href="/article/1950209621381672960.htm" title="Android 应用权限管理详解" target="_blank">Android 应用权限管理详解</a> <span class="text-muted"></span> <div>文章目录1.权限类型2.权限请求机制3.权限组和分级4.权限管理的演进5.权限监控和SELinux强制访问控制6.应用权限审核和GooglePlayProtect7.开发者最佳实践8.用户权限管理9.Android应用沙箱模型10.ScopedStorage(分区存储)11.背景位置权限(BackgroundLocationAccess)12.权限回收和自动清理13.权限请求的用户体验设计14.G</div> </li> <li><a href="/article/1950208485933248512.htm" title="mysql复习" target="_blank">mysql复习</a> <span class="text-muted">立夏的李子</span> <a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a><a class="tag" taget="_blank" href="/search/database/1.htm">database</a> <div>mysqlselect语法selectfromjoinwheregroupbyhavingorderbylimit联合查询innerjoin()leftjoin(以左表为基准,匹配右表,不匹配的返回左表,右表以null值填充)rightjoind··(去除列重复的数据)索引类型主键索引(PrimaryKey)唯一索引(Unique)常规索引(Index)全文索引(FullText)索引准则索引不是</div> </li> <li><a href="/article/1950200667587014656.htm" title="学C++的五大惊人好处" target="_blank">学C++的五大惊人好处</a> <span class="text-muted"></span> <div>为什么要学c++学c++有什么用学习c++的好处有1.中考可以加分2.高考可能直接录取3.就业广且工资高4.在未来30--50年c++一定是一个很受欢迎的职业5.c++成功的例子deepsick等AI智能C++语言兼备编程效率和编译运行效率的语言C++语言是C语言功能增强版,在c语言的基础上添加了面向对象编程和泛型编程的支持既继承了C语言高效,简洁,快速和可移植的传统,又具备类似Java、Go等其</div> </li> <li><a href="/article/1950200541233606656.htm" title="Android 基础知识:Android 应用权限详解" target="_blank">Android 基础知识:Android 应用权限详解</a> <span class="text-muted">流水mpc</span> <a class="tag" taget="_blank" href="/search/android/1.htm">android</a> <div>这篇文章为大家系统的梳理一下Android权限相关的知识,在日常开发中,我们都用过权限,但是对于权限的一些细节我们可能掌握的还不够全面,这篇文章会全面的为大家介绍权限相关的知识。当然,本篇文章依然是参考了Google的官方文档:应用权限。本文目录一、认识Android权限(一)Android系统为什么需要权限?Android系统设置权限的目的是保护Android用户的隐私。对于用户的敏感数据And</div> </li> <li><a href="/article/1950200162810916864.htm" title="Selenium基础教程" target="_blank">Selenium基础教程</a> <span class="text-muted">lemontree1945</span> <a class="tag" taget="_blank" href="/search/selenium/1.htm">selenium</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E6%B5%8B%E8%AF%95%E5%B7%A5%E5%85%B7/1.htm">测试工具</a> <div>1.Selenium环境安装1.1浏览器安装Chrome和ChromeDriver下载地址:https://googlechromelabs.github.io/chrome-for-testing/注意:驱动版本号要和浏览器版本号一致;安装后关闭浏览器自动更新:services.msc:打开系统服务找到和google相关的服务,全部修改为禁用1.2安装第三方库seleniumpipinstall</div> </li> <li><a href="/article/1950199280132222976.htm" title="Android Slices:让应用功能在系统级交互中触手可及" target="_blank">Android Slices:让应用功能在系统级交互中触手可及</a> <span class="text-muted">安卓开发者</span> <a class="tag" taget="_blank" href="/search/Android/1.htm">Android</a><a class="tag" taget="_blank" href="/search/Jetpack/1.htm">Jetpack</a><a class="tag" taget="_blank" href="/search/android/1.htm">android</a><a class="tag" taget="_blank" href="/search/%E4%BA%A4%E4%BA%92/1.htm">交互</a><a class="tag" taget="_blank" href="/search/gitee/1.htm">gitee</a> <div>引言在当今移动应用生态中,用户每天要面对数十个甚至上百个应用的选择,如何让自己的应用在关键时刻触达用户,成为开发者面临的重要挑战。Google在Android9Pie中引入的Slices技术,正是为了解决这一痛点而生。本文将全面介绍AndroidSlices的概念、实现方法、应用场景以及最佳实践,帮助开发者掌握这一提升用户参与度的强大工具。什么是AndroidSlices?AndroidSlice</div> </li> <li><a href="/article/1950187733427220480.htm" title="2018年中南大学中英翻译" target="_blank">2018年中南大学中英翻译</a> <span class="text-muted">某翁</span> <div>参考:20180827235856533.jpg【1】机器学习理论表明,机器学习算法能从有限个训练集样本上得到较好的泛化【1】Machinelearningtheoryshowsthatmachinelearningalgorithmcangeneralizewellfromfinitetrainingsetsampleslimited有限的infinite无限的【2】这似乎违背了一些基本的逻辑准</div> </li> <li><a href="/article/1950187554129113088.htm" title="Django学习笔记(一)" target="_blank">Django学习笔记(一)</a> <span class="text-muted"></span> <div>学习视频为:pythondjangoweb框架开发入门全套视频教程一、安装pipinstalldjango==****检查是否安装成功django.get_version()二、django新建项目操作1、新建一个项目django-adminstartprojectproject_name2、新建APPcdproject_namedjango-adminstartappApp注:一个project</div> </li> <li><a href="/article/1950187223672483840.htm" title="GDP经济社会人文民生栅格数据下载网站汇总" target="_blank">GDP经济社会人文民生栅格数据下载网站汇总</a> <span class="text-muted">疯狂学习GIS</span> <div>  本文为“GIS数据获取整理”专栏(https://blog.csdn.net/zhebushibiaoshifu/category_10857546.html)中第八篇独立博客,因此本文全部标题均由“8”开头。本文对目前主要的GDP、社会与经济数据获取网站加以整理与介绍,若需其它GIS领域数据(如遥感影像数据、气象数据、土地土壤数据、农业数据等),大家可以点击上方专栏查看,也可以看这一篇汇总文</div> </li> <li><a href="/article/1950180497007112192.htm" title="【MySQL】MySQL数据库如何改名" target="_blank">【MySQL】MySQL数据库如何改名</a> <span class="text-muted">武昌库里写JAVA</span> <a class="tag" taget="_blank" href="/search/%E9%9D%A2%E8%AF%95%E9%A2%98%E6%B1%87%E6%80%BB%E4%B8%8E%E8%A7%A3%E6%9E%90/1.htm">面试题汇总与解析</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/boot/1.htm">boot</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a><a class="tag" taget="_blank" href="/search/sql/1.htm">sql</a><a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a> <div>MySQL建库授权语句https://www.jianshu.com/p/2237a9649ceeMySQL数据库改名的三种方法https://www.cnblogs.com/gomysql/p/3584881.htmlMySQL安全修改数据库名几种方法https://blog.csdn.net/haiross/article/details/51282417MySQL重命名数据库https://</div> </li> <li><a href="/article/1950169526440095744.htm" title="从零到一:打造基于GigaChat AI的艺术创作平台 | 笙囧同学的全栈开发实战" target="_blank">从零到一:打造基于GigaChat AI的艺术创作平台 | 笙囧同学的全栈开发实战</a> <span class="text-muted"></span> <div>作者简介:笙囧同学,中科院计算机大模型方向硕士,全栈开发爱好者联系方式:3251736703@qq.com各大平台账号:笙囧同学座右铭:偷懒是人生进步的阶梯前言在AI技术飞速发展的今天,如何将前沿的大模型技术与实际应用相结合,一直是我们开发者关注的焦点。今天,笙囧同学将带大家从零开始,构建一个基于GigaChatAI的艺术创作平台,实现React前端+Django后端的完整全栈解决方案。这不仅仅是</div> </li> <li><a href="/article/1950160572926455808.htm" title="基于STM32设计的LCD指针式电子钟与日历项目" target="_blank">基于STM32设计的LCD指针式电子钟与日历项目</a> <span class="text-muted">鱼弦</span> <a class="tag" taget="_blank" href="/search/%E5%8D%95%E7%89%87%E6%9C%BA%E7%B3%BB%E7%BB%9F%E5%90%88%E9%9B%86/1.htm">单片机系统合集</a><a class="tag" taget="_blank" href="/search/stm32/1.htm">stm32</a><a class="tag" taget="_blank" href="/search/%E5%B5%8C%E5%85%A5%E5%BC%8F%E7%A1%AC%E4%BB%B6/1.htm">嵌入式硬件</a><a class="tag" taget="_blank" href="/search/%E5%8D%95%E7%89%87%E6%9C%BA/1.htm">单片机</a> <div>鱼弦:公众号【红尘灯塔】,CSDN博客专家、内容合伙人、新星导师、全栈领域优质创作者、51CTO(Top红人+专家博主)、github开源爱好者(go-zero源码二次开发、游戏后端架构https://github.com/Peakchen)基于STM32设计的LCD指针式电子钟与日历项目1.介绍基于STM32设计的LCD指针式电子钟与日历项目是一款利用STM32微控制器、LCD显示屏和指针机构实</div> </li> <li><a href="/article/1950159185941426176.htm" title="Collecting Numbers II" target="_blank">Collecting Numbers II</a> <span class="text-muted">YouQian772</span> <a class="tag" taget="_blank" href="/search/%E6%95%B0%E5%AD%A6/1.htm">数学</a><a class="tag" taget="_blank" href="/search/%E6%8E%92%E5%BA%8F/1.htm">排序</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a> <div>题目描述Youaregivenanarraythatcontainseachnumberbetween1...nexactlyonce.Yourtaskistocollectthenumbersfrom1toninincreasingorder.Oneachround,yougothroughthearrayfromlefttorightandcollectasmanynumbersaspossi</div> </li> <li><a href="/article/1950146898060963840.htm" title="夏天唯一的爱...让我们吃芒果" target="_blank">夏天唯一的爱...让我们吃芒果</a> <span class="text-muted">老易的天</span> <div>芒果芒果是一种美味的水果。芒果科学名称MangiferaIndica。果实是圆形,椭圆形,嬉皮状,圆形,长或窄。生芒果可能是绿色,绿色,和芒果黄色,橙色,混合红色,白色甚至绿色。成熟的水果可以在大小和质量上有所不同。每个较大的芒果的平均重量为750克至1千克。通常芒果有很多种。如Fazli,Langra,Golakkhas,Amrapali,Mallika,Subarnarekha。芒果被水果之王</div> </li> <li><a href="/article/1950130692448907264.htm" title="Vue CSR 到 Nuxt 3 SSR 迁移:技术实现与问题解决实录" target="_blank">Vue CSR 到 Nuxt 3 SSR 迁移:技术实现与问题解决实录</a> <span class="text-muted">二倍速播放</span> <a class="tag" taget="_blank" href="/search/%E5%89%8D%E7%AB%AF/1.htm">前端</a><a class="tag" taget="_blank" href="/search/vue.js/1.htm">vue.js</a> <div>1.迁移动机与技术选型1.1CSR架构的局限性基于Vue3和Vite构建的客户端渲染(CSR)单页应用(SPA)提供了良好的开发体验和用户交互流畅性。但是其核心局限在于:搜索引擎优化(SEO):初始HTML响应仅包含一个根div元素,实际内容由JavaScript在浏览器端动态生成。虽然主流搜索引擎(如Google)能够执行部分JavaScript,但其抓取效率和稳定性不如直接获取完整HTML。非</div> </li> <li><a href="/article/1950124769777086464.htm" title="# 【GEE基础及工具)(一)】工欲善其事,必先利其器:借助Open Earth Engine实现影像高效处理及批量任务执行" target="_blank"># 【GEE基础及工具)(一)】工欲善其事,必先利其器:借助Open Earth Engine实现影像高效处理及批量任务执行</a> <span class="text-muted">遥感AI实战</span> <a class="tag" taget="_blank" href="/search/GEE%E5%9F%BA%E7%A1%80%E6%95%99%E7%A8%8B/1.htm">GEE基础教程</a><a class="tag" taget="_blank" href="/search/%E9%81%A5%E6%84%9F/1.htm">遥感</a><a class="tag" taget="_blank" href="/search/GEE/1.htm">GEE</a><a class="tag" taget="_blank" href="/search/%E5%9C%B0%E7%90%86%E4%BF%A1%E6%81%AF/1.htm">地理信息</a><a class="tag" taget="_blank" href="/search/%E4%BF%A1%E6%81%AF%E5%8F%AF%E8%A7%86%E5%8C%96/1.htm">信息可视化</a><a class="tag" taget="_blank" href="/search/sentinel/1.htm">sentinel</a> <div>在遥感数据分析与处理工作中,海量影像数据的预处理(如去云、裁剪)和分析(如均值计算)是支撑后续研究的核心环节。而Sentinel-2影像作为常用的遥感数据源,常因云层遮挡、数据量大等问题增加处理难度。同时,在使用GoogleEarthEngine(GEE)处理数据时,“批量导出任务需手动逐个启动”的问题也会显著降低效率。本文将从“工具优化”和“数据处理”两个维度展开,详细介绍如何通过GEE完成Se</div> </li> <li><a href="/article/1950106872220545024.htm" title="Android CameraX 使用指南:简化相机开发" target="_blank">Android CameraX 使用指南:简化相机开发</a> <span class="text-muted">安卓开发者</span> <a class="tag" taget="_blank" href="/search/Android/1.htm">Android</a><a class="tag" taget="_blank" href="/search/Jetpack/1.htm">Jetpack</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E7%A0%81%E7%9B%B8%E6%9C%BA/1.htm">数码相机</a><a class="tag" taget="_blank" href="/search/android/1.htm">android</a> <div>前言在Android开发中,相机功能一直是比较复杂的部分,需要处理不同设备的兼容性、生命周期管理以及复杂的API调用。Google推出的CameraX库极大地简化了这一过程,让开发者能够更轻松地实现高质量的相机功能。本文将带你全面了解CameraX的使用方法。什么是CameraX?CameraX是Jetpack系列中的一个库,它基于Camera2API构建,但提供了更高层次的抽象,具有以下优点:简</div> </li> <li><a href="/article/1950100186747432960.htm" title="Python 中的 JWT 认证:从生成到验证的完整指南" target="_blank">Python 中的 JWT 认证:从生成到验证的完整指南</a> <span class="text-muted">盛夏绽放</span> <a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E8%AF%AD%E8%A8%80/1.htm">开发语言</a><a class="tag" taget="_blank" href="/search/%E6%9C%89%E9%97%AE%E5%BF%85%E7%AD%94/1.htm">有问必答</a><a class="tag" taget="_blank" href="/search/%E5%90%8E%E7%AB%AF/1.htm">后端</a> <div>文章目录Python中的JWT认证:从生成到验证的完整指南一、JWT是什么?为什么需要它?传统session与JWT对比二、JWT的结构解析三、Python中实现JWT1.安装PyJWT包2.生成JWT3.验证JWT4.错误处理大全四、高级应用场景1.双令牌系统(Access+Refresh)详细说明表格:异常处理补充表:2.与FastAPI/Django集成五、安全最佳实践六、性能优化技巧算法性</div> </li> <li><a href="/article/1950082290147192832.htm" title="TiDB - 分布式数据库的架构与特性" target="_blank">TiDB - 分布式数据库的架构与特性</a> <span class="text-muted">爽新全效瓷兔膏</span> <div>本文还有配套的精品资源,点击获取简介:TiDB是一个开源的分布式NewSQL数据库,受到了Google的Spanner/F1系统的启发。它提供水平扩展和强一致性事务,适用于需要高可用性和大规模数据处理的场景。TiDB的核心特点包括其分布式架构,由TiDBServer(SQL层)、PDServer(调度器)和TiKVServer(存储引擎)组成;支持无缝的水平扩展和ACID事务;与MySQL高度兼容</div> </li> <li><a href="/article/1950078131993899008.htm" title="C#返回两个数组或多个数组,函数方法的一种写法" target="_blank">C#返回两个数组或多个数组,函数方法的一种写法</a> <span class="text-muted">zhannghong2003</span> <a class="tag" taget="_blank" href="/search/C%23/1.htm">C#</a><a class="tag" taget="_blank" href="/search/c%23/1.htm">c#</a> <div>public(float[]x,float[]y)GetPolygonCollider2DPoints(PolygonCollider2Dobjects){Vector2[]points=objects.points;float[]x=newfloat[points.Length];float[]y=newfloat[points.Length];for(inti=0;i<points.Lengt</div> </li> <li><a href="/article/1950077375941242880.htm" title="python3中,pycharm中怎么连接数据库" target="_blank">python3中,pycharm中怎么连接数据库</a> <span class="text-muted">weixin_33736832</span> <a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E5%BA%93/1.htm">数据库</a><a class="tag" taget="_blank" href="/search/python/1.htm">python</a><a class="tag" taget="_blank" href="/search/%E5%BC%80%E5%8F%91%E5%B7%A5%E5%85%B7/1.htm">开发工具</a> <div>因为python3现在还不能直接连接数据库,所有如果想连接,就只能通过以下方法:在APP中的,__init__.py中,添加以下代码就可以:importpymysqlpymysql.install_as_MySQLdb()当然前提是,那就的在setting.py中连接数据库添加所连接的mysql数据库的详细信息,如下:DATABASES={'default':{'ENGINE':'django.d</div> </li> <li><a href="/article/1950072074810748928.htm" title="计算机考研408真题解析(2023-09 深入解析散列表线性探测与惰性删除)" target="_blank">计算机考研408真题解析(2023-09 深入解析散列表线性探测与惰性删除)</a> <span class="text-muted">良师408</span> <a class="tag" taget="_blank" href="/search/%E8%80%83%E7%A0%94/1.htm">考研</a><a class="tag" taget="_blank" href="/search/%E6%95%A3%E5%88%97%E8%A1%A8/1.htm">散列表</a><a class="tag" taget="_blank" href="/search/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/1.htm">数据结构</a><a class="tag" taget="_blank" href="/search/408%E7%9C%9F%E9%A2%98/1.htm">408真题</a><a class="tag" taget="_blank" href="/search/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%80%83%E7%A0%94/1.htm">计算机考研</a> <div>【良师408】计算机考研408真题解析(2023-09深入解析散列表线性探测与惰性删除)传播知识,做懂学生的好老师1.【哔哩哔哩】(良师408)2.【抖音】(良师408)goodteacher4083.【小红书】(良师408)4.【CSDN】(良师408)goodteacher4085.【微信】(良师408)goodteacher408特别提醒:【良师408】所收录真题根据考生回忆整理,命题版权归</div> </li> <li><a href="/article/1950068866143023104.htm" title="十大名牌羽绒服排行榜,哪些品牌的羽绒服比较好" target="_blank">十大名牌羽绒服排行榜,哪些品牌的羽绒服比较好</a> <span class="text-muted">高省张导师</span> <div>羽绒服品牌排行榜前十名:波司登、CanadaGoose(加拿大鹅)、TheNorthFace北面、Moncler盟可睐、鸭鸭YAYA、雅鹿、雪中飞SnowFlying、艾莱依ERAL、UNIQLO优衣库、千仞岗CHERICOM。大家好,我是高省APP最大团队,【高省】是一个可省钱佣金高,能赚钱有收益的平台,百度有几百万篇报道,也期待你的加入。高省邀请码520888,注册送2皇冠会员,送万元推广大礼</div> </li> <li><a href="/article/1950052560144494592.htm" title="今日欧美圈:哈卷新专登顶专辑榜,绵羊姐一月宣布新专?" target="_blank">今日欧美圈:哈卷新专登顶专辑榜,绵羊姐一月宣布新专?</a> <span class="text-muted">胡萝卜音乐</span> <div>EllieGoulding在推特互动中提到,将在明年1月宣布新专辑的相关信息。再来一场睡衣派对?NattiNatasha与BeckyG在推特互动展望再度合作!哈卷HarryStyles个人二专《FineLine》美国首周以47.6万总销量空降Billboard专辑榜冠军!这是今年第三高的单周销量!同时这张专辑空降了英国专辑榜季军和澳大利亚、爱尔兰、新西兰、荷兰、瑞典等国的专辑榜冠军!在EdShee</div> </li> <li><a href="/article/1950051777327984640.htm" title="ROS2编写一个简单的插件" target="_blank">ROS2编写一个简单的插件</a> <span class="text-muted">CrimsonEmber</span> <a class="tag" taget="_blank" href="/search/ROS%E7%AC%94%E8%AE%B0/1.htm">ROS笔记</a><a class="tag" taget="_blank" href="/search/ROS2/1.htm">ROS2</a><a class="tag" taget="_blank" href="/search/%E7%AC%94%E8%AE%B0/1.htm">笔记</a><a class="tag" taget="_blank" href="/search/%E5%AD%A6%E4%B9%A0/1.htm">学习</a> <div>1.createabaseclasspackageros2pkgcreate--build-typeament_cmake--licenseApache-2.0--dependenciespluginlib--node-namearea_nodepolygon_base编辑ros2_ws/src/polygon_base/include/polygon_base/regular_polygon.h</div> </li> <li><a href="/article/1950045851674013696.htm" title="openssl-1.1.1w-win64" target="_blank">openssl-1.1.1w-win64</a> <span class="text-muted">创想未来CTF</span> <a class="tag" taget="_blank" href="/search/Qt/1.htm">Qt</a><a class="tag" taget="_blank" href="/search/C%2B%2B/1.htm">C++</a><a class="tag" taget="_blank" href="/search/https/1.htm">https</a> <div>下载地址:ICSDownload-Overbyte解压后添加环境变量或放在目录“C:\Windows\System32”下其他版本下载GitGodeOpenSSL1.1.1g安装包</div> </li> <li><a href="/article/77.htm" title="算法 单链的创建与删除" target="_blank">算法 单链的创建与删除</a> <span class="text-muted">换个号韩国红果果</span> <a class="tag" taget="_blank" href="/search/c/1.htm">c</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a> <div> 先创建结构体 struct student { int data; //int tag;//标记这是第几个 struct student *next; }; // addone 用于将一个数插入已从小到大排好序的链中 struct student *addone(struct student *h,int x){ if(h==NULL) //?????? </div> </li> <li><a href="/article/204.htm" title="《大型网站系统与Java中间件实践》第2章读后感" target="_blank">《大型网站系统与Java中间件实践》第2章读后感</a> <span class="text-muted">白糖_</span> <a class="tag" taget="_blank" href="/search/java%E4%B8%AD%E9%97%B4%E4%BB%B6/1.htm">java中间件</a> <div>       断断续续花了两天时间试读了《大型网站系统与Java中间件实践》的第2章,这章总述了从一个小型单机构建的网站发展到大型网站的演化过程---整个过程会遇到很多困难,但每一个屏障都会有解决方案,最终就是依靠这些个解决方案汇聚到一起组成了一个健壮稳定高效的大型系统。          看完整章内容,</div> </li> <li><a href="/article/331.htm" title="zeus持久层spring事务单元测试" target="_blank">zeus持久层spring事务单元测试</a> <span class="text-muted">deng520159</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/DAO/1.htm">DAO</a><a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/jdbc/1.htm">jdbc</a> <div>今天把zeus事务单元测试放出来,让大家指出他的毛病, 1.ZeusTransactionTest.java 单元测试   package com.dengliang.zeus.webdemo.test; import java.util.ArrayList; import java.util.List; import org.junit.Test; import </div> </li> <li><a href="/article/458.htm" title="Rss 订阅 开发" target="_blank">Rss 订阅 开发</a> <span class="text-muted">周凡杨</span> <a class="tag" taget="_blank" href="/search/html/1.htm">html</a><a class="tag" taget="_blank" href="/search/xml/1.htm">xml</a><a class="tag" taget="_blank" href="/search/%E8%AE%A2%E9%98%85/1.htm">订阅</a><a class="tag" taget="_blank" href="/search/rss/1.htm">rss</a><a class="tag" taget="_blank" href="/search/%E8%A7%84%E8%8C%83/1.htm">规范</a> <div>                RSS是 Really Simple Syndication的缩写(对rss2.0而言,是这三个词的缩写,对rss1.0而言则是RDF Site Summary的缩写,1.0与2.0走的是两个体系)。   RSS</div> </li> <li><a href="/article/585.htm" title="分页查询实现" target="_blank">分页查询实现</a> <span class="text-muted">g21121</span> <a class="tag" taget="_blank" href="/search/%E5%88%86%E9%A1%B5%E6%9F%A5%E8%AF%A2/1.htm">分页查询</a> <div>在查询列表时我们常常会用到分页,分页的好处就是减少数据交换,每次查询一定数量减少数据库压力等等。 按实现形式分前台分页和服务器分页: 前台分页就是一次查询出所有记录,在页面中用js进行虚拟分页,这种形式在数据量较小时优势比较明显,一次加载就不必再访问服务器了,但当数据量较大时会对页面造成压力,传输速度也会大幅下降。 服务器分页就是每次请求相同数量记录,按一定规则排序,每次取一定序号直接的数据</div> </li> <li><a href="/article/712.htm" title="spring jms异步消息处理" target="_blank">spring jms异步消息处理</a> <span class="text-muted">510888780</span> <a class="tag" taget="_blank" href="/search/jms/1.htm">jms</a> <div>spring JMS对于异步消息处理基本上只需配置下就能进行高效的处理。其核心就是消息侦听器容器,常用的类就是DefaultMessageListenerContainer。该容器可配置侦听器的并发数量,以及配合MessageListenerAdapter使用消息驱动POJO进行消息处理。且消息驱动POJO是放入TaskExecutor中进行处理,进一步提高性能,减少侦听器的阻塞。具体配置如下: </div> </li> <li><a href="/article/839.htm" title="highCharts柱状图" target="_blank">highCharts柱状图</a> <span class="text-muted">布衣凌宇</span> <a class="tag" taget="_blank" href="/search/hightCharts/1.htm">hightCharts</a><a class="tag" taget="_blank" href="/search/%E6%9F%B1%E5%9B%BE/1.htm">柱图</a> <div>第一步:导入 exporting.js,grid.js,highcharts.js;第二步:写controller   @Controller@RequestMapping(value="${adminPath}/statistick")public class StatistickController {  private UserServi</div> </li> <li><a href="/article/966.htm" title="我的spring学习笔记2-IoC(反向控制 依赖注入)" target="_blank">我的spring学习笔记2-IoC(反向控制 依赖注入)</a> <span class="text-muted">aijuans</span> <a class="tag" taget="_blank" href="/search/spring/1.htm">spring</a><a class="tag" taget="_blank" href="/search/mvc/1.htm">mvc</a><a class="tag" taget="_blank" href="/search/Spring+%E6%95%99%E7%A8%8B/1.htm">Spring 教程</a><a class="tag" taget="_blank" href="/search/spring3+%E6%95%99%E7%A8%8B/1.htm">spring3 教程</a><a class="tag" taget="_blank" href="/search/Spring+%E5%85%A5%E9%97%A8/1.htm">Spring 入门</a> <div>IoC(反向控制 依赖注入)这是Spring提出来了,这也是Spring一大特色。这里我不用多说,我们看Spring教程就可以了解。当然我们不用Spring也可以用IoC,下面我将介绍不用Spring的IoC。 IoC不是框架,她是java的技术,如今大多数轻量级的容器都会用到IoC技术。这里我就用一个例子来说明: 如:程序中有 Mysql.calss 、Oracle.class 、SqlSe</div> </li> <li><a href="/article/1093.htm" title="TLS java简单实现" target="_blank">TLS java简单实现</a> <span class="text-muted">antlove</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/ssl/1.htm">ssl</a><a class="tag" taget="_blank" href="/search/keystore/1.htm">keystore</a><a class="tag" taget="_blank" href="/search/tls/1.htm">tls</a><a class="tag" taget="_blank" href="/search/secure/1.htm">secure</a> <div>  1. SSLServer.java package ssl; import java.io.FileInputStream; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; import java.security.KeyStore; import </div> </li> <li><a href="/article/1220.htm" title="Zip解压压缩文件" target="_blank">Zip解压压缩文件</a> <span class="text-muted">百合不是茶</span> <a class="tag" taget="_blank" href="/search/Zip%E6%A0%BC%E5%BC%8F%E8%A7%A3%E5%8E%8B/1.htm">Zip格式解压</a><a class="tag" taget="_blank" href="/search/Zip%E6%B5%81%E7%9A%84%E4%BD%BF%E7%94%A8/1.htm">Zip流的使用</a><a class="tag" taget="_blank" href="/search/%E6%96%87%E4%BB%B6%E8%A7%A3%E5%8E%8B/1.htm">文件解压</a> <div>   ZIP文件的解压缩实质上就是从输入流中读取数据。Java.util.zip包提供了类ZipInputStream来读取ZIP文件,下面的代码段创建了一个输入流来读取ZIP格式的文件; ZipInputStream in = new ZipInputStream(new FileInputStream(zipFileName));     &n</div> </li> <li><a href="/article/1347.htm" title="underscore.js 学习(一)" target="_blank">underscore.js 学习(一)</a> <span class="text-muted">bijian1013</span> <a class="tag" taget="_blank" href="/search/JavaScript/1.htm">JavaScript</a><a class="tag" taget="_blank" href="/search/underscore/1.htm">underscore</a> <div>        工作中需要用到underscore.js,发现这是一个包括了很多基本功能函数的js库,里面有很多实用的函数。而且它没有扩展 javascript的原生对象。主要涉及对Collection、Object、Array、Function的操作。       学</div> </li> <li><a href="/article/1474.htm" title="java jvm常用命令工具——jstatd命令(Java Statistics Monitoring Daemon)" target="_blank">java jvm常用命令工具——jstatd命令(Java Statistics Monitoring Daemon)</a> <span class="text-muted">bijian1013</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/jvm/1.htm">jvm</a><a class="tag" taget="_blank" href="/search/jstatd/1.htm">jstatd</a> <div>1.介绍         jstatd是一个基于RMI(Remove Method Invocation)的服务程序,它用于监控基于HotSpot的JVM中资源的创建及销毁,并且提供了一个远程接口允许远程的监控工具连接到本地的JVM执行命令。         jstatd是基于RMI的,所以在运行jstatd的服务</div> </li> <li><a href="/article/1601.htm" title="【Spring框架三】Spring常用注解之Transactional" target="_blank">【Spring框架三】Spring常用注解之Transactional</a> <span class="text-muted">bit1129</span> <a class="tag" taget="_blank" href="/search/transactional/1.htm">transactional</a> <div>Spring可以通过注解@Transactional来为业务逻辑层的方法(调用DAO完成持久化动作)添加事务能力,如下是@Transactional注解的定义:   /* * Copyright 2002-2010 the original author or authors. * * Licensed under the Apache License, Version </div> </li> <li><a href="/article/1728.htm" title="我(程序员)的前进方向" target="_blank">我(程序员)的前进方向</a> <span class="text-muted">bitray</span> <a class="tag" taget="_blank" href="/search/%E7%A8%8B%E5%BA%8F%E5%91%98/1.htm">程序员</a> <div>作为一个普通的程序员,我一直游走在java语言中,java也确实让我有了很多的体会.不过随着学习的深入,java语言的新技术产生的越来越多,从最初期的javase,我逐渐开始转变到ssh,ssi,这种主流的码农,.过了几天为了解决新问题,webservice的大旗也被我祭出来了,又过了些日子jms架构的activemq也开始必须学习了.再后来开始了一系列技术学习,osgi,restful.....</div> </li> <li><a href="/article/1855.htm" title="nginx lua开发经验总结" target="_blank">nginx lua开发经验总结</a> <span class="text-muted">ronin47</span> <div>使用nginx lua已经两三个月了,项目接开发完毕了,这几天准备上线并且跟高德地图对接。回顾下来lua在项目中占得必中还是比较大的,跟PHP的占比差不多持平了,因此在开发中遇到一些问题备忘一下 1:content_by_lua中代码容量有限制,一般不要写太多代码,正常编写代码一般在100行左右(具体容量没有细心测哈哈,在4kb左右),如果超出了则重启nginx的时候会报 too long pa</div> </li> <li><a href="/article/1982.htm" title="java-66-用递归颠倒一个栈。例如输入栈{1,2,3,4,5},1在栈顶。颠倒之后的栈为{5,4,3,2,1},5处在栈顶" target="_blank">java-66-用递归颠倒一个栈。例如输入栈{1,2,3,4,5},1在栈顶。颠倒之后的栈为{5,4,3,2,1},5处在栈顶</a> <span class="text-muted">bylijinnan</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a> <div> import java.util.Stack; public class ReverseStackRecursive { /** * Q 66.颠倒栈。 * 题目:用递归颠倒一个栈。例如输入栈{1,2,3,4,5},1在栈顶。 * 颠倒之后的栈为{5,4,3,2,1},5处在栈顶。 *1. Pop the top element *2. Revers</div> </li> <li><a href="/article/2109.htm" title="正确理解Linux内存占用过高的问题" target="_blank">正确理解Linux内存占用过高的问题</a> <span class="text-muted">cfyme</span> <a class="tag" taget="_blank" href="/search/linux/1.htm">linux</a> <div>Linux开机后,使用top命令查看,4G物理内存发现已使用的多大3.2G,占用率高达80%以上: Mem:   3889836k total,  3341868k used,   547968k free,   286044k buffers Swap:  6127608k total,&nb</div> </li> <li><a href="/article/2236.htm" title="[JWFD开源工作流]当前流程引擎设计的一个急需解决的问题" target="_blank">[JWFD开源工作流]当前流程引擎设计的一个急需解决的问题</a> <span class="text-muted">comsci</span> <a class="tag" taget="_blank" href="/search/%E5%B7%A5%E4%BD%9C%E6%B5%81/1.htm">工作流</a> <div>      当我们的流程引擎进入IRC阶段的时候,当循环反馈模型出现之后,每次循环都会导致一大堆节点内存数据残留在系统内存中,循环的次数越多,这些残留数据将导致系统内存溢出,并使得引擎崩溃。。。。。。       而解决办法就是利用汇编语言或者其它系统编程语言,在引擎运行时,把这些残留数据清除掉。</div> </li> <li><a href="/article/2363.htm" title="自定义类的equals函数" target="_blank">自定义类的equals函数</a> <span class="text-muted">dai_lm</span> <a class="tag" taget="_blank" href="/search/equals/1.htm">equals</a> <div>仅作笔记使用 public class VectorQueue { private final Vector<VectorItem> queue; private class VectorItem { private final Object item; private final int quantity; public VectorI</div> </li> <li><a href="/article/2490.htm" title="Linux下安装R语言" target="_blank">Linux下安装R语言</a> <span class="text-muted">datageek</span> <a class="tag" taget="_blank" href="/search/R%E8%AF%AD%E8%A8%80+linux/1.htm">R语言 linux</a> <div>命令如下:sudo gedit  /etc/apt/sources.list1、deb http://mirrors.ustc.edu.cn/CRAN/bin/linux/ubuntu/ precise/ 2、deb http://dk.archive.ubuntu.com/ubuntu hardy universesudo apt-key adv --keyserver ke</div> </li> <li><a href="/article/2617.htm" title="如何修改mysql 并发数(连接数)最大值" target="_blank">如何修改mysql 并发数(连接数)最大值</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a> <div>MySQL的连接数最大值跟MySQL没关系,主要看系统和业务逻辑了   方法一:进入MYSQL安装目录 打开MYSQL配置文件 my.ini 或 my.cnf查找 max_connections=100 修改为 max_connections=1000 服务里重起MYSQL即可   方法二:MySQL的最大连接数默认是100客户端登录:mysql -uusername -ppass</div> </li> <li><a href="/article/2744.htm" title="单一功能原则" target="_blank">单一功能原则</a> <span class="text-muted">dcj3sjt126com</span> <a class="tag" taget="_blank" href="/search/%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E7%9A%84%E7%A8%8B%E5%BA%8F%E8%AE%BE%E8%AE%A1/1.htm">面向对象的程序设计</a><a class="tag" taget="_blank" href="/search/%E8%BD%AF%E4%BB%B6%E8%AE%BE%E8%AE%A1/1.htm">软件设计</a><a class="tag" taget="_blank" href="/search/%E7%BC%96%E7%A8%8B%E5%8E%9F%E5%88%99/1.htm">编程原则</a> <div>单一功能原则[ 编辑]     SOLID 原则 单一功能原则 开闭原则 Liskov代换原则 接口隔离原则 依赖反转原则 查   论   编 在面向对象编程领域中,单一功能原则(Single responsibility principle)规定每个类都应该有</div> </li> <li><a href="/article/2871.htm" title="POJO、VO和JavaBean区别和联系" target="_blank">POJO、VO和JavaBean区别和联系</a> <span class="text-muted">fanmingxing</span> <a class="tag" taget="_blank" href="/search/VO/1.htm">VO</a><a class="tag" taget="_blank" href="/search/POJO/1.htm">POJO</a><a class="tag" taget="_blank" href="/search/javabean/1.htm">javabean</a> <div>POJO和JavaBean是我们常见的两个关键字,一般容易混淆,POJO全称是Plain Ordinary Java Object / Plain Old Java Object,中文可以翻译成:普通Java类,具有一部分getter/setter方法的那种类就可以称作POJO,但是JavaBean则比POJO复杂很多,JavaBean是一种组件技术,就好像你做了一个扳子,而这个扳子会在很多地方被</div> </li> <li><a href="/article/2998.htm" title="SpringSecurity3.X--LDAP:AD配置" target="_blank">SpringSecurity3.X--LDAP:AD配置</a> <span class="text-muted">hanqunfeng</span> <a class="tag" taget="_blank" href="/search/SpringSecurity/1.htm">SpringSecurity</a> <div>前面介绍过基于本地数据库验证的方式,参考http://hanqunfeng.iteye.com/blog/1155226,这里说一下如何修改为使用AD进行身份验证【只对用户名和密码进行验证,权限依旧存储在本地数据库中】。   将配置文件中的如下部分删除: <!-- 认证管理器,使用自定义的UserDetailsService,并对密码采用md5加密--> </div> </li> <li><a href="/article/3125.htm" title="mac mysql 修改密码" target="_blank">mac mysql 修改密码</a> <span class="text-muted">IXHONG</span> <a class="tag" taget="_blank" href="/search/mysql/1.htm">mysql</a> <div>$ sudo /usr/local/mysql/bin/mysqld_safe –user=root & //启动MySQL(也可以通过偏好设置面板来启动)$ sudo /usr/local/mysql/bin/mysqladmin -uroot password yourpassword //设置MySQL密码(注意,这是第一次MySQL密码为空的时候的设置命令,如果是修改密码,还需在-</div> </li> <li><a href="/article/3252.htm" title="设计模式--抽象工厂模式" target="_blank">设计模式--抽象工厂模式</a> <span class="text-muted">kerryg</span> <a class="tag" taget="_blank" href="/search/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/1.htm">设计模式</a> <div>抽象工厂模式:     工厂模式有一个问题就是,类的创建依赖于工厂类,也就是说,如果想要拓展程序,必须对工厂类进行修改,这违背了闭包原则。我们采用抽象工厂模式,创建多个工厂类,这样一旦需要增加新的功能,直接增加新的工厂类就可以了,不需要修改之前的代码。     总结:这个模式的好处就是,如果想增加一个功能,就需要做一个实现类,</div> </li> <li><a href="/article/3379.htm" title="评"高中女生军训期跳楼”" target="_blank">评"高中女生军训期跳楼”</a> <span class="text-muted">nannan408</span> <div>   首先,先抛出我的观点,各位看官少点砖头。那就是,中国的差异化教育必须做起来。    孔圣人有云:有教无类。不同类型的人,都应该有对应的教育方法。目前中国的一体化教育,不知道已经扼杀了多少创造性人才。我们出不了爱迪生,出不了爱因斯坦,很大原因,是我们的培养思路错了,我们是第一要“顺从”。如果不顺从,我们的学校,就会用各种方法,罚站,罚写作业,各种罚。军</div> </li> <li><a href="/article/3506.htm" title="scala如何读取和写入文件内容?" target="_blank">scala如何读取和写入文件内容?</a> <span class="text-muted">qindongliang1922</span> <a class="tag" taget="_blank" href="/search/java/1.htm">java</a><a class="tag" taget="_blank" href="/search/jvm/1.htm">jvm</a><a class="tag" taget="_blank" href="/search/scala/1.htm">scala</a> <div>直接看如下代码: package file import java.io.RandomAccessFile import java.nio.charset.Charset import scala.io.Source import scala.reflect.io.{File, Path} /** * Created by qindongliang on 2015/</div> </li> <li><a href="/article/3633.htm" title="C语言算法之百元买百鸡" target="_blank">C语言算法之百元买百鸡</a> <span class="text-muted">qiufeihu</span> <a class="tag" taget="_blank" href="/search/c/1.htm">c</a><a class="tag" taget="_blank" href="/search/%E7%AE%97%E6%B3%95/1.htm">算法</a> <div>中国古代数学家张丘建在他的《算经》中提出了一个著名的“百钱买百鸡问题”,鸡翁一,值钱五,鸡母一,值钱三,鸡雏三,值钱一,百钱买百鸡,问翁,母,雏各几何? 代码如下: #include <stdio.h> int main() { int cock,hen,chick; /*定义变量为基本整型*/ for(coc</div> </li> <li><a href="/article/3760.htm" title="Hadoop集群安全性:Hadoop中Namenode单点故障的解决方案及详细介绍AvatarNode" target="_blank">Hadoop集群安全性:Hadoop中Namenode单点故障的解决方案及详细介绍AvatarNode</a> <span class="text-muted">wyz2009107220</span> <a class="tag" taget="_blank" href="/search/NameNode/1.htm">NameNode</a> <div>正如大家所知,NameNode在Hadoop系统中存在单点故障问题,这个对于标榜高可用性的Hadoop来说一直是个软肋。本文讨论一下为了解决这个问题而存在的几个solution。 1. Secondary NameNode 原理:Secondary NN会定期的从NN中读取editlog,与自己存储的Image进行合并形成新的metadata image 优点:Hadoop较早的版本都自带,</div> </li> </ul> </div> </div> </div> <div> <div class="container"> <div class="indexes"> <strong>按字母分类:</strong> <a href="/tags/A/1.htm" target="_blank">A</a><a href="/tags/B/1.htm" target="_blank">B</a><a href="/tags/C/1.htm" target="_blank">C</a><a href="/tags/D/1.htm" target="_blank">D</a><a href="/tags/E/1.htm" target="_blank">E</a><a href="/tags/F/1.htm" target="_blank">F</a><a href="/tags/G/1.htm" target="_blank">G</a><a href="/tags/H/1.htm" target="_blank">H</a><a href="/tags/I/1.htm" target="_blank">I</a><a href="/tags/J/1.htm" target="_blank">J</a><a href="/tags/K/1.htm" target="_blank">K</a><a href="/tags/L/1.htm" target="_blank">L</a><a href="/tags/M/1.htm" target="_blank">M</a><a href="/tags/N/1.htm" target="_blank">N</a><a href="/tags/O/1.htm" target="_blank">O</a><a href="/tags/P/1.htm" target="_blank">P</a><a href="/tags/Q/1.htm" target="_blank">Q</a><a href="/tags/R/1.htm" target="_blank">R</a><a href="/tags/S/1.htm" target="_blank">S</a><a href="/tags/T/1.htm" target="_blank">T</a><a href="/tags/U/1.htm" target="_blank">U</a><a href="/tags/V/1.htm" target="_blank">V</a><a href="/tags/W/1.htm" target="_blank">W</a><a href="/tags/X/1.htm" target="_blank">X</a><a href="/tags/Y/1.htm" target="_blank">Y</a><a href="/tags/Z/1.htm" target="_blank">Z</a><a href="/tags/0/1.htm" target="_blank">其他</a> </div> </div> </div> <footer id="footer" class="mb30 mt30"> <div class="container"> <div class="footBglm"> <a target="_blank" href="/">首页</a> - <a target="_blank" href="/custom/about.htm">关于我们</a> - <a target="_blank" href="/search/Java/1.htm">站内搜索</a> - <a target="_blank" href="/sitemap.txt">Sitemap</a> - <a target="_blank" href="/custom/delete.htm">侵权投诉</a> </div> <div class="copyright">版权所有 IT知识库 CopyRight © 2000-2050 E-COM-NET.COM , All Rights Reserved. <!-- <a href="https://beian.miit.gov.cn/" rel="nofollow" target="_blank">京ICP备09083238号</a><br>--> </div> </div> </footer> <!-- 代码高亮 --> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shCore.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shLegacy.js"></script> <script type="text/javascript" src="/static/syntaxhighlighter/scripts/shAutoloader.js"></script> <link type="text/css" rel="stylesheet" href="/static/syntaxhighlighter/styles/shCoreDefault.css"/> <script type="text/javascript" src="/static/syntaxhighlighter/src/my_start_1.js"></script> </body> </html>