
个人博客http://selton.cn/
Docker教程 Build,Ship & Run anywhere. 更加优秀的页面展现请到Docker教程 Origin 早在十多年前国内外的一些大厂就开始投入研发和使用容器技术,比如Google,对他们来说,使用容器能够充分利用计算资源节省硬件成本,而这几年,真正把容器技术发扬光大的是Docker。 Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口 Docker的slogan“Build,Ship & Run anywhere”定位非常清晰,Docker的出现打破了传统运维模式里从打包到部署的过程中环境、语言、平台不一致的乱象,将这一整套开发运维模式标准化了,从而真正帮助企业实践了DevOps和微服务化。 但是,国内的大型企业面临技术转型的的时候,历史包袱太沉重,对Docker的接受和部署非常缓慢。同样地,多数的中小型企业也并未把Docker作为生产环境上部署和管理服务的标配,而选择继续使用传统的运维方案。 造成这种现象的原因是什么呢?可能原因之一是企业从业人员对Docker的学习认知并不够。之二是目前市面上Docker的容器编排系统很多,常见的就有K8s、Mesos、Swarm、Rancher、Newben,编排系统的学习和认知成本也进一步提升了企业应用Docker的难度 本文作者同大家一起开始步入docker的镜像世界,一步一步,深入docker,从入门到精通,从使用到理解掌握 quick-start 安装Docker 使用Docker 安装 mac平台 RedHat系Linux平台 win10平台 Mac Docker基本安装 mac安装了homebrew的话 直接brew cask install docker(我的没有成功,就去官网下载了) 官网下载需要登录帐号 下载的是ce版本 安装完后 启动终端后,通过命令可以是否安装成功 docker info 以及查看docker版本 docker --version Docker中配置国内镜像 在正常情况下,docker有一个默认连接的国外官方镜像,在国外的网友访问该官方镜像自然不成问题,但是国内毕竟不是国外,由于国情不同,中国的网络访问国外官方镜像网速一向很慢,而且往往还会遭遇断网的窘境,所以说我们要想正常使用docker的镜像,那么我们就不得不配置相应的国内镜像。 Docker可以配置的国内镜像有很多可供选择,比如说:阿里云,网易蜂巢,DaoCloud,Docker中国区官方镜像等,这些都是可以提供给大家随意选择的不错的镜像仓库。 在任务栏点击 Docker for mac 应用图标(右上方) -> Perferences... -> Daemon -> Registry mirrors 在列表中填写加速器地址即可。用的是网易的http://hub-mirror.c.163.com 修改完成之后,点击 Apply & Restart 按钮,Docker 就会重启并应用配置的镜像地址了。 RedHat Docker基本安装 使用yum包管理工具安装 yum install -y docker 启动docker服务 systemctl start docker 查看是否安装成功 docker --version Docker中配置国内镜像 使用vi修改 /etc/docker/daemon.json 文件 { "registry-mirrors": ["http://hub-mirror.c.163.com"] } 配置完之后执行下面的命令,以使docker的配置文件生效 systemctl daemon-reload systemctl restart docker docker info可以查看到修改过的配置 Registry Mirrors: http://hub-mirror.c.163.com Win10 Docker基本安装 Docker中配置国内镜像 使用 构建基本环境 安装成功之后,打开终端 确定你想要的操作系统,如果是centos docker search centos NAME DESCRIPTION STARS OFFICIAL AUTOMATED centos The official build of CentOS. 4773 [OK] 复制这个名字centos docker seach会在dockerhub,dockerhub(dockerhub类似于github,github大部分用于提交同步代码,dockerhub用于镜像同步与存储)中寻找name和docker search str的这个str相接近的字符串 镜像:这里可以理解为一个压缩包,这有助于理解 然后我们将这个远程库中的镜像下拉到本地,在前期,你可以将镜像理解为一个压缩包,这里,就是centos系统的压缩包 docker pull centos 即可获取到最新的centos版本的镜像 如果需要指明版本 docker pull centos:版本号 即可获取到相应版本 docker image ls 命令简化:docker images 查看可以使用的所有image的列表 REPOSITORY TAG IMAGE ID CREATED SIZE centos latest 5182e96772bf 8 weeks ago 200MB 可以看到我们刚刚pull下来的镜像 TAG表示的版本号,latest表示是最新版,IMAGE ID唯一确定这个镜像,以后都是用这个序列号表示这个镜像 CREATED表示这个镜像是什么时候被创建出来的 SIZE表示这个镜像的大小 接下来我们需要'解压'这个镜像 docker run -it --name mycentos centos /bin/bash 解释: Docker run 命令用来创建一个新的容器并运行,相当于 docker create和docker start的组合。 用 docker run --help可以显示命令的使用说明。 -i, --interactive Keep STDIN open even if not attached(如果没有打开交互界面,则打开) -t, --tty=false 分配tty设备,该可以支持终端登录,默认为false 带着-it参数会打开一个命令行窗口,退出这个窗口就相当于是'关机',不过还可以通过docker start的方式'开机',这也就是docker的前台运行方式,后台运行之后会提到 既然我们要解压这个镜像,就一定得知道这个镜像('压缩包')是谁,centos参数就可以唯一的确定这个'压缩包',可以唯一标识的只有name:tag或者id,如果像这样只写了name,没有注明tag,一律表示最新版latest,而这里我们正好是latest --name mycentos --name参数后跟我们解压后的东西的名字mycentos是名字,这样mycentos就能唯一的确定这个解压后的东西,实际上我们之前说了,'压缩包'里压缩的就是一个centos操作系统,所以解压后的东西就是一个centos操作系统 /bin/bash 这是表示载入容器后运行bash ,docker中必须要保持一个进程的运行,要不然整个容器就会退出。 这个就表示启动容器后启动bash。 好的,介绍完了,让我们实际操作试试看 再次确认我们上一步push下来的image docker image ls REPOSITORY TAG IMAGE ID CREATED SIZE centos latest 5182e96772bf 2 months ago 200MB docker run -it --name mycentos centos /bin/bash 如果运行成功,就会变成这样 zyfselton@MacBook-Pro-6:docker run -it --name mycentos centos /bin/bash [root@9f733e598d67 /]# 这就表示我们已经成功的创建并启动和进入了这个centos操作系统 9f733e598d67这个比我们给这个系统起得名字更能唯一标识,但是不容易记忆,需要每次自己查看 保存自己的环境 然后我们新建一个文件夹 mkdir myapp cd myapp mkdir work1 这里的新建文件夹是简单工作,主要是表示我们在这个系统里面做了一些事,我们需要测试我们做的这个事(这里是新建文件夹)能不能被保存到'压缩包'中去,当然你可以在里面安装环境,写笔记,运行程序等等,但是某些需要暴露端口给外部使用的这种服务的安装,后面会详细介绍 然后我们关闭这个窗口,或者输入exit也可以退出这个窗口 也就相当于关闭了刚才的操作系统 那我们的修改,或者说是工作内容会被清除吗 doker ps 会显示所有正在运行的os(Operation System操作系统) CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 发现没有 docker ps -a 会显示所有os(不管有没有在启动着) CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9f733e598d67 centos "/bin/bash" 9 minutes ago Exited (0) 2 minutes ago 发现这个id 9f733e598d67和我们之前启动os的id一样,恩,也算是确认过眼神了 这个时候我们要启动它 输入docker start后接 CONTAINER ID就会启动那个os docker start 9f733e598d67 此时docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9f733e598d67 centos "/bin/bash" 45 minutes ago Up 30 minutes mycentos 就会发现我们的系统启动了 可是我们还没有进入到这个系统 进入系统 docker exec -it 9f733e598d67 /bin/bash [root@9f733e598d67 /]# 这里的-it和/bin/bash和之前docker run的那个代表的意思类似 cd myapp cd work1 进入容器后的目录为workdir,默认workdir为自己的home(~)下,我们创建的时候在home下,所以地址是没有问题的 发现我们之前的工作内容都在 这个时候即便exit或者关闭窗口,之后再次进入安装docker环境的机器(就是宿主机,比如我的就是mac环境)的窗口时,我们docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9f733e598d67 centos "/bin/bash" 4 hours ago Up About an hour mycentos 然后我们可以将这个带有工作内容的os'压缩'成镜像,这样相当于有了一个压缩包,每次我们解压这个压缩包都会的到一个相应内容的os docker commit 9f733e598d67 selton/mycentos:1 记得这儿的selton/mycentos:1可以标识这个由9f733e598d67压缩包解压后得到的完整的os /之前的selton一定要是你的用户名,就是你去docker官网注册的帐号的用户名,没有注册一定要注册一个,不然之后我们无法将压缩包像git提交代码到仓库一样提交我们的压缩包到我们的库 docker image ls就会发现一个新的压缩包 REPOSITORY TAG IMAGE ID CREATED SIZE selton/mycentos 1 81bc6c9c1684 5 seconds ago 200MB 关闭掉刚才的容器 docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 9f733e598d67 centos "/bin/bash" 4 hours ago Up About an hour mycentos docker stop 9f733e598d67 将新的压缩包解压,看看里面有没有我们的工作内容 docker run -it --name mycentos2 selton/mycentos:1 /bin/bash --name后面的mycentos2是我们解压后的os的名字,selton/mycentos:1的selton/mycentos是name,1是tag,表示的是具体的某个压缩包 cd myapp cd work1 发现工作内容都在,至此,我们就明白了docker的主要作用 那么,我们的压缩包是在本地机器上的(这里是mac),但是开发环境和测试环境一定是不一样的,我们如何做到将我们的压缩包让开发环境获取,这就和代码一样,记得我们起初是怎么获得centos的吗 没错,回头看你会发现这个命令docker pull,这和git pull不光长得像,作用也是类似的,用于从远程库中获取到我们的压缩包,不过git中需要先建立本地库和特定远程库的关系,但是我们的docker的镜像库的远程库只有一个,就是dockerhub库 将自己的环境提交到远程 官方下载docker是需要帐号的,此时我们已经有了帐号,如果没有,请前往官网注册一个帐号 docker login 会提示让输入用户名:selton 然后就是输入密码 登录需要大概十秒的时间 之后会显示Login Succeeded 如果需要退出,docker logout Removing login credentials for https://index.docker.io/v1/ 如果你和我一样使用了统一密码管理工具,请记得粘贴使用窗口上的编辑->paste,而不是cv 登录成功之后推送我们的镜像到自己的库中 docker images REPOSITORY TAG IMAGE ID CREATED SIZE myos/mycentos 1 81bc6c9c1684 19 hours ago 200MB 查看到了我们制作的镜像('压缩包') 登录docker官网就可以看到多了一个你提交的这个镜像(''压缩包'') 也就是现在只要有一台机器安装了docker,就可以得到这个镜像('压缩包'),里面除了一个现成的os还有我们可能安装部署完的环境 同步远程库环境到本地 先尝试一下本地下载远程的我们提交的镜像 先删除掉本地的 docker images REPOSITORY TAG IMAGE ID CREATED SIZE selton/mycentos 1 e732a1e5c865 About an hour ago 200MB docker rmi e732a1e5c865 显示 Untagged: selton/mycentos:1 Untagged: selton/mycentos@sha256:8d264bbac07545d8933dcbab286bf343a52bf5a63426b5c4b9d944f4b9acc558 Deleted: sha256:e732a1e5c8652bbb8a48e2ffed6dee7c52df5dfc74f19b0c433b01f2a814417d Deleted: sha256:3f45206b758eae4a3864432e0e0fda23991d3956a779d4831c1f95dcb4d7191b docker images查看,ok docker pull selton/mycentos:1 就在下载了 同步远程库环境到linux上(开发,测试,生产) 我用的是一台阿里云的云服务器,1核2g内存,40g硬盘,centos7 安装docker yum install -y docker ok 启动docker服务 systemctl start docker docker --version 由于我们刚刚的库就和git的库道理一样,是公开的 无须登录 docker pull selton/mycentos:1 docker images可以查看到 REPOSITORY TAG IMAGE ID CREATED SIZE docker.io/selton/mycentos 1 e732a1e5c865 6 hours ago 200 MB 之后就和之前的操作一样了 我们再次重复一下以加深印象 现将这个image('压缩包')解压 docker run -it --name mycentos e732a1e5c865 /bin/bash ok,成功进入这个解压出来的container(os)中 cd myapp cd work1 ok,至此完成docker的入门使用
初级排序算法-定义排序规则 排序就是将一组对象按照某种逻辑序列重新排列的过程. Table of contents 介绍 为什么学它 排序算法类的模板 验证 性能评估 介绍 现在计算机的广泛使用使得数据无处不在,而整理数据的第一步通常就是进行排序 所有的计算机都实现了各种排序算法以供系统和用户使用 为什么学它 即使你只是使用标准库中的排序算法,学习排序算法仍然有三大实际意义 对排序算法的分析将有助于你全面理解比较算法性能的方法 类似的技术也能有效解决其他类型的问题 排序算法常常是我们使用算法解决其他问题的第一步 排序算法类的模板 package algorithms4.sort; import edu.princeton.cs.algs4.In; public class Example { public static void sort(Comparable[] a){ //排序算法 } private static boolean less(Comparable v, Comparable w){ return v.compareTo(w) < 0; } private static void exch(Comparable[] a, int i, int j){ Comparable t = a[i]; a[i] = a[j]; a[j] = t; } private static void show(Comparable[] a){ //在单行中打印数组 for (int i = 0; i < a.length; i++) { System.out.println(a[i] + " "); } System.out.println(); } public static boolean isSorted(Comparable[] a){ //测试数组元素是否有序 for (int i = 0; i < a.length; i++) { if (less(a[i], a[i -1])) { return false; } } return true; } public static void main(String[] args) { //从标准输入读取字符串,将他们排序并输出 String[] a = new In("需要排序的数据文件").readAllStrings(); sort(a); assert isSorted(a); show(a); } } 我们会将排序算法放在类的sort()方法中,该类还将包含辅助函数less类()和exch()(可能还会有其他辅助函数)以及一个示例用例main() 为了区别不同的排序算法,我们为相应的类取了不同的名字,例如Insertion.sort(),Merge.sort(),Quick.sort()等 这个类展示的是数组排序实现的框架,对于我们学习的每种排序算法,我们都会为这样一个类实现一个sort()方法并将example改为算法的名称 验证 通过assert isSorted(a);来确认排序后的数组元素都是有序的,尽管一般都会做简单地测试,并从数学上证明算法的正确性,但是实现每个算法时加上这条语句仍是必要的 如果我们只使用exch()来交换数组的元素,这个测试就足够了 但是当我们直接将值存入数组的方式(也就是原数组不变,得到另一个排序数组) 这条语句就无法提供足够的保证了(例如,凭空造出一个1,2,3数组,哈哈) 性能评估 运行时间.评估算法的性能,计算各个排序算法在不同的随机输入下的基本操作的次数(包括比较和交换,或者是读写数组的次数) 在研究排序算法时,我们需要计算比较和交换的数量,对于不交换的算法,我们会计算访问数组的次数 额外的内存使用 排序算法的额外内存开销和运行时间是同样重要的 排序算法可以分为两类 除了函数调用所需的栈和固定数量的实例变量之外无须额外内存的原地排序算法 以及需要额外内存来存储另一份数组副本的其他排序算法 数据类型 我们的排序算法模板适用于任何实现了Comparable接口的数据类型,遵循java惯例的好处是,很多你希望排序的数据都实现了comparable接口,例如基本类型,以及String和其他许多高级数据类型,(如file和url)都实现了comparable接口 因此,可以直接使用这些类型的数组作为参数调用我们的排序方法 在创建自己的数据类型时,我们只需要实现Comparable接口就能保证用例代码将其排序 实现comparable接口中的compareTo方法来定义目标对象类型的自然排序规则 如下面的date数据类型(@Data注解转向另一篇博客lombok) package algorithms4.sort; import lombok.Data; @Data public class DateExample implements Comparable<DateExample> { private final int month; private final int day; private final int year; public DateExample(int d, int m, int y){ day = d; month = m; year = y; } @Override public int compareTo(DateExample that) { if (this.year < that.year) { return -1; } else if (this.year > that.year) { return 1; } else if (this.month < that.month) { return -1; } else if (this.month > that.month) { return 1; } else if (this.day < that.day) { return -1; } else { return this.day > that.day ? 1 : 0; } } @Override public String toString(){ return year + "/" + month + "/" + day; } } compareto必须实现一个完整的比较接口 数学意义上 当两个元素满足 1.自反性,对于所有的v,v=v 2.反对称性,有v<w情况的存在,都有v>w的情况存在 3.传递性,如果v<=w,且w<=x,则v<=x compareto实现了我们的主键抽象,它给出了实现了comparable接口的任意数据类型的对象的大小顺序的定义
Lambda 来源于微积分数学中的 λ,其涵义是声明为了表达一个函数具体需要什么. Table of contents Introduction 使用 Introduction 什么是Lambda? 我们知道,对于一个Java变量,我们可以赋给其一个“值”。 如果你想把“一块代码”赋给一个Java变量,应该怎么做呢? 比如,我想把右边那块代码,赋给一个叫做aBlockOfCode的Java变量: 在Java 8之前,这个是做不到的。但是Java 8问世之后,利用Lambda特性,就可以做到了。 当然,这个并不是一个很简洁的写法。所以,为了使这个赋值操作更加elegant, 我们可以移除一些没用的声明。 这样,我们就成功的非常优雅的把“一块代码”赋给了一个变量。而“这块代码”,或者说“这个被赋给一个变量的函数”,就是一个Lambda表达式。 但是这里仍然有一个问题,就是变量aBlockOfCode的类型应该是什么? 在Java 8里面,所有的Lambda的类型都是一个接口,而Lambda表达式本身,也就是”那段代码“,需要是这个接口的实现。这是我认为理解Lambda的一个关键所在,简而言之就是,Lambda表达式本身就是一个接口的实现。直接这样说可能还是有点让人困扰,我们继续看看例子。我们给上面的aBlockOfCode加上一个类型: 这种只有一个接口函数需要被实现的接口类型,我们叫它”函数式接口“。为了避免后来的人在这个接口中增加接口函数导致其有多个接口函数需要被实现,变成"非函数接口”,我们可以在这个上面加上一个声明@FunctionalInterface, 这样别人就无法在里面添加新的接口函数了: 这样,我们就得到了一个完整的Lambda表达式声明: 使用 Lambda表达式有什么作用? 最直观的作用就是使得代码变得异常简洁。 我们可以对比一下Lambda表达式和传统的Java对同一个接口的实现: 这两种写法本质上是等价的。但是显然,Java 8中的写法更加优雅简洁。并且,由于Lambda可以直接赋值给一个变量,我们就可以直接把Lambda作为参数传给函数, 而传统的Java必须有明确的接口实现的定义,初始化才行: 有些情况下,这个接口实现只需要用到一次。传统的Java 7必须要求你定义一个“污染环境”的接口实现MyInterfaceImpl,而相较之下Java 8的Lambda, 就显得干净很多。 Lambda结合FunctionalInterface Lib, forEach, stream(),method reference等新特性可以使代码变的更加简洁! 直接上例子。 假设Person的定义和List的值都给定。 该注解见lombok 现在需要你打印出guiltyPersons List里面所有LastName以"Z"开头的人的FirstName。 原生态Lambda写法:定义两个函数式接口,定义一个静态函数,调用静态函数并给参数赋值Lambda表达式。 这个代码实际上已经比较简洁了,但是我们还可以更简洁么? 当然可以。在Java 8中有一个函数式接口的包,里面定义了大量可能用到的函数式接口(java.util.function (Java Platform SE 8 ))。所以,我们在这里压根都不需要定义NameChecker和Executor这两个函数式接口,直接用Java 8函数式接口包里的Predicate和Consumer就可以了——因为他们这一对的接口定义和NameChecker/Executor其实是一样的。 第一步简化 - 利用函数式接口包: 静态函数里面的for each循环其实是非常碍眼的。这里可以利用Iterable自带的forEach()来替代。forEach()本身可以接受一个Consumer 参数。 第二步简化 - 用Iterable.forEach()取代foreach loop: 由于静态函数其实只是对List进行了一通操作,这里我们可以甩掉静态函数,直接使用stream()特性来完成。stream()的几个方法都是接受Predicate,Consumer等参数的(java.util.stream (Java Platform SE 8 ))。你理解了上面的内容,stream()这里就非常好理解了,并不需要多做解释。 第三步简化 - 利用stream()替代静态函数: 对比最开始的Lambda写法,这里已经非常非常简洁了。但是如果,我们的要求变一下,变成print这个人的全部信息,及p -> System.out.println(p); 那么还可以利用Method reference来继续简化。所谓Method reference, 就是用已经写好的别的Object/Class的method来代替Lambda expression。格式如下: 第四步简化 - 如果是println(p),则可以利用Method reference代替forEach中的Lambda表达式: 这基本上就是能写的最简洁的版本了 Lambda配合Optional可以使Java对于null的处理变的异常优雅 这里假设我们有一个person object,以及一个person object的Optional wrapper: Optional如果不结合Lambda使用的话,并不能使原来繁琐的null check变的简单。 只有当Optional结合Lambda一起使用的时候,才能发挥出其真正的威力! 我们现在就来对比一下下面四种常见的null处理中,Java 8的Lambda+Optional和传统Java两者之间对于null的处理差异。 情况一 - 存在则开干 情况二 - 存在则返回,无则返回屁 情况三 - 存在则返回,无则由函数产生 情况四 - 夺命连环null检查 由上述四种情况可以清楚地看到,Optional+Lambda可以让我们少写很多ifElse块。尤其是对于情况四那种夺命连环null检查,传统java的写法显得冗长难懂,而新的Optional+Lambda则清新脱俗,清楚简洁 关于Java的Lambda, 还有东西需要讨论和学习。比如如何handle lambda exception,如何利用Lambda的特性来进行parallel processing等。总之,我只是一如既往地介绍个大概,让你大概知道,哦!原来是这样子就OK了。网上关于Lambda有很多相关的教程,多看多练。假以时日,必定有所精益。 转载自知乎用户Sevenvidia,来自Amazon,Lambda 表达式有何用处?如何使用?
mac编辑器vim美化 更加优秀的页面展现请到Mac编辑器vim美化 contents 环境 效果呈现 安装 quick start 环境 mac10.13.6,vim7(该版本mac自带的vim是7),git mac下vim的配置文件有两处 一处是所有用户通配的位置,位于/usr/share/vim下,名称为vimrc,无后缀 另一处位于~/.vim下,是当前用户的配置切换到这个用户时,这个配置会顶替上一种配置 为了不让自己的配置影响到别的使用者(假使他们没有配置自己的vimrc配置),建议修改当前用户下,也就是~/.vim/vimrc的配置 效果呈现 一般的vim界面 进行配置之后 界面效果由两个vim插件完成 配色由插件gruvbox完成,gruvbox有两种颜色模式,light和dark模式,可以设置 vim-airline插件完成页面最下方的当前页面进度百分比等信息 安装 为了更好地管理插件的安装,还需要另一个插件vundle帮助我们管理插件,类似于homebrew的作用 先安装vundle插件 git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim 件如果没有的话,在~/.vim下新建文件vimrc 在vimrc中写入 set nocompatible filetype off set rtp+=~/.vim/bundle/Vundle.vim call vundle#begin() call vundle#end() filetype plugin indent on 在call vundle#begin()和call vundle#end()之间写入需要加入的插件 为了管理vundle自己这个插件,加入了这一行,可以不加入 Plugin 'VundleVim/Vundle.vim' 加入皮肤 Plugin 'morhetz/gruvbox' 加入页面信息 Plugin 'vim-airline/vim-airline' 之后:wq保存一下 :PluginInstall就开始自动下载和安装相应插件 发现虽然有了页面信息,也就是vim-airline插件起了作用 但是gruvbox依然没有起作用 在Plugin 'morhetz/gruvbox'下加入这两行 colorscheme gruvbox set background=dark dark就是展示的配色,还有light 保存退出后再次打开vim,发现报错 大意是找不到名为gruvbox的color主题 如果发生这种情况,接下来需要手动完成主题的安装 平常vim自带的颜色主题在/usr/share/vim/vim80/colors下 我们需要将gruvbox的主题文件拷贝到这个目录下 为什么vundle安装失败,原因可能就是普通用户下vundle帮助你下载好了gruvbox之后没有权利将文件移动到/usr/share/vim/vim80/colors下 可以看到gruvbox插件是下载下来的 在/Users/zyfselton/.vim/bundle/gruvbox/colors下的gruvbox.vim 切换到root将gruvbox.vim复制到/usr/share/vim/vim80/colors下 保存退出vim,再次打开,发现配色和图片展示一致 快速使用 命令行执行git clone https://github.com/VundleVim/Vundle.vim.git ~/.vim/bundle/Vundle.vim 在~/.vim下新建文件vimrc set nocompatible filetype off set rtp+=~/.vim/bundle/Vundle.vim call vundle#begin() Plugin 'VundleVim/Vundle.vim' Plugin 'morhetz/gruvbox' colorscheme gruvbox set background=dark Plugin 'vim-airline/vim-airline' call vundle#end() filetype plugin indent on vim一般模式输入:PluginInstall 切换到root,将/Users/zyfselton/.vim/bundle/gruvbox/colors (你的用户名zyfselton需要替换掉)下的gruvbox.vim 复制到/usr/share/vim/vim80/colors下,完成
SSM搭建 SSM(Spring+SpringMVC+MyBatis)框架集由Spring、SpringMVC、MyBatis三个开源框架整合而成,常作为数据源较简单的web项目的框架。. SpringIoc · SpringMVC · Mybatis Table of contents 环境 搭建 使用 环境 jdk8 tomcat8 maven IDEA win7 搭建 导入web工程依赖 导入spring工程依赖 搭建基本包结构 配置resources配置文件 导入web工程依赖 将基本的web工程的依赖导入 <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.1</version> <scope>provided</scope> </dependency> <!--没有这个依赖会报错--> <!--java.lang.NoClassDefFoundError:org/springframework/dao/support/DaoSupport--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.3.11.RELEASE</version> </dependency> 导入spring工程依赖 将基本的spring工程所需要的依赖导入 springmvc依赖 <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.11.RELEASE</version> </dependency> 基本包结构 先搭建基本包的基本结构
Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载 Table of contents 安装 使用 在spring中使用 安装 下载下来memcached.exe 切换到memcached.exe所在路径 输入memcached -d install win + r 输入 services.msc打开window服务 随便选中一个输入memcached就可以查看到安装好的服务,右击启动它,然后关闭窗口 使用 新建java工程或者maven工程 导入三个必备的依赖,fastjson-1.2.3,slf4j-api-1.7.5,xmemcached-2.3.2 main方法中加入 MemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil.getAddresses("127.0.0.1:11211")); builder.setSessionLocator(new KetamaMemcachedSessionLocator()); try { MemcachedClient memcachedClient =builder.build(); //在这里写入测试代码 memcachedClient.shutdown(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } catch (MemcachedException e) { e.printStackTrace(); } catch (TimeoutException e) { e.printStackTrace(); } 测试一定时间后数据是否还能取出来 永久时间,测试重启服务之后先前存储的数据是否还能取出来 delete 自动增长 时效 memcachedClient.set("testTime",2,"testTimeValue"); String testTime = memcachedClient.get("testTime"); System.out.println("testTime = " + testTime); TimeUnit.SECONDS.sleep(4); System.out.println("4s 过去了"); String valueExist = memcachedClient.get(testTime); System.out.println("valueExist = " + valueExist); 输出: testTime = testTimeValue 4s 过去了 valueExist = null 恢复 //存储,然后关闭掉服务 memcachedClient.set("test",0,"testValue"); String testTime=memcachedClient.get("test"); System.out.println("testTime="+testTime); System.out.println("存储成功"); 输出: testTime = testValue 存储成功 //关闭掉服务后的重启 String testTime = memcachedClient.get("test"); System.out.println("testTime = " + testTime); System.out.println("取值失败"); 输出: testTime = null 取值失败 delete memcachedClient.set("test",0,"testValue"); String getVal = memcachedClient.get("test"); System.out.println("getVal = " + getVal); memcachedClient.delete("test"); System.out.println("after delete ..."); getVal = memcachedClient.get("test"); System.out.println("getVal = " + getVal); 输出: getVal = testValue after delete ... getVal = null 自动增长 //三个参数,第一个指定键,第二个指定递增的幅度大小,第三个指定当key不存在的情况下的初始值 for (int i = 0; i < 5; i++) { memcachedClient.incr("博客的赞",1,20); String point = memcachedClient.get("博客的赞"); System.out.println("point = " + point); } 输出: point = 20 point = 21 point = 22 point = 23 point = 24 关于incr的用法,值得警惕的是,它的值虽然看起来是一个数字,实际上正如代码中的String point = memcachedClient.get("博客的赞"); 其实是一个字符串,所以会出现如下错误 memcachedClient.set("博客的赞1",0,10); int str = memcachedClient.get("博客的赞1"); System.out.println("str1 = " + str); memcachedClient.incr("博客的赞1",2,22); str = memcachedClient.get("博客的赞1"); System.out.println("str2 = " + str); 输出: net.rubyeye.xmemcached.exception.MemcachedClientException: cannot increment or decrement non-numeric value,key=博客的赞1 at net.rubyeye.xmemcached.command.Command.decodeError(Command.java:267) .. at com.google.code.yanf4j.nio.impl.NioController.onRead(NioController.java:157) at com.google.code.yanf4j.nio.impl.Reactor.dispatchEvent(Reactor.java:323) at com.google.code.yanf4j.nio.impl.Reactor.run(Reactor.java:180) str1 = 10 输出的顺序不同,注意输出的异常栈信息的第一条和后面的几条就指明nio.impl.Reactor.run,线程的,这儿就不深入展开了 spring 新建一个maven工程 pom.xml 在resource中新建sping-config.xml spring单元测试代码骨架 测试代码 骨架 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring-config.xml") public class TestMem { @Autowired private MemcachedClient memcachedClient; @Test public void test1(){ //测试代码部分 } } 测试代码 memcachedClient.set("springData",3,"dataVal"); String str = memcachedClient.get("springData"); System.out.println("str = " + str); 输出: str = dataVal 还可以存储对象,不过该对象必须实现Serializable接口,不然会报错java.io.NotSerializableException: Teacher, 实现接口后 import lombok.Data; import java.io.Serializable; @Data public class Teacher implements Serializable { private int age; private String name; } 关于@Data关我在另一篇博客中有介绍lombok Teacher teacher = new Teacher(); teacher.setAge(3); teacher.setName("23"); memcachedClient.set("te", 0, teacher); Teacher teacher1 = memcachedClient.get("te"); System.out.println("teacher1 = " + teacher1); 输出: teacher1 = Teacher(age=3, name=23) pom.xml <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.selton</groupId> <artifactId>DemoMemSpring</artifactId> <version>1.0</version> <dependencies> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>1.18.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.11.RELEASE</version> </dependency> <dependency> <groupId>com.googlecode.xmemcached</groupId> <artifactId>xmemcached</artifactId> <version>2.0.0</version> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.3.11.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> </dependencies> </project> config <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd "> <bean id="memcachedClient" name="memcachedClient" class="net.rubyeye.xmemcached.utils.XMemcachedClientFactoryBean"> <property name="servers"> <!--配置端口,另加入的话,空格隔开--> <value>127.0.0.1:11211</value> </property> <property name="weights"> <list> <!--设置不同端口的权重,这里只有一个端口--> <value>1</value> </list> </property> <property name="sessionLocator"> <bean class="net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator"></bean> </property> <property name="transcoder"> <bean class="net.rubyeye.xmemcached.transcoders.SerializingTranscoder" /> </property> <property name="bufferAllocator"> <bean class="net.rubyeye.xmemcached.buffer.SimpleBufferAllocator"></bean> </property> </bean> </beans>
Solr Solr is the popular, blazing-fast, open source enterprise search platform built on Apache Lucene™.. Table of contents 开发环境 部署到tomcat 开发环境 Tomcat8. Solr7. Jdk8. solr5以及以上的版本就需要tomcat8以及jdk8往上了 部署 在tomcat-webapps文件夹下新建文件夹solr并将solr-7.1.0\server\solr-webapp\webapp文件夹下的内容一并拷贝到solr文件夹下 在\solr\WEB-INF下新建文件夹classes并将\solr-7.1.0\example\resources下的log4j.properties文件拷贝到此文件夹 将\solr-7.1.0\server\lib下的metrics开头的五个jar和\solr-7.1.0\server\lib\ext文件夹下的所有jar包复制到tomcat下\solr\WEB-INF\lib文件夹下 在tomcat根目录新建文件夹solr_home并将\solr-7.1.0\server\solr下的文件全部拷贝到solr_home下,此文件夹下内容为solr的一个实例。 修改solr服务器目录中web.xml文件指定solr_home所在位置,在web.xml中有一段被注释掉了,解开注释,并且将的内容替换为solr_home的路径 去掉权限,web.xml中最后的标签内容注释掉 启动tomcat,solr会借助jetty自动启动solr服务实例,最后访问solr服务地址即可看到solr主页:http://localhost:8080/solr/index.html
Bootstrap Admin 效果展示 Table of contents Create Remove Update Export Tree Create 相关插件 bootstrap-validator 演示 Remove 相关插件 bootstrap-multiselect bootstrap-table 演示 Update 相关插件 bootstrap-table bootstrap-editable bootstrap-table-editable 演示 Export 相关插件 tableExport bootstrap-table-export 演示 Tree 相关插件 bootstrap-treeview 演示
Log4j2 Apache Log4j 2 is an upgrade to Log4j that provides significant improvements over its predecessor Table of contents 环境搭建 配置详解 环境搭建 一般java工程: Download 配置到环境 配置文件路径 下载 apache官网给的链接是清华镜像网站的,国外的很多开源的在国内太慢的话不妨尝试一下国内的最大镜像站清华镜像站,下载地址log4j-2.11.0, 配置到环境 To use Log4j 2 in your application make sure that both the API and Core jars are in the application’s classpath. Add the dependencies listed below to your classpath. -----这是官网的原话,翻译过来就是,使用log4j2的方式是,将接口jar和实现jar放到所需项目的classpath中,并添加依赖,具体就是log4j-api-2.11.0.jar和log4j-core-2.11.0.jar 配置文件路径 log4j2.xml可以放在任意的地方,只要你最后把它放到了classpath里,上面的项目中新建一个resources目录用于放置log4j2.xml,如果在未加入classpath时尝试运行时会报如下错误: ERROR StatusLogger No Log4j 2 configuration file found. Using default configuration (logging only errors to the console), or user programmatically provided configurations. Set system property 'log4j2.debug' to show Log4j 2 internal initialization logging. See <https://logging.apache.org/log4j/2.x/manual/configuration.html> for instructions on how to configure Log4j 2 大义就是运行的项目没有在classpath 中找到你的配置文件 以Eclipse环境为例,可以在Run—Run Configurations对应项目的Classpath中选择User Entries,点击Advanced,选择Add Folder,把resources文件夹添加进来。 idea环境 熟悉idea的话点击右上方放大镜图标左侧的图标 再点击左侧边栏Project Settings下的Modules 扩展一下,点了之后,右侧边栏区域 表示的是已作为classpath的文件夹,这里可以从已设置为classpath的文件夹中取消选择, 取消掉以后记得按下下方的apply保存一下 选中右侧出现的工程目录中你用来存放配置文件log4j2.xml中的文件夹,点击一下选中,再点击resource,右侧边栏就会多出一下你选中的文件,点下方的apply即可 配置详解 <?xml version="1.0" encoding="UTF-8"?> <!-- monitorInterval,配置为120,单位为秒。即在服务运行过程中发生了log4j2配置文件的修改,log4j2能够在monitorInterval时间范围重新加载配置,无需重启应用。--> <Configuration status="WARN" monitorInterval="120"> <properties> <!--当输出到文件中的时候使用LOG_HOME替代输出到的文件夹,类似于配置java环境的时候的JAVA_HOME的做法--> <!--最好是填写相对路径,基路径是当前项目也就是src的上一级别--> <!--如果写成testlog/mylog,即使没有testlog,也会在src同级目录下新建出testlog文件夹以及其下的mylog文件夹--> <property name="LOG_HOME">testlog/mylog</property> </properties> <!--翻译:附加器,记录方式--> <Appenders> <!--appenders里的两个属性,分别为name=Console和name=log(两个名字是自己起的)--> <!--appenders属性同级的loggers中的root的level的值控制输出信息的严格级别,一般是info--> <!--root中的AppenderRef的ref写appenders中的name,在这里也就是添Console或log--> <!--name是自己命名的,target=SYSTEM_OUT表示输出到控制台--> <Console name="Console" target="SYSTEM_OUT"> <!--pattern控制格式化输出的格式--> <!--例子:在代码中写入logger.info("info级别信息");--> <!--输出:12:8:34.501 [main] INFO com.selton.Log4jTest - info级别信息--> <PatternLayout pattern="%d{H:m:s.S} [%t] %-5level %logger{36} - %msg%n"/> </Console> <!--临时日志生成--> <!--<File name="log" fileName="log/test.log" append="true"> <PatternLayout pattern="%d{H:m:s.S} [%t] %-5level %logger{36} - %msg%n"/> </File>--> <!--fileName:日志存储路径, filePattern:历史日志封存路径。其中%d{yyyyMMddHH}表示了封存历史日志的时间单位(目前单位为小时,yyyy表示年,MM表示月,dd表示天,HH表示小时,mm表示分钟,ss表示秒,SS表示毫秒)。 注意后缀,log4j2自动识别zip等后缀,表示历史日志需要压缩。--> <RollingRandomAccessFile name="File" immediateFlush="true" fileName="${LOG_HOME}/today.log" filePattern="${LOG_HOME}/history-%d{yyyy-MM-dd}.log"> <!-- level,表示最低接受的日志级别,配置为INFO,即我们期望打印INFO级别以上的日志。--> <!--onMatch,表示当日志事件的日志级别与level一致时,应怎么做。一般为ACCEPT,表示接受。--> <!--onMismatch,表示日志事件的日志级别与level不一致时,应怎么做。一般为DENY,表示拒绝。也可以为NEUTRAL表示中立。--> <Filters> <!--最下方的Root level="debug",如果不设置这句的话,4个级别信息都会打印,设置后,就会只打印INFO以及之上--> <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/> </Filters> <!--输出到文件夹中去--> <PatternLayout pattern="%d{y-M-d H:m:s.S} [%t] %-5level %logger{36} - %msg%n" /> <!--<HTMLLayout pattern="%d{y-M-d H:m:s.S} [%t] %-5level %logger{36} - %msg%n" />--> <!--必配项,TriggeringPolicy(触发策略) --> <Policies> <!--按天,划分日志文件--> <TimeBasedTriggeringPolicy interval="1" modulate="true" /> </Policies> <!--必配项,RolloverStrategy(覆盖策略)--> <!--<DefaultRolloverStrategy max="20"/>--> </RollingRandomAccessFile> </Appenders> <Loggers> <Root level="debug"> <AppenderRef ref="File"/> </Root> </Loggers> </Configuration>
通过点击事件获得li标签内容 Table of contents 随笔 随笔 <li onclick="liClick(this)">数据</li> //点击会显示'数据' <script> function liClick(data){ console.log($(data).text()); } </script>
Hibernate 随心所欲的使用面向对象思想操纵数据库. Table of contents 介绍 搭建开发环境 半sql半面向对象写法 完全的sql写法 完全的面向对象写法 Hibernate Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,它将POJO与数据库表建立映射关系,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维来操纵数据库,从而无需顾及数据库的实现究竟是SQLServer还是Mysql还是Oracle 搭建环境 搭建在一般工程中 jar下载 配置文件 jar下载 官网Hibernate进入后看到hibenate ORM,点击more,左侧边栏选择releases中的一个版本,页面最下方,选择download下载即可 官方jar包:lib文件:requeired文件里的所有jar拷贝到自己的新建工程中去,然后在加上连接数据库相关的包,mysql-connector 配置文件 Hibernate.cfg.xml放置在src下,需要修改url,username和password <?xmlversion='1.0'encoding='utf-8'?> <!DOCTYPEhibernate-configurationPUBLIC "-//Hibernate/HibernateConfigurationDTD//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <propertyname="connection.url">jdbc:mysql://localhost:3306/mycms</property> <propertyname="connection.driver_class">com.mysql.jdbc.Driver</property> <propertyname="connection.username">root</property> <propertyname="connection.password">123456</property> <mappingresource="com/selton/Node.hbm.xml"></mapping> </session-factory> </hibernate-configuration> <mappingresource="com/selton/Node.hbm.xml"></mapping> Node.hbm.xml 映射到具体的pojo,一个pojo配置一个映射的xml 名字和数据库的名字即使一样,也需要写上property的映射 <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.selton"> <class name="com.selton.Node" table="tree"> <id name="id" column="id"> <generator class="native"></generator> <!--可以实现自动增长,也就是将实体类存储到数据库的时候,少set一个主键 比较疑惑,反正有没有这句话,数据库那儿都需要自动增长--> </id> <property name="nodeId" column="nodeId"></property> <property name="pid" column="pid"></property> <property name="type" column="type"></property> <property name="url" column="url"></property> <property name="icon" column="icon"></property> <property name="description" column="description"></property> <property name="level" column="level"></property> <property name="name" column="name"></property> </class> </hibernate-mapping> pojo的主键属性名称对应到数据库实体的主键名称写在id中 ,其他的写在property中 半sql半面向对象写法 单个数据(对象)存储到数据库 查询单个对象 更新单个对象 删除对象 查询整个表 查询某个对象的某个属性 查询指定行数据 查询指定数据传回一个实体 分组聚合 排序 limit 使用参数 in 写在配置文件里 在一个入口方法或者测试类方法中,加入 //构建上下文换肩加配置连接池,开启事务 Configuration configuration = new Configuration().configure(); SessionFactory sessionFactory = configuration.buildSessionFactory(); Session session = sessionFactory.openSession(); Transaction transaction = session.beginTransaction(); //这里填写测试代码 //提交事务并关闭各种流 transaction.commit(); session.close(); sessionFactory.close(); 以下的测试代码,分别放在上面代码的`这里填写测试代码处 单个数据存储到数据库 Node node = new Node();; node.setNodeId("testNodeId1"); node.setPid("testPid1"); node.setType((byte)1); node.setLevel((short)1); node.setName("testName1"); session.save(node); 查询单个对象 Node node=(Node)session.get(Node.class,1); System.out.println(node); //Node.class后面的1是数据库中的主键值 更新单个对象 Node node=(Node)session.get(Node.class,10); node.setName("updateName"); session.update(node); 删除对象 Node node = (Node) session.get(Node.class, 10); session.delete(node); 查询整个表 这里需要注意,如果你的pojo叫 myuser,而数据库中对应的表叫user,所有使用createQuery的地方,涉及到了表,就该填myuser Query query = session.createQuery("FROM Node"); List list = query.list(); System.out.println("list = " + list); 查询某个对象的某个属性 Query query = session.createQuery("SELECT nodeId FROM Node"); List list = query.list(); System.out.println("list = " + list); 查询指定行数据 Query query = session.createQuery("FROM Node WHERE type=?"); query.setParameter(0,10); List list = query.list(); System.out.println("list = " + list); 查询指定数据传回一个实体 //需要pojo有相应的构造器 Query query = session.createQuery("SELECT new Node(id,name,nodeId) FROM Node"); List<Node> list = query.list(); System.out.println("list = " + list); 分组聚合 Query query = session.createQuery("SELECT type,SUM(id) FROM Node GROUP BY type"); List list = query.list(); for (Object o : list) { Object[] result = (Object[]) o; System.out.println(Arrays.toString(result)); } 排序 Query query = session.createQuery("FROM Node ORDER BY id DESC"); List list = query.list(); System.out.println("list = " + list); limit Query query = session.createQuery("FROM Node ORDER BY id DESC"); query.setFirstResult(2); query.setMaxResults(3); List list = query.list(); System.out.println("list = " + list); 使用参数 String colName = "id"; String sql = "FROM Node WHERE " + colName + "=?"; Query query = session.createQuery(sql); query.setParameter(0,6); Node node = (Node) query.uniqueResult(); System.out.println("node = " + node); 或者这种 Query query = session.createQuery("FROM Node WHERE id:id"); query.setParameter("id",7); List list = query.list(); System.out.println("list = " + list); in Query query = session.createQuery("FROM Node WHERE id IN(:ids)"); query.setParameterList("ids",new Object[]{4,6,7}); List list = query.list(); System.out.println("list = " + list); //In 的效率很低 写在配置文件里 User.hbm.xml <hibernate-mapping> ... <query name="getUserByAge"> FROM Node WHERE id between ? AND ? </query> ... </hibernate-mapping> 代码部分 Query query = session.getNamedQuery("getUserByAge"); query.setParameter(0,6); query.setParameter(1,8); List list = query.list(); System.out.println("list = " + list); 完全的sql写法 仿照第一种半sql写法,格式变化就可以 1.原生sql写法 SQLQuery query = session.createSQLQuery("SELECT * FROM tree"); query.addEntity(Node.class); List list = query.list(); System.out.println("list = " + list); 完全的面向对象写法 仿照第一种半sql写法,格式变化就可以 Criteria criteria = session.createCriteria(Node.class); criteria.add(Restrictions.eq("id",6)); List list = criteria.list(); System.out.println("list = " + list); 相当于查出来了所有的放在criteria里面 不等于 排序 添加分页 分组聚合 不等于 Criteria criteria = session.createCriteria(Node.class); criteria.add(Restrictions.ne("id",1)); List list = criteria.list(); System.out.println("list = " + list); 排序 Criteria criteria = session.createCriteria(Node.class); criteria.addOrder(Order.desc("id")); List list = criteria.list(); System.out.println("list = " + list); 添加分页 Criteria criteria = session.createCriteria(Node.class); criteria.add(Restrictions.ne("id",1)); criteria.setFirstResult(0); criteria.setMaxResults(2); List list = criteria.list(); System.out.println("list = " + list); 分组聚合 Criteria criteria = session.createCriteria(Node.class); ProjectionList projectionList = Projections.projectionList(); projectionList.add(Projections.sum("id")); projectionList.add(Projections.groupProperty("type")); criteria.setProjection(projectionList); List list = criteria.list(); for (Object o : list) { Object[] result = (Object[]) o; System.out.println(Arrays.toString(result)); }
Java多线程 部分转载自知乎用户 Snailclimb · 南理汉子 · 很好 Table of contents 进程和多线程简介 使用多线程 实例变量和线程安全 一些常用方法 如何停止一个线程 线程的优先级 Java多线程分类 简介 相关概念 多线程 概念 何为线程 何为进程 线程和进程有何不同 通俗说法 开个QQ,开了一个进程;开了迅雷,开了一个进程。在QQ的这个进程里,传输文字开一个线程、传输语音开了一个线程、弹出对话框又开了一个线程。所以运行某个软件,相当于开了一个进程。在这个软件运行的过程里(在这个进程里),多个工作支撑的完成QQ的运行,那么这“多个工作”分别有一个线程。所以一个进程管着多个线程。通俗的讲:“进程是爹妈,管着众多的线程儿子”... 线程 线程进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源。 进程 是具有一定独立功能的程序、它是系统进行资源分配和调度的一个独立单位,重点在系统调度和单独的单位,也就是说进程是可以独 立运行的一段程序。 线程和进程有何不同 关系: 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(通常说的主线程) 资源分配给进程,同一进程的所有线程共享该进程的所有资源 线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。 处理机分给线程,即真正在处理机上运行的是线程 线程是指进程内的一个执行单元,也是进程内的可调度实体 区别: 调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。 并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。 拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。 多线程 多线程 多线程就是几乎同时执行多个线程(一个处理器在某一个时间点上永远都只能是一个线程!即使这个处理器是多核的,除非有多个处理器才能实现多个线程同时运行。)。几乎同时是因为实际上多线程程序中的多个线程实际上是一个线程执行一会然后其他的线程再执行,并不是很多书籍所谓的同时执行。 为什么多线程是必要的 使用线程可以把占据长时间的程序中的任务放到后台去处理 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度 程序的运行速度可能加快 假设你的公司从上到下都不知道多线程有什么用,就你知道。你刚进公司。比如你的机器是intel i7 4核,你要跑一个运算4万次,每次都独立,不考虑IO负担。那么,单线程你只能用到一个核,耗时40分钟,你打开任务管理器,看到CPU利用率一直是25%。你修改程序,开4个线程,每个算1万次,你看到CPU利用率一直是100%,耗时10分钟。你告诉老板40分钟后完成任务,10分钟后你检查完毕起身去买了杯咖啡,回来开始刷知乎。30分钟过后刷过瘾了,你发信息告诉老板任务完成。老板说:Good job. 使用 继承Thread类 MyThread.java public class MyThread extends Thread { @Override public void run() { super.run(); int i = 1000; while (--i > 0) { System.out.println("|||||||||||||||||"); } System.out.println("MyThread"); } } Run.java public class Run { public static void main(String[] args) { MyThread mythread = new MyThread(); mythread.start(); System.out.println("运行结束"); } } 运行结果: 运行结束 ||||||||||||||||| ... ... ||||||||||||||||| MyThread 以共有两个线程,一个是main,一个是mythread,如果把他们两个比作赛跑选手,main选手要做的事情是,先开始跑,紧接着喊mythread一声,老哥,开始跑吧,然后再喊一声运行结束,他就完成了任务,而mythread需要执行完一个长循环才可以结束 实现Runnable接口 推荐实现Runnable接口方式开发多线程,因为Java单继承但是可以实现多个接口,也就是相比上一个方法,这个方法在实现了多线程的同时还多出了选择继承一个父类的权利 MyRunnable.java public class MyRunnable implements Runnable { @Override public void run() { System.out.println("MyRunnable"); } } Run.java public class Run { public static void main(String[] args) { Runnable runnable=new MyRunnable(); Thread thread=new Thread(runnable); thread.start(); System.out.println("运行结束!"); } } 运行结果 运行结束! MyRunnable 实例变量和线程安全 定义线程类中的实例变量针对其他线程可以有共享和不共享之分 不共享数据的情况 MyThread.java public class MyThread extends Thread { private int count = 5; public MyThread(String name) { super(); this.setName(name); } @Override public void run() { super.run(); while (count > 0) { count--; System.out.println("由 " + MyThread.currentThread().getName() + " 计算,count=" + count); } } } Run.java public class Run { public static void main(String[] args) { MyThread a = new MyThread("A"); MyThread b = new MyThread("B"); MyThread c = new MyThread("C"); a.start(); b.start(); c.start(); } } 运行结果 由 A 计算,count=4 由 B 计算,count=4 由 C 计算,count=4 由 B 计算,count=3 由 A 计算,count=3 由 A 计算,count=2 由 A 计算,count=1 由 A 计算,count=0 由 B 计算,count=2 由 C 计算,count=3 由 B 计算,count=1 由 C 计算,count=2 由 B 计算,count=0 由 C 计算,count=1 由 C 计算,count=0 可以看出每个线程都有一个属于自己的实例变量count,它们之间互不影响。我们再来看看另一种情况。 共享数据的情况 MyThread.java public class MyThread extends Thread { private int count = 5; @Override public void run() { super.run(); count--; System.out.println("由 " + MyThread.currentThread().getName() + " 计算,count=" + count); } } Run.java public class Run { public static void main(String[] args) { MyThread mythread=new MyThread(); //下列线程都是通过mythread对象创建的 Thread a=new Thread(mythread,"A"); Thread b=new Thread(mythread,"B"); Thread c=new Thread(mythread,"C"); Thread d=new Thread(mythread,"D"); Thread e=new Thread(mythread,"E"); a.start(); b.start(); c.start(); d.start(); e.start(); } } 运行结果 由 A 计算,count=3 由 C 计算,count=2 由 B 计算,count=3 由 D 计算,count=1 由 E 计算,count=0 可以看出这里已经出现了错误,我们想要的是依次递减的结果。为什么呢?? 因为在大多数jvm中,count--的操作分为如下下三步: 取得原有count值 计算i -1 对i进行赋值 所以多个线程同时访问时出现问题就是难以避免的了。 那么有没有什么解决办法呢? 答案是:当然有,而且很简单。 在run方法前加上synchronized关键字即可得到正确答案。 加上关键字后的运行结果: 由 A 计算,count=4 由 E 计算,count=3 由 D 计算,count=2 由 B 计算,count=1 由 C 计算,count=0 一些常用方法 currentThread() 返回对当前正在执行的线程对象的引用。 getId() 返回此线程的标识符 getName() 返回此线程的名称 getPriority() 返回此线程的优先级 isAlive() 测试这个线程是否还处于活动状态。 什么是活动状态呢? 活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备运行的状态。 sleep(long millis) 使当前正在执行的线程以指定的毫秒数“休眠”(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。 interrupt() 中断这个线程。 interrupted() 和isInterrupted() interrupted():测试当前线程是否已经是中断状态,执行后具有将状态标志清除为false的功能 isInterrupted(): 测试线程Thread对相关是否已经是中断状态,但部清楚状态标志 setName(String name) 将此线程的名称更改为等于参数 name 。 isDaemon() 测试这个线程是否是守护线程。 setDaemon(boolean on) 将此线程标记为 daemon线程或用户线程。 join() 在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。 join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行 yield() yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU时间。注意:放弃的时间不确定,可能一会就会重新获得CPU时间片。 setPriority(int newPriority) 更改此线程的优先级 停止一个线程 stop(),suspend(),resume()(仅用于与suspend()一起使用)这些方法已被弃用,所以我这里不予讲解。 使用interrupt()方法 我们上面提到了interrupt()方法,先来试一下interrupt()方法能不能停止线程MyThread.java public class MyThread extends Thread { @Override public void run() { super.run(); for (int i = 0; i < 5000000; i++) { System.out.println("i=" + (i + 1)); } } } Run.java public class Run { public static void main(String[] args) { try { MyThread thread = new MyThread(); thread.start(); Thread.sleep(2000); thread.interrupt(); } catch (InterruptedException e) { System.out.println("main catch"); e.printStackTrace(); } } } 运行上诉代码你会发现,线程并不会终止。 针对上面代码的一个改进: interrupted()方法判断线程是否停止,如果是停止状态则break MyThread.java public class MyThread extends Thread { @Override public void run() { super.run(); for (int i = 0; i < 500000; i++) { if (this.interrupted()) { System.out.println("已经是停止状态了!我要退出了!"); break; } System.out.println("i=" + (i + 1)); } System.out.println("看到这句话说明线程并未终止------"); } } Run.java public class Run { public static void main(String[] args) { try { MyThread thread = new MyThread(); thread.start(); Thread.sleep(2000); thread.interrupt(); } catch (InterruptedException e) { System.out.println("main catch"); e.printStackTrace(); } System.out.println("end!"); } } 运行结果: i=437968 i=437968 i=437968 ... end! 已经是停止状态了!我要退出了! 看到这句话说明线程并未终止 for循环虽然停止执行了,但是for循环下面的语句还是会执行,说明线程并未被停止。 使用return终止线程 MyThread.java public class MyThread extends Thread { @Override public void run() { while (true) { if (this.isInterrupted()) { System.out.println("ֹͣ停止了!"); return; } System.out.println("timer=" + System.currentTimeMillis()); } } } Run.java public class Run { public static void main(String[] args) throws InterruptedException { MyThread t=new MyThread(); t.start(); Thread.sleep(2000); t.interrupt(); } } 运行结果输出停止了后就没有继续输出,线程的确被终止了 线程的优先级 每个线程都具有各自的优先级,线程的优先级可以在程序中表明该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使哪个线程进入运行状态。但这个并不意味着低 优先级的线程得不到运行,而只是它运行的几率比较小,如垃圾回收机制线程的优先级就比较低。所以很多垃圾得不到及时的回收处理。 线程优先级具有继承特性比如A线程启动B线程,则B线程的优先级和A是一样的。 线程优先级具有随机性也就是说线程优先级高的不一定每一次都先执行完。 Thread类中包含的成员变量代表了线程的某些优先级。如Thread.MINPRIORITY(常数1),Thread.NORMPRIORITY(常数5), Thread.MAXPRIORITY(常数10)。其中每个线程的优先级都在Thread.MINPRIORITY(常数1) 到Thread.MAXPRIORITY(常数10) 之间,在默认情况下优先级都是Thread.NORMPRIORITY(常数5)。 学过操作系统这门课程的话,我们可以发现多线程优先级或多或少借鉴了操作系统对进程的管理。 线程优先级具有继承特性测试代码: MyThread1.java public class MyThread1 extends Thread { @Override public void run() { System.out.println("MyThread1 run priority=" + this.getPriority()); MyThread2 thread2 = new MyThread2(); thread2.start(); } } MyThread2.java public class MyThread2 extends Thread { @Override public void run() { System.out.println("MyThread2 run priority=" + this.getPriority()); } } Run.java public class Run { public static void main(String[] args) { System.out.println("main thread begin priority=" + Thread.currentThread().getPriority()); Thread.currentThread().setPriority(6); System.out.println("main thread end priority=" + Thread.currentThread().getPriority()); MyThread1 thread1 = new MyThread1(); thread1.start(); } } 运行结果: main thread begin priority=5 main thread end priority=6 MyThread1 run priority=6 MyThread2 run priority=6 Java多线程分类 多线程分类 用户线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程 守护线程:运行在后台,为其他前台线程服务.也可以说守护线程是JVM中非守护线程的 “佣人”。 特点:一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作 应用:数据库连接池中的检测线程,JVM虚拟机启动后的检测线程 最常见的守护线程:垃圾回收线程 如何设置守护线程? 可以通过调用Thead类的setDaemon(true)方法设置当前的线程为守护线程 注意事项: setDaemon(true)必须在start()方法前执行,否则会抛出IllegalThreadStateException异常 在守护线程中产生的新线程也是守护线程 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑 MyThread.java public class MyThread extends Thread { private int i = 0; @Override public void run() { try { while (true) { i++; System.out.println("i=" + (i)); Thread.sleep(100); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } Run.java public class Run { public static void main(String[] args) { try { MyThread thread = new MyThread(); thread.setDaemon(true); thread.start(); Thread.sleep(5000); System.out.println("我离开thread对象也不再打印了,也就是停止了!"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } 最后打印了主线程的我离开thread对象也不再打印了,也就是停止了!后,子线程的死循环输出也终止了
浅析 阿里巴巴 Java 开发规约 (未完成) contents 为什么要学 编程规约 P3C IDEA 插件 why-use 我们知道,一般稍微大一点的公司,都会在系统架构设计完成之后,编码工作开始之前,给出一份属于自家公司,或是自家团队给出的编码规范文档,所有的编码工作人员都必须遵守其中的规范,避免规范不统一带来的不必要的沟通问题,而当你去到另一家公司的时候,可能又要学习另一种风格有差异的编码规范,阿里给我们带来了标准,相信用不了多久,会统一国内各java开发公司的规范,乃至击败Google,称为全球java开发者的规范 养成良好的编程规范的作用(为何学习并养成编程风格) 好的编码规范可以尽可能的减少一个软件的维护成本,并且几乎没有任何一个软件,在其整个生命周期中,均由最初的开发人员来维护; 好的编码规范可以改善软件的可读性,可以让开发人员尽快而彻底地理解新的代码; 好的编码规范可以最大限度的提高团队开发的合作效率; 长期的规范性编码还可以让开发人员养成好的编码习惯,甚至锻炼出更加严谨的思维; 阿里java规范的作用(为何要学习阿里的java编码规范) 就在不久前,5月17日,作为唯一的中国代表,阿里巴巴获邀加入Java全球管理组织Java Community Process (JCP)的最高执行委员会。 JCP是一个开放的国际组织,由Java开发者及被授权者组成,主要职能是发展和更新Java技术规范。 阿里此次能够入选JCP执行委员会主要缘于在电商、金融、物流等领域积累的丰富Java应用场景实践,让阿里巴巴有机会通过迭代式创新,将前沿Java技术应用于真实的生产环境。此外,阿里去年面向全球推出的《阿里巴巴Java开发规约》扫描插件,能够在Eclipse等著名开发工具中进行JAVA代码检测,极大推动了Java编程语言规范开发的进程。 此前在JCP组织当中,Java标准规范的制定主要由硅谷巨头牵头主导,此次阿里巴巴的加入,对于国内开发者、企业而言,将会使Java开发过程中容错与效率变得更高,国内开发标准或将成为全球规范。 编程规约 命名规约 常量定义 OOP规约 命名规约 采用空格缩进,禁止使用tab字符。 这是Google和ali一致的规约,只不过前者是一个tab对应2个空格,后者则是4个空格。之所以不提倡tab键,是因为不同的IDE对tab键的“翻译”默认有所差异,容易因不同程序员的个性化而导致同一份代码的格式混乱。。 POJO类中布尔类型的变量,都不要加is,否则部分框架解析会引起序列化错误。 定义为基本数据类型boolean isSuccess;的属性,它的方法也是isSuccess(),RPC框架在反向解析的时候,“以为”对应的属性名称是success,导致属性获取不到,进而抛出异常。 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,类名若有复数含义,则可使用复数形式。 避免硬编码问题是每个程序员都应该具备的基本素养,硬编码所带来的可读性差、维护困难等问题。 接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性 正例:接口方法签名:void f(); 反例:接口方法定义:public abstract void f(); Service/DAO层方法命名规约 1)获取单个对象的方法用get做前缀。 2)获取多个对象的方法用list做前缀。 3)获取统计值的方法用count做前缀。 4)插入的方法用save(推荐)或insert做前缀。 5)删除的方法用remove(推荐)或delete做前缀。 6)修改的方法用update做前缀。 常量定义 不要使用一个常量类维护所有常量,应该按常量功能进行归类,分开维护。 如:缓存相关的常量放在类:CacheConsts下;系统配置相关的常量放在类:ConfigConsts下。 说明:大而全的常量类,非得ctrl+f才定位到修改的常量,不利于理解,也不利于维护。 OOP规约 相同参数类型,相同业务含义,才可以使用Java的可变参数,避免使用Object。 说明:可变参数必须放置在参数列表的最后。(提倡同学们尽量不用可变参数编程)effective java中提倡慎用可变参数,在重视性能的情况下,使用可变参数需要特别小心,可变参数方法的每次调用都会导致进行一次数组分配和初始化。如果凭经验确定无法承受这一成本,但又需要可变参数的灵活性,还有一种模式可以让你如愿以偿。 假设确定对某个方法95%的调用会有3个或者更少的参数,就声明改方法的5个重载,每个重载方法带有0至3个普通参数,当参数的数目超过3个时,就使用一个可变参数方法: 例如: public void foo(){} public void foo(int a1){} public void foo(int a1,int a2){} public void foo(int a1,int a2,int a3){} public void foo(int a1,int a2,int a3,int ... rest){} 这种方法可能不太恰当,但是一旦需要它时,它可就帮上大忙了。 总之,在定义参数数目不定的方法时,可变参数方法是一种很方便的方式,但是它们不应该被过度滥用。如果使用不当,会产生混乱的结果。 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在init方法中。 初始化方法的参数,通过不同的参数体现不同的初始化方法,再由不同的构造器的重载传给初始化方法不同参数,以达到逻辑清晰,代码复用的目的,这一点在java.lang.thread中得到了淋漓尽致的体现,thread类的构造器中都是简单地调用了一下init()方法,具体的事都在init方法中,不同的构造器的重载只是传给init不同的参数 public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } Thread(Runnable target, AccessControlContext acc) { init(null, target, "Thread-" + nextThreadNum(), 0, acc); } ... ... POJO类必须写toString方法。使用工具类source> generate toString时,如果继承了另一个POJO类,注意在前面加一下super.toString。 类内方法定义顺序依次是:公有方法或保护方法> 私有方法> getter/setter方法。 说明: 公有方法是类的调用者和维护者最关心的方法,首屏展示最好; 保护方法虽然只是子类关心,也可能是“模板设计模式”下的核心方法; 而私有方法外部一般不需要特别关心,是一 个黑盒实现; 因为方法信息价值较低,所有Service和DAO的getter/setter方法放在类体最 后。 插件 P3C是先进的反潜海上巡逻机,译作猎户座,海神之子 阿里云栖大会最新开源的Java代码规范检查工具p3c,作用类似于CheckStyle,FindBugs的综合,是《阿里巴巴Java开发手册》的有效补充,阿里将代码规范检查插件命名为p3c,大概就是取其先进的猎杀,预防bug的能力 Download P3C IDEA plugin 安装步骤 点击工具栏File 点击Setting 点击Plugin 点击Install Plugin From disk 弹出对话框,找到你下载的插件 p3c、checkstyle都是代码规范检测工具,帮助java开发者提高编码质量,有效缩小菜鸟和大咖的代码差距,是少数Java开发者都应该安装的插件。
Bootstrap-table 快速入门---- bootstrap-table是一个基于Bootstrap的jQuery插件可以实现从数据库中提取数据到前端进行相应操作的功能 基于bootstrap-table的后台系统功能展示 Table of contents Quick start Why use it What's included details 进阶之行内编辑 额外的坑点 quick-start 对本文有什么疑问或者建议,可以在下方的评论区说明,笔者会尽能力给出解答,另外已经完成了bootstrap-table的增删改查操作,以及增加操作的的模态框(bootstrap modal),以及表单校检(bootstrap-validate),最近有点忙,过一段时间更新,还有侧边栏的动态生成(bootstrap-treeview)也是在学习当中 快速使用: Download bootstrap-table hello demo 预览前端demo代码 Clone the repo: git clone https://github.com/seltonGitHub/helloBootTable.git 没有配置java环境,移步JDK安装与环境变量配置; 没有配置tomcat环境,移步tomcat的下载和安装配置; 没有下载配置idea环境,移步IntelliJ IDEA安装以及配置; why-use 学习成本低,配置简单,文档齐全 与Bootstrap无缝衔接,整体风格一致,也便于二次开发 开发者活跃,Github定期维护 whats-included file list: bootstrapDemo/ ├── web/ │ ├── js │ ├── WEB-INF │ └── bootindex.html └── src/ │ └── DataSendServlet.java 表单展示页面 (bootindex.html) javascript文件 (showOrder.js)从服务器取得数据,然后渲染表格 details $("#table").bootstrapTable({ method: "post", url: "获取后台数据的url", ... ... }); 这里的js语句的所有渲染操作是针对html页面中的id为table的一个table,所以不要忘了在导入了该js的html中构建出id为table的table bootstrap-table中的重要键值的简单解释: url(必须修改) method pageSize(必须修改) jsonstyle(必须修改) columns(必须修改) contentType(必须填写) queryParams pageNumber 表格绑定事件 showorder.js会向服务器发起ajax访问 bootstrapTable构建元素解析: url $("#table").bootstrapTable({ method: "post", url: "获取后台数据的url", ... ... }); ajax访问到的后台路径(必须),该后台需要按照指定的json 格式返回数据 method get发送的数据在请求报文的请求行,也就是url部分,而且参数如果有中文会出现乱码问题,而post发送的数据在报文实体,都应该是post,表单的提交也一般都是post queryParams 不需要任何修改,相当于ajax中的data键,上面的method决定这些参数传递给后台的的传递方式.发送给后台的数据,给出实现表单分页的两个参数,offset和limit,在oTableInit.queryParams中给出,后台用request.getParameter()的方式拿到queryParams中传递过来的值,然后制定dao pageSize 当前table一次最多显示多少行,也就是你的table的一页应该展现多少行,必须 pageNumber 起始页,一般是1不用改,这个和pageSize决定了queryParams中的offset的值,offset=(pageNow - 1) * pageSize,limit=pageSize contentType contentType: "application/x-www-form-urlencoded" columns $("#table").bootstrapTable({ method: "post", url: "获取后台数据的url", data: [ {field: 'testId', title: 'ID'}, {field: 'testName', title: '姓名'}, {field: 'testPassword', title: '密码'} ] ... ... ] }); 你的table的表结构,以上例子表示表有三列,列的实际显示名字分别是ID,姓名,密码,但是field代表实际数据的名字,表中的数据是由于ajax向服务器发起访问,服务器返回给的数据中的rows的每一个json对象的键都会对应到field的列中-----服务器返还的值 jsonstyle { "total":25, "rows":[ { "testID":1, "testName":"xiaoming1", "testPassword":"xiaomingpwd1" }, { "testID":2, "testName":"xiaoming2", "testPassword":"xiaomingpwd2" } ] } 数据库返还给发起访问的ajax的数据,必须满足,包含两个json形式的键值对, 一个是total键,值为表单拥有者在数据库中的全部数据的数量(行数),这个数据和pageSize决定table展示的页面有多少页,另一个是rows键,值为多个json对象,rows的每一个json对象就是当前table页的一行实体展示,这里的rows相当于会给前端table两行数据,testID,testName,testPassword分别会被填入到table中的field对应的列中-----前端接收到值表现 offset oTableInit.queryParams = function (params) { var temp = { //这里的键的名字和控制器的变量名必须一直,这边改动,控制器也需要改成一样的 limit: params.limit, //params.limit, 页面大小 offset: params.offset, testNum: 445, testNum1: 343 }; offset=(pageNumber - 1) * pageSize,是会被发送到后台使用的数据,后台数据提取sql语句示例 limit oTableInit.queryParams = function (params) { var temp = { //这里的键的名字和控制器的变量名必须一直,这边改动,控制器也需要改成一样的 limit: params.limit, //params.limit, 页面大小 offset: params.offset, testNum: 445, testNum1: 343 }; limit=pageSize,是会被发送到后台使用的数据,后台数据提取sql语句示例 sql示例 SELECT * FROM test WHERE id = ? LIMIT offset,limit 表格绑定事件 用于测试ajax返回的数据是最好的 $("#table").bootstrapTable({ method: "post", url: "获取后台数据的url", onLoadSuccess: function(){ //加载成功时执行 console.info("加载成功"); }, onLoadError: function(){ //加载失败时执行 console.info("加载数据失败"); } }); 关于事件,更为详细的介绍请访问boottableDoc editable 这是在操作table吗,感觉就像是数据库展现在了页面上 如果你已经阅读完或者已经在自己代码中实现了上述功能,但是table存在的目的本来就不应该只是展现,应该还有寻常的CRUD,精力有限,只是实践了update,笔者使用的是行内编辑的方式实现的update,需要用到另一个工具X-editable,不过还好boottable有这样的插件,将x-editable封装整合到了当中,只需要引入https://cdn.bootcss.com/bootstrap-table/1.12.1/extensions/editable/bootstrap-table-editable.min.js 快速使用 方法详解 关于editable的语法的细节 x-editableDemo在线展示 start editable $("#table").bootstrapTable({ method: "post", url: "获取后台数据的url", [ {field: 'testId', title: 'ID', editable: {mode: 'popup'} }, {field: 'testName', title: '姓名'}, {field: 'testPassword', title: '密码'} ] ... ... ] }); onEditableSave: function (field, row, oldValue, $el) { $.ajax({ type: "post", url: "/ordercenter/updateOrder.json", data: { orderid: row.orderid, updateCol: field, updateVal: eval('row.'+field) }, dataType: 'JSON', success: function (data, status) { console.log(data); if (status == "success") { alert('旧数据: 订单号: ' + row.orderid + ' ' + field + ': ' + oldValue + '\r\n' + '更新后的数据: 订单号: ' + data.updateId + ' ' + data.updateCol + ': ' + data.updateVal) } }, error: function () { alert('编辑失败'); }, complete: function () { } }); }, details editable 编辑后的提交方法统一放到onEditableSave事件里面统一处理 例子: 页面table中的列姓名,field为testName,实际的值为xiaoming1,通过修改将其改为xiaoming2,这时候field为testName,row为一个json,键值对分别为该行的所有键值组合,oldValue为xiaoming1 更为详细的描述请到x-editable 建议读者直接使用我的onEditableSave,它向后台发送了三个数据精确完成update,行特定标识和列特定标识定位到修改了哪一个具体的数据,再给出updateVal指出原本的数据被修改成了updateVal moreEditable editable中的mode的值一般是popup,翻译是弹出的意思,也可以使用inline值,但是点击并且编辑的时候会使表格样式发生改变,而popup则不会 demo web-xml html代码 js代码 后台代码 web-xml <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <welcome-file-list> <welcome-file>/bootindex.html</welcome-file> </welcome-file-list> <servlet> <servlet-name>DataSendServlet</servlet-name> <servlet-class>com.selton.DataSendServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>DataSendServlet</servlet-name> <url-pattern>/DataSendServlet</url-pattern> </servlet-mapping> </web-app> html-demo <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="https://cdn.bootcss.com/bootstrap/3.0.1/css/bootstrap.min.css"> <link href="https://cdn.bootcss.com/bootstrap-table/1.12.1/bootstrap-table.min.css" rel="stylesheet"> <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script> <link href="//cdnjs.cloudflare.com/ajax/libs/x-editable/1.5.0/bootstrap3-editable/css/bootstrap-editable.css" rel="stylesheet"/> <script src="//cdnjs.cloudflare.com/ajax/libs/x-editable/1.5.0/bootstrap3-editable/js/bootstrap-editable.min.js"></script> <script language="JavaScript" src="/js/showOrder.js"></script> <script language="JavaScript"> $(function () { //1.初始化Table var oTable = new TableInit(); oTable.Init(); }); </script> </head> <body> <div class="container"> <table id="Table"></table> </div> <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script> <script src="http://apps.bdimg.com/libs/bootstrap/3.3.4/js/bootstrap.min.js"></script> <script src="https://cdn.bootcss.com/bootstrap-table/1.12.1/bootstrap-table.min.js"></script> </body> </html> js-demo var TableInit = function () { var oTableInit = new Object(); //初始化Table oTableInit.Init = function () { $('#Table').bootstrapTable({ url: '/DataSendServlet', //请求后台的URL(*) method: 'get', //请求方式(*) async: true, //true表示执行到这,ajax向后台发起访问,在等待响应的这段时间里,继续执行下面的代码 //设置为true,基本都是后面的代码(除非还有ajax)先执行 // toolbar: '#toolbar', //工具按钮用哪个容器 striped: true, //是否显示行间隔色 cache: false, //是否使用缓存,默认为true,所以一般情况下需要设置一下这个属性(*) pagination: true, //是否显示分页(*) queryParams: oTableInit.queryParams,//传递参数(*) sidePagination: "server", //分页方式:client客户端分页,server服务端分页(*) paginationPreText:'<', //上一页按钮样式 paginationNextText:'>', //下一页按钮样式 pageNumber: 1, //初始化加载第一页,默认第一页 pageSize: 10, //每页的记录行数(*) pageList: [10, 25, 50, 100], //可供选择的每页的行数(*) contentType: "application/x-www-form-urlencoded", //重要选项,必填 showColumns: true, //是否显示所有的列 showRefresh: true, //是否显示刷新按钮 minimumCountColumns: 2, //最少允许的列数 clickToSelect: true, //是否启用点击选中行 //height: 700, //行高,如果没有设置height属性,表格自动根据记录条数决定表格高度,最好不要设置这个属性 uniqueId: "no", //每一行的唯一标识,一般为主键列 showToggle: true, //是否显示详细视图和列表视图的切换按钮 cardView: false, //是否显示详细视图 detailView: false, //是否显示父子表 columns: [ { field: 'testId', title: 'ID', editable: { mode: 'inline' } }, { field: 'testName', title: '用户名' }, { field: 'testPassword', title: '密码' } ], rowStyle: function (row, index) { var classesArr = ['success', 'info']; var strclass = ""; if (index % 2 === 0) {//偶数行 strclass = classesArr[0]; } else {//奇数行 strclass = classesArr[1]; } return {classes: strclass}; },//隔行变色 }); }; //得到查询的参数 oTableInit.queryParams = function (params) { var temp = { //这里的键的名字和控制器的变量名必须一直,这边改动,控制器也需要改成一样的 limit: params.limit, //params.limit, 页面大小 offset: params.offset, testNum: 445, testNum1: 343 }; return temp; }; return oTableInit; }; server-demo package com.selton; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.Enumeration; /** * @author seltonzyf@gmail.com * @date 2018/5/10 13:59 */ @WebServlet(name = "DataSendServlet") public class DataSendServlet extends HttpServlet { @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //这里输出后台从ajax拿到的数据 Enumeration<String> parameterNames = request.getParameterNames(); while (parameterNames.hasMoreElements()) { String s = parameterNames.nextElement(); String parameter = request.getParameter(s); System.out.println("s = " + s); System.out.println("parameter = " + parameter); } response.getWriter().print("{\"total\": 11, \"rows\":[{\"testId\":9, \"testName\":\"selton\", \"testPassword\": 1}]}"); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request,response); } } 注意 bootstrap-table只能被调用一次的问题 在inittable之前 $("#table").bootstrapTable('destroy'); 清空之前表内数据 服务器向前端发送的用于构建表单的json,所有的键都会变成小写