暂时未有相关云产品技术能力~
在计算机中有一些数据结构总是与数据的查找分不开,比如二叉查找树(Binary Search Tree)、红黑树、B树、B+树等等数据结构。你可曾想过为什么会有这么多种用于搜索的数据结构?为什么红黑树结构在计算机中内存中被广泛应用?为什么MySQL多种数据引擎都选择B+树作为其索引实现?为什么Redis要使用跳表?带着这些问题,这篇文章我们探讨一下这些数据结构在实际中的应用以及针对于存在的问题产生的演进。二分查找对于查找数据,不得不提的一种基础算法就是二分法,很多数据结构的查找算法核心思想都是二分法,其查找效率也经常会用来和二分法来做对比。二分法的时间复杂度为**O(logN)**,这是一个非常优秀的时间复杂度,其效率仅次于常数时间复杂度 **O(1)**。二分查找的实现思路是这样的:对数据集进行排序找到数据集的中间节点,判断是否为查找的值,等于直接返回。根据与中间节点大小的比较结果,确定收缩查找区间范围是中间节点的左边还是右边。重复上述 2、3 过程继续查找。从其实现思路来看,有两个点很重要:一是可以保证数据的有序性,二是适合进行数据分段存储,方便缩小区间查找。所以根据这种思路,演化出了两个不同的路线,树和跳表两种数据结构。二叉搜索树BST二叉搜索树(BST,Binary Search Tree)(又叫二叉查找树)是一棵空树,或者是具有下列性质的二叉树:若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值它的左、右子树也分别为二叉排序树。二叉搜索树的问题二叉搜索树符合了使用二分法查询数据的要求,但是有个问题:因为插入顺序的不同,二叉树的高度不稳定,极端情况下可能变成链表(就是插入的数据是有序的,递增或者递减)。这就成了线性查询,时间复杂度最多变成O(n),查询效率不稳定。为了解决这个问题,就产生了各种树的平衡算法,保证树的节点高度不会差太多。所以就有了AVL树(平衡二叉树)和红黑树等新的数据结构。AVL树平衡二叉树全称叫做平衡二叉搜索(排序)树,AVL树是最早的平衡二叉树之一。为了解决一般的二叉搜索树存在的问题,即根节点到叶子结点的高度相差太多,查询效率不问题,并且极端情况有成为链表的可能。所以AVL树具有以下特点:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,左右两个子树 也都是一棵平衡二叉树。在AVL树中,任何节点的两个子树的高度最大差别为 1 ,所以它也被称为平衡二叉树 。AVL树查找、插入和删除在平均和最坏情况下都是O(LogN)。AVL 什么意思 ?AVL 是大学教授 G.M. Adelson-Velsky 和 E.M. Landis 名称的缩写,他们提出的平衡二叉树的概念,为了纪念他们,将 平衡二叉树 称为 AVL树。与普通二叉搜索树的区别的是,它在插入和删除节点的时候,会根据需要进行左旋或者右旋,来保证二叉树的平衡,示意图如下。这不是本文的讨论重点,感兴趣自己可以研究。AVL树的问题AVL树高度的平衡情况固然很好,但这是有代价的。为了维持平衡,其旋转是非常耗时的。AVL实现平衡的关键在于旋转操作:插入和删除可能破坏二叉树的平衡,此时需要通过一次或多次树旋转来重新平衡这个树。当插入数据时,最多只需要两次旋转(单旋转或双旋转);但是当删除数据时,会导致树失衡,AVL需要维护从被删除节点到根节点这条路径上所有节点的平衡,旋转的量级为O(lgn)。由于旋转的耗时,AVL树在删除数据时效率很低;在删除操作较多时,维护平衡所需的代价可能高于其带来的好处,因此AVL实际使用并不广泛。场景:windows对进程地址空间的管理用到了AVL树。针对于这种情况,红黑树对其做了优化。红黑树RB-Tree红黑树是一种自平衡的二叉查找树,是一种高效的查找树。它是由 Rudolf Bayer 于1978年发明,在当时被称为平衡二叉 B 树(symmetric binary B-trees)。后来,在1978年被 Leo J. Guibas 和 Robert Sedgewick 修改为如今的红黑树。红黑树具有良好的效率,它可在 O(logN) 时间内完成查找、增加、删除等操作。红黑树是一种接近平衡的二叉树(说它是接近平衡因为它并没有像AVL树的平衡因子的概念,它只是靠着满足红黑节点的5条性质来维持一种接近平衡的结构,进而提升整体的性能,并没有严格的卡定某个平衡因子来维持绝对平衡)。特性一棵红黑树同时满足以下特性:节点是红色或黑色根是黑色叶子节点(外部节点,空节点)都是黑色,这里的叶子节点指的是最底层的空节点(外部节点),下图中的那些null节点才是叶子节点,null节点的父节点在红黑树里不将其看作叶子节点红色节点的子节点都是黑色红色节点的父节点都是黑色从根节点到叶子节点的所有路径上不能有 2 个连续的红色节点从任一节点到叶子节点的所有路径都包含相同数目的黑色节点红黑树的查找,插入和删除操作,时间复杂度都是O(logN)。红黑树解决了AVL树什么问题AVL的左右子树高度差不能超过1,每次进行插入/删除操作时,几乎都需要通过旋转操作保持平衡在频繁进行插入/删除的场景中,频繁的旋转操作使得AVL的性能大打折扣红黑树通过牺牲严格的平衡,换取插入/删除时少量的旋转操作,整体性能优于AVL。红黑树插入时的不平衡,不超过两次旋转就可以解决;删除时的不平衡,不超过三次旋转就能解决红黑树的红黑规则,保证最坏的情况下,也能在O(log 2N)时间内完成查找操作。红黑树和AVL树的效率对比:如果插入一个node引起了树的不平衡,AVL树和红黑树都是最多只需要2次旋转操作,即两者都是O(1);但是在删除node引起树的不平衡时,最坏情况下,AVL需要维护从被删node到root这条路径上所有node的平衡性,因此需要旋转的量级O(logN),而红黑树最多只需3次旋转,只需要O(1)的复杂度。其次,AVL树的结构相较红黑树来说更为平衡,在插入和删除node更容易引起Tree的不平衡,因此在大量数据需要插入或者删除时,AVL需要rebalance的频率会更高。因此,红黑树在需要大量插入和删除node的场景下,效率更高。自然,由于AVL高度平衡,因此AVL的search效率更高。map的实现只是折衷了两者在search、insert以及delete下的效率。总体来说,红黑树的统计性能是高于AVL的。最坏情况下,AVL树有最多O(logN)次旋转,而红黑树最多三次。场景:红黑树的应用就很多了。epoll在内核中的实现,用红黑树管理事件块。nginx中,用红黑树管理timer等。Java1.8版本后的的hashMap中使用链表+红黑树解决哈希冲突问题,Java中的TreeMap使用红黑树存储排序键值对。著名的linux进程调度Completely Fair Scheduler,用红黑树管理进程控制块。红黑树的问题虽然红黑树是一种已经被性能优化了的自平衡的二叉查找树,插入修改效率和查找销量得到了平衡,但他依然存在一些问题。依旧在插入和删除时需要对节点进行旋转,频繁修改数据的场景影响效率。红黑树毕竟是一种二叉树,当数据量很大时,树的高度会变得很大,查找时经过的节点过多,效率变低。红黑树在内存中表现优秀,但因为树的高度的问题,当使用磁盘等辅助存储设备读写数据时(如MySQL等数据库),会导致数据在磁盘中散布分散,并且IO次数过多,效率变低。适合单个查询,对于数据查询中常见的范围查询场景,无法很好支持。针对于上述问题,有了天生为磁盘存储而生的B树。B树B树是一种多路搜索树,又名平衡多路查找树(查找路径不只两个),与二叉树相比,B树的每个非叶节点可以有多个子树。因此,当总节点数量相同时,B树的高度远远小于AVL树和红黑树(B树是一颗“矮胖子”),磁盘IO次数大大减少。数据库索引技术里大量使用者B树和B+树的数据结构。定义B树最重要的概念是阶数(Order),对于一颗m阶B树(就是一个节点最多包含几个子节点),需要满足以下条件:每个节点最多包含 m 个子节点。如果根节点包含子节点,则至少包含 2 个子节点;除根节点外,每个非叶节点至少包含 m/2 个子节点。拥有 k 个子节点的非叶节点将包含 k - 1 条记录。所有叶节点都在同一层中。度数:在树中,每个节点的子节点(子树)的个数就称为该节点的度 (degree)。阶数:阶(order)定义为一个节点最多可以有多少个元素。如下图,这是一个2阶3度的B树。场景:MongoDB索引。B树的优势B树相对平衡二叉树在节点空间的利用率上进行改进,B树在每个节点保存更多的数据,减少了树的高度,从而提升了查找的性能。B树的优势除了树高小,还有对访问局部性原理的利用。所谓局部性原理,是指当一个数据被使用时,其附近的数据有较大概率在短时间内被使用。B树将键相近的数据存储在同一个节点,当访问其中某个数据时,数据库会将该整个节点读到缓存中;当它临近的数据紧接着被访问时,可以直接在缓存中读取,无需进行磁盘IO;换句话说,B树的缓存命中率更高。在数据库应用中,B树的每个节点存储的数据量大约为4K, 这是因为考虑到磁盘数据存储是采用块的形式存储的,每个块的大小为4K,每次对磁盘进行IO数据读取时,同一个磁盘块的数据会被一次性读取出来,所以每一次磁盘IO都可以读取到B树中一个节点的全部数据。对于顺数插入的数据,B树的结构优势可以使其在内存中顺序排列,存贮到同一个磁盘页中,顺序插入对磁盘的利用率和读取效率都非常友好。场景:MySQL的InnbDB 索引。B树的问题B树虽然解决了磁盘存储的问题,但是在查询范围数据时依旧不够优秀,比如你要查询1-5的数据,必须按照树的中顺遍历来访问各个节点。对于这个问题,B+树对其进行了优化。B+树B+树是在B树的基础上又一次的改进,其主要对两个方面进行了提升,一方面是查询的稳定性,另外一方面是在数据排序方面更友好。B+树也是多路平衡查找树,其特性主要有以下4点:B树中每个节点(包括叶节点和非叶节点)都存储真实的数据,B+树中只有叶子节点存储真实的数据,非叶节点只存储键。在MySQL中,这里所说的真实数据,可能是行的全部数据(如Innodb的聚簇索引),也可能只是行的主键(如Innodb的辅助索引),或者是行所在的地址(如MyIsam的非聚簇索引)。B树中一条记录只会出现一次,不会重复出现,而B+树的键则可能重复重现——一定会在叶节点出现,也可能在非叶节点重复出现。B+树的叶节点之间通过双向链表链接。B+树叶子节点的关键字从小到大有序排列,左边结尾数据都会保存右边节点开始数据的指针。因为叶子节点都是有序排列的,所以B+树对于数据的排序有着更好的支持。B+树非叶子节点的子节点数=关键字数,或者非叶节点的关键字数=子节点数-1(这里有两种算法的实现方式),虽然他们数据排列结构不一样,但其原理还是一样的Mysql 的B+树是用第一种方式实现)。第一种算法:第二种算法:B+树和B树的对比1、B+树的层级更少:相较于B树B+每个非叶子节点存储的关键字数更多,所以每个磁盘块存储的数据更多,树的层级更少所以查询数据更快2、B+树查询速度更稳定:B+所有关键字数据地址都存在叶子节点上,所以每次查找的次数都相同所以查询速度要比B树更稳定3、B+树天然具备排序功能:所有关键字都出现在叶子结点的链表中(稠密索引),B+树所有的叶子节点数据构成了一个有序链表,在查询大小区间的数据时候更方便,数据紧密性很高,缓存的命中率也会比B树高。4、B+树全节点遍历更快:B+树遍历整棵树只需要遍历所有的叶子节点即可,而不需要像B树一样需要对每一层进行遍历,这有利于数据库做全表扫描。B树相对于B+树的优点是,如果经常访问的数据离根节点很近,而B树的非叶子节点本身存有关键字其数据的地址,所以这种数据检索的时候会要比B+树快。5、B+树更适合文件索引系统B+树的缺点在B+树的构建过程中,为了保持树的平衡,节点的合并拆分是比较耗费时间的,所以B*树就是在如何减少构建中节点合并和拆分的次数,从而提升树的数据插入、删除性能。B*树相对于B+树,B*树的不同之处如下:(1)首先是关键字个数限制问题,B+树初始化的关键字初始化个数是cei(m/2),b树的初始化个数为(cei(2/3m))(2)B+树节点满时就会分裂,而B*树节点满时会检查兄弟节点是否满(因为每个节点都有指向兄弟的指针),如果兄弟节点未满则向兄弟节点转移关键字,如果兄弟节点已满,则从当前节点和兄弟节点各拿出1/3的数据创建一个新的节点出来;B*树 与B+树对比在B+树的基础上因其初始化的容量变大,使得节点空间使用率更高,而又存有兄弟节点的指针,可以向兄弟节点转移关键字的特性使得B*树额分解次数变得更少。总结对于上述的演进过程,这里给出一个简要总结,如下图。建议收藏!
MYSQL5.7详细安装步骤准备阶段更换yum源1、打开 mirrors.aliyun.com,选择centos的系统,点击帮助2、执行命令:yum install wget -y3、改变某些文件的名称mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup4、执行更换yum源的命令wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo5、更新本地缓存yum clean allyum makecache安装阶段1、查看系统中是否自带安装mysqlyum list installed | grep mysql2、删除系统自带的mysql及其依赖(防止冲突)yum -y remove mysql-libs.x86_643、安装wget命令yum install wget -y 4、给CentOS添加rpm源,并且选择较新的源wget dev.mysql.com/get/mysql-community-release-el6-5.noarch.rpm如果提示无法建立SSL连接,追加命令wget https://repo.mysql.com//mysql-community-release-el6-5.noarch.rpm --no-check-certificate5、安装下载好的rpm文件 yum install mysql-community-release-el6-5.noarch.rpm -y6、安装成功之后,会在/etc/yum.repos.d/文件夹下增加两个文件7、修改mysql-community.repo文件原文件:修改之后:如果报错 需要:libsasl2.so.2()(64bit),则继续修改 更换baseurl[mysql57-community-dmr] name=MySQL 5.7 Community Server Development Milestone Release #baseurl=http://repo.mysql.com/yum/mysql-5.7-community/el/6/$basearch/ baseurl=http://repo.mysql.com/yum/mysql-5.7-community/el/7/$basearch/ enabled=1 gpgcheck=0 gpgkey=file:/etc/pki/rpm-gpg/RPM-GPG-KEY-mysql8、使用yum安装mysqlyum install mysql-community-server -y9、启动mysql服务并设置开机启动#启动之前需要生成临时密码,需要用到证书,可能证书过期,需要进行更新操作 yum update -y #启动mysql服务 service mysqld start #设置mysql开机启动 chkconfig mysqld on10、获取mysql的临时密码grep "password" /var/log/mysqld.lognXirXd7g*XTV11、使用临时密码登录mysql -uroot -p #输入密码12、修改密码set global validate_password_policy=0; set global validate_password_length=1; ALTER USER 'root'@'localhost' IDENTIFIED BY '123123123';13、修改远程访问权限grant all privileges on *.* to 'root'@'%' identified by 'root' with grant option; flush privileges; 如果出现报错 ERROR 1819 (HY000): Your password does not satisfy the current policy requirements 设置密码等级为low,再次尝试 set global validate_password_policy=LOW;14、设置字符集为utf-8vi /etc/my.cnf#在[mysqld]部分添加: character-set-server=utf8 #在文件末尾新增[client]段,并在[client]段添加: default-character-set=utf8service mysql restartservice mysqld restart15关闭防火墙查看防火墙状态firewall-cmd --state停止firewallsystemctl stop firewalld.service 禁止firewall开机启动systemctl disable firewalld.service
我的博客即将入驻“云栖社区”,诚邀技术同仁一同入驻。
pip是一款非常方便的python包管理工具,本文主要介绍在windows 10下安装pip方法。 1. 下载pip 地址:https://pypi.python.org/pypi/pip#downloads 注意选择tar.gz压缩包,目前最新版本为9.0.1,这里选择的版本是:pip-9.0.1.tar.gz (md5, pgp) 点击:下载 2. 解压安装 解压下载的压缩包至工作目录下(如D:\),打开Windows cmd,运行如下命令进入解压后的pip目录 cd /d D:\pip-9.0.1 使用如下命令进行安装 python setup.py install 3. 添加环境变量 添加windows系统环境变量,与安装python时添加的方法一样 如我的python目录是:F:\Python27\; 则添加如下2个目录到系统环境变量里:F:\Python27\;F:\Python27\Scripts; 4. pip常用命令 安装成功后,重新进入CMD后运行pip,可以看到帮助文档: pip常用命令如下: 1 2 3 4 5 6 7 8 9 10 11 #安装包 pip install xxx #升级包,可以使用-U 或者 --upgrade pip install -U xxx #卸载包 pip uninstall xxx #列出已安装的包 pip list 5. 常见问题 最近遇到过使用pip命令时报错AttributeError: 'module' object has no attribute 'wraps',发现是pip安装版本错误导致,使用上述方法重新安装最新版本pip即可解决。 6. 执行python setup.py install报错 python错误:ImportError: No module named setuptools 这句错误提示的表面意思是:没有setuptools的模块,说明python缺少这个模块,那我们只要安装这个模块即可解决此问题,下面我们来安装一下:1.下载setuptools包:https://pypi.python.org/pypi/setuptools2.解压setuptools包3.编译setuptools :python setup.py build4.开始执行setuptools安装:python setup.py install 这时再执行:python setup.py install 成功!
spark历史:伯克利实验室研究项目,基于Hadoop的Mapreduce机制,引入内存管理机制,提高了迭代式计算和交互式中的效率。 spark组件: spark core:spark基本功能,包括任务调度,内存管理,容错机制 内部定义了RDDs(弹性分布式数据集),提供多个APIs调用,为其他组件提供底层服务 spark sql:spark处理结构化数据的库,类似Hive SQL,MySql,主要为企业提供报表统计 spark streaming:实时数据流处理组件,类似Storm,提供API操作实时流数据,企业中用来从Kafka中接收数据做实时统计 Mlib:机器学习功能包,包括聚类,回归,模型评估和数据导入。同时支持集群平台上的横向扩展 Graphx:处理图的库,并进行图的并行计算 Cluster Manager是:spark自带的集群管理 Spark紧密集成的优点: spark底层优化,基于spark底层的组件也得到相应的优化,紧密集成节省了组件的部署,测试时间
由于各Linux开发厂商的不同,因此不同开发厂商的Linux版本操作细节也不一样,今天就来说一下CentOS下JDK的安装: 方法一:手动解压JDK的压缩包,然后设置环境变量 1.在/usr/目录下创建java目录 [root@localhost ~]# mkdir/usr/java [root@localhost ~]# cd /usr/java 2.下载jdk,然后解压 [root@localhost java]# curl -O http://download.Oracle.com/otn-pub/java/jdk/7u79-b15/jdk-7u79-linux-x64.tar.gz [root@localhost java]# tar -zxvf jdk-7u79-linux-x64.tar.gz 3.设置环境变量 [root@localhost java]# vi /etc/profile 在profile中添加如下内容: #set java environment JAVA_HOME=/usr/java/jdk1.7.0_79 JRE_HOME=/usr/java/jdk1.7.0_79/jre CLASS_PATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin export JAVA_HOME JRE_HOME CLASS_PATH PATH 让修改生效: [root@localhost java]# source /etc/profile 4.验证JDK有效性 [root@localhost java]# java -version java version "1.7.0_79" Java(TM) SE Runtime Environment (build 1.7.0_79-b15) Java HotSpot(TM) 64-Bit Server VM (build 24.79-b02, mixed mode) 方法二:用yum安装JDK 1.查看yum库中都有哪些jdk版本(暂时只发现了openjdk) [root@localhost ~]# yum search java|grep jdk ldapjdk-javadoc.x86_64 : Javadoc for ldapjdk java-1.6.0-openjdk.x86_64 : OpenJDK Runtime Environment java-1.6.0-openjdk-demo.x86_64 : OpenJDK Demos java-1.6.0-openjdk-devel.x86_64 : OpenJDK Development Environment java-1.6.0-openjdk-javadoc.x86_64 : OpenJDK API Documentation java-1.6.0-openjdk-src.x86_64 : OpenJDK Source Bundle java-1.7.0-openjdk.x86_64 : OpenJDK Runtime Environment java-1.7.0-openjdk-demo.x86_64 : OpenJDK Demos java-1.7.0-openjdk-devel.x86_64 : OpenJDK Development Environment java-1.7.0-openjdk-javadoc.noarch : OpenJDK API Documentation java-1.7.0-openjdk-src.x86_64 : OpenJDK Source Bundle java-1.8.0-openjdk.x86_64 : OpenJDK Runtime Environment java-1.8.0-openjdk-demo.x86_64 : OpenJDK Demos java-1.8.0-openjdk-devel.x86_64 : OpenJDK Development Environment java-1.8.0-openjdk-headless.x86_64 : OpenJDK Runtime Environment java-1.8.0-openjdk-javadoc.noarch : OpenJDK API Documentation java-1.8.0-openjdk-src.x86_64 : OpenJDK Source Bundle ldapjdk.x86_64 : The Mozilla LDAP Java SDK 2.选择版本,进行安装 //选择1.7版本进行安装 [root@localhost ~]# yum install java-1.7.0-openjdk //安装完之后,默认的安装目录是在: /usr/lib/jvm/java-1.7.0-openjdk-1.7.0.75.x86_64 3.设置环境变量 [root@localhost ~]# vi /etc/profile 在profile文件中添加如下内容 #set java environment JAVA_HOME=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.75.x86_64 JRE_HOME=$JAVA_HOME/jre CLASS_PATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin export JAVA_HOME JRE_HOME CLASS_PATH PATH 让修改生效 [root@localhost java]# source /etc/profile 4.验证(同上一方法) 方法三:用rpm安装JDK 1.下载rpm安装文件 [root@localhost ~]$ curl -O http://download.oracle.com/otn-pub/java/jdk/7u79-b15/jdk-7u79-linux-x64.rpm 2.使用rpm命令安装 [root@localhost ~]# rpm -ivh jdk-7u79-linux-x64.rpm 3.设置环境变量 [root@localhost java]# vi /etc/profile 在打开的profile文件中添加如下内容 #set java environment JAVA_HOME=/usr/java/jdk1.7.0_79 JRE_HOME=/usr/java/jdk1.7.0_79/jre CLASS_PATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin export JAVA_HOME JRE_HOME CLASS_PATH PATH 让修改生效 [root@localhost java]# source /etc/profile 4.验证(同上一方法) 注:和yum安装类似,不用设置环境变量就可以运行java命令。rpm安装方式默认会把jdk安装到/usr/java/jdk1.7.0_79,然后通过三层链接,链接到/usr/bin,具体链接如下: [root@localhost ~]# cd /bin [root@localhost bin]# ll|grep java lrwxrwxrwx. 1 root root 25 Mar 28 11:24 jar ->/usr/java/default/bin/jar lrwxrwxrwx. 1 root root 26 Mar 28 11:24 java -> /usr/java/default/bin/java lrwxrwxrwx. 1 root root 27 Mar 28 11:24 javac ->/usr/java/default/bin/javac lrwxrwxrwx. 1 root root 29 Mar 28 11:24 javadoc ->/usr/java/default/bin/javadoc lrwxrwxrwx. 1 root root 28 Mar 28 11:24 javaws ->/usr/java/default/bin/javaws lrwxrwxrwx. 1 root root 30 Mar 28 11:24 jcontrol ->/usr/java/default/bin/jcontrol [root@localhost bin]# cd /usr/java/ [root@localhost java]# ll total 4 lrwxrwxrwx. 1 root root 16 Mar 28 11:24 default-> /usr/java/latest drwxr-xr-x. 8 root root 4096 Mar 28 11:24 jdk1.7.0_79 lrwxrwxrwx. 1 root root 21 Mar 28 11:24 latest -> /usr/java/jdk1.7.0_79 方法四:Ubuntu 上使用apt-get安装JDK 1.查看apt库都有哪些jdk版本 root@linuxidc:~# apt-cache search java|grep jdk default-jdk - Standard Java or Java compatible Development Kit default-jdk-doc - Standard Java or Java compatible Development Kit (documentation) gcj-4.6-jdk - gcj and classpath development tools for Java(TM) gcj-jdk - gcj and classpath development tools for Java(TM) openjdk-6-dbg - Java runtime based on OpenJDK (debugging symbols) openjdk-6-demo - Java runtime based on OpenJDK (demos and examples) openjdk-6-doc - OpenJDK Development Kit (JDK) documentation openjdk-6-jdk - OpenJDK Development Kit (JDK) openjdk-6-jre-lib - OpenJDK Java runtime (architecture independent libraries) openjdk-6-source - OpenJDK Development Kit (JDK) source files openjdk-7-dbg - Java runtime based on OpenJDK (debugging symbols) openjdk-7-demo - Java runtime based on OpenJDK (demos and examples) openjdk-7-doc - OpenJDK Development Kit (JDK) documentation openjdk-7-jdk - OpenJDK Development Kit (JDK) openjdk-7-source - OpenJDK Development Kit (JDK) source files uwsgi-plugin-jvm-openjdk-6 - Java plugin for uWSGI (OpenJDK 6) uwsgi-plugin-jwsgi-openjdk-6 - JWSGI plugin for uWSGI (OpenJDK 6) openjdk-6-jre - OpenJDK Java runtime, using Hotspot JIT openjdk-6-jre-headless - OpenJDK Java runtime, using Hotspot JIT (headless) openjdk-7-jre - OpenJDK Java runtime, using Hotspot JIT openjdk-7-jre-headless - OpenJDK Java runtime, using Hotspot JIT (headless) openjdk-7-jre-lib - OpenJDK Java runtime (architecture independent libraries) 2.选择版本进行安装 root@linuxidc:~# apt-get install openjdk-7-jdk 3.设置环境变量 root@linuxidc:~# vi /etc/profile 在打开的profile文件中添加如下内容 #set java environment JAVA_HOME=/usr/lib/jvm/java-1.7.0-openjdk-amd64 JRE_HOME=$JAVA_HOME/jre CLASS_PATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JRE_HOME/lib PATH=$PATH:$JAVA_HOME/bin:$JRE_HOME/bin export JAVA_HOME JRE_HOME CLASS_PATH PATH 让修改生效 root@linuxidc:~# source /etc/profile 4.验证(同上一方法)
1、删除空目录 1-1)rmdir [目录名] (remove empty directories) 1-1-1)只能删除空目录 2、删除文件或目录 2-1)rm -rf [文件或目录] (remove) 2-1-1)选项 -r :删除目录 2-1-2)选项 -f :强制 2-1-3)rm [文件名] :提示是否删除 2-1-4)rm -r [目录] :删除目录需加 -r ,目录中含有子文件,将持续询问是否删除 2-1-5)rm -rf [目录] :删除目录, 不会询问是否删除而直接进行 2-1-6)纯字符界面没有回收站。注意自杀指令:rm -rf / 2-2)rm -rf /tmp/* :删除tmp目录下的内容 2-3)rm -rf /tmp/ :删除tmp目录 3、建议:删除文件或目录习惯使用命令 rm -rf [文件或目录] 4、复制命令 4-1)cp [选项] [原文件或目录] [目标目录] (copy) 4-1-1)选项 -r :复制目录 4-1-2)选项 -p :连带文件属性复制 4-1-3)选项 -d :若原文件是链接文件,则复制链接属性 4-1-4)选项 -a :相当于 -pdr 全选 4-2)例子 4-2-1)cp abc /tmp/ 若目标目录不加文件名,则为原名复制 4-2-2)cp abc /tmp/ana 若目标目录后加入文件名,则为改名复制 4-2-3)cp -r japan/ /tmp/ 复制目录 4-2-4)cp -a japan/ /tmp/ 完全复制,包括所有属性 4-3)被复制文件的时间为执行复制命令的时间,若要使被复制的文件与原文件属性完全一致,需加 -a 5、剪切或改名命令 5-1)mv [原文件或目录] [目标目录] (move) 5-1-1)mv japan/ /tmp/ 剪切目录时不需加 -r 5-1-2)mv abc longls 当原文件与目标目录,在同一目录下,则为改名命令 6、当前系统时间命令:date 7、ls -l 命令别名 :ll linux 是个严谨的操作系统,一级目录都严格规定 做练习推荐在家目录中(root或home),以及tmp。 1、/ 根目录 2、/bin 命令保存目录(普通用户就可以读取的命令) 2-1)根目录下的bin和sbin,usr目录下的bin和sbin,这四个目录都是用来保存系统命令。其中,bin目录 下的命令任何用户都可以执行,sbin目录下只有root才可以执行。linux使用此方式来区分用户权限。 3、/boot 启动目录,启动相关文件 4、/dev 设备文件保存目录 5、/etc 配置文件保存目录 6、/home 普通用户的家目录 7、/lib 系统库保存目录 7-1)操作系统中并没有把所有功能都写入linux中,而将常用的功能写成一个个程序库,此些库保存在lib 中,当需要时调用即可。避免全部写入linux中,使其无比庞大且运行缓慢。 8、/mnt 系统挂载U盘、移动硬盘目录 8-1)老师习惯于在mnt下创建CD-ROM来挂载光盘,在mnt下创建USB来挂载U盘。原因在于老师的linux中不存 在media、misc目录。 9、/media 挂载光盘目录 10、/misc 外接磁带机挂载目录 11、/root 超级用户的家目录 12、/tmp 临时目录 12-1)可存放临时数据 13、/sbin 命令保存目录(超级用户才能使用的目录) 14、/proc 直接写入内存的 14-1)该目录同sys目录不能直接操作,这两个目录保存的是内存的挂载点。其中的数据直接写在内存中。避免数据丢失或由于内存溢出导致系统崩溃。 15、/sys 16、/usr 系统软件资源目录 16-1)/usr/bin/ 系统命令(普通用户) 16-2)/usr/sbin/ 系统命令(超级用户) 17、/var 系统相关文档内容
1.起始符: [root@localhost ~]# root 当前登录用户 localhost 主机名 ~ 当前所在目录(家目录) # 超级用户的提示符 2.ls命令选项: ls -a 显示所有文件,包括隐藏文件 ls -l 显示详细信息 ls -d 查看目录属性 ls -h 人性化显示文件大小 ls -i 显示inode 3.文件权限(10位),第一位为文件类型,后面每3位一组 -rw-r--r-- -文件类型(-文件 d目录 l软链接目录) rw- u所有者 r-- g所属组 r-- o其他人 r读 w写 x执行 -rw-r--r--. 1 root root 1207 .代表ACL权限 1应用计数 在linux中“.”开头的文件是隐藏文件。 4.# 超级用户的提示符 ¥普通用户的提示符 ~ 代表当前目录 5、linux中一切皆文件。目录为目录文件。普通文件用来保存数据,目录文件用来保存文件。 6、mkdir :建立目录(make directories) 2-1)mkdir -p [目录名] 2-1-1)-p :递归创建,当创建多级目录时需加 2-2)例: 2-2-1)mkdir light 2-2-2) mkdir -p imooc/linux/light 7、cd [目录] :切换目录(change directory) 3-1)简化操作 3-1-1)cd ~/ cd :进入当前用户的家目录 3-1-2)cd - :进入上次目录 3-1-3)cd .. :进入上一级目录 3-1-4)cd . :进入当前目录 8、路径 4-1)相对路径:参照当前所在目录,进行查找 4-2)绝对路径:从根目录开始制定,一级一级递归,进行查找 9、pwd :查询所在目录位置(print working directory)
学会在命令行中获取帮助 在 Linux 环境中,如果你遇到困难,可以使用man命令,它是Manual pages的缩写。 Manual pages 是 UNIX 或类 UNIX 操作系统中在线软件文档的一种普遍的形式, 内容包括计算机程序(包括库和系统调用)、正式的标准和惯例,甚至是抽象的概念。用户可以通过执行man命令调用手册页。 你可以使用如下方式来获得某个命令的说明和使用方式的详细介绍: $ man <command_name> 比如你想查看 man 命令本身的使用方式,你可以输入: man man 通常情况下,man 手册里面的内容都是英文的,这就要求你有一定的英文基础。man 手册的内容很多,涉及了 Linux 使用过程中的方方面面。为了便于查找,man 手册被进行了分册(分区段)处理,在 Research UNIX、BSD、OS X 和 Linux 中,手册通常被分为8个区段,安排如下: 区段 说明 1 一般命令 2 系统调用 3 库函数,涵盖了C标准函数库 4 特殊文件(通常是/dev中的设备)和驱动程序 5 文件格式和约定 6 游戏和屏保 7 杂项 8 系统管理命令和守护进程 要查看相应区段的内容,就在 man 后面加上相应区段的数字即可,如: $ man 1 ls 会显示第一区段中的ls命令 man 页面。 所有的手册页遵循一个常见的布局,为了通过简单的 ASCII 文本展示而被优化,而这种情况下可能没有任何形式的高亮或字体控制。一般包括以下部分内容: NAME(名称) 该命令或函数的名称,接着是一行简介。 SYNOPSIS(概要) 对于命令,正式的描述它如何运行,以及需要什么样的命令行参数。对于函数,介绍函数所需的参数,以及哪个头文件包含该函数的定义。 DESCRIPTION(说明) 命令或函数功能的文本描述。 EXAMPLES(示例) 常用的一些示例。 SEE ALSO(参见) 相关命令或函数的列表。 也可能存在其它部分内容,但这些部分没有得到跨手册页的标准化。常见的例子包括:OPTIONS(选项),EXIT STATUS(退出状态),ENVIRONMENT(环境),BUGS(程序漏洞),FILES(文件),AUTHOR(作者),REPORTING BUGS(已知漏洞),HISTORY(历史)和 COPYRIGHT(版权)。 通常 man 手册中的内容很多,你可能不太容易找到你想要的结果,不过幸运的是你可以在 man 中使用搜索/<你要搜索的关键字>,查找完毕后你可以使用n键切换到下一个关键字所在处,shift+n为上一个关键字所在处。使用Space(空格键)翻页,Enter(回车键)向下滚动一行,或者使用j,k(vim 编辑器的移动键)进行向前向后滚动一行。按下h键为显示使用帮助(因为 man 使用 less 作为阅读器,实为less工具的帮助),按下q退出。 想要获得更详细的帮助,你还可以使用info命令,不过通常使用man就足够了。如果你知道某个命令的作用,只是想快速查看一些它的某个具体参数的作用,那么你可以使用--help参数,大部分命令都会带有这个参数,如: $ ls --help
Shell 常用通配符: 字符 含义 * 匹配 0 或多个字符 ? 匹配任意一个字符 [list] 匹配 list 中的任意单一字符 [!list] 匹配 除list 中的任意单一字符以外的字符 [c1-c2] 匹配 c1-c2 中的任意单一字符 如:[0-9] [a-z] {string1,string2,...} 匹配 string1 或 string2 (或更多)其一字符串 {c1..c2} 匹配 c1-c2 中全部字符 如{1..10} 一些常用快捷键 按键 作用 Ctrl+d 键盘输入结束或退出终端 Ctrl+s 暂停当前程序,暂停后按下任意键恢复运行 Ctrl+z 将当前程序放到后台运行,恢复到前台为命令fg Ctrl+a 将光标移至输入行头,相当于Home键 Ctrl+e 将光标移至输入行末,相当于End键 Ctrl+k 删除从光标所在位置到行末 Alt+Backspace 向前删除一个单词 Shift+PgUp 将终端显示向上滚动 Shift+PgDn 将终端显示向下滚动
新手指南: 新手应该知道的 26 个命令 AUTHOR: Locez VERSION: 1 当你进入了 Linux 的世界,在下载、安装 了某个 Linux 发行版,体验了 Linux 桌面并安装了一些你喜爱和需要的 软件 之后,应该去了解下 Linux 真正的魅力所在:命令行 。每一个命令其实就是一个程序,借助这些命令,我们可以办到非常多的事情。下面将会为大家介绍一下几个常用的命令。 如何寻求帮助? 在 Linux 下遇到问题,最重要的是要自己寻求帮助,下面是三种寻求帮助的方法。 man man 是 Linux 的帮助手册,即 manual 。因为大多数程序都会自带手册,所以可以通过 man 命令获取帮助。执行以后,在 man page 页面中按 q 退出。 获取 ls 的帮助 $ man ls 查看有多少(针对不同方面的)同名的手册 $ man -f ls ls (1) - list directory contents ls (1p) - list directory contents 查看特定的手册 $ man 1p ls info 与 man 不同的是,可以像浏览网页一样在各个节点中跳转。 从文档首页开始浏览 $ info 获取特定程序的帮助 $ info program help 除了上面的两种方法外,还有一种简单使用的方法,那就是 --help 参数,一般程序都会有这个参数,会输出最简单有用的介绍。 $ man --help ### 获取 man 的帮助 $ info --help ### 获取 info 的帮助 $ ls --help ### 获取 ls 的帮助 如何简单操作? 在 Terminal(终端) 中,有许多操作技巧,这里就介绍几个简单的。 光标 up(方向键上) 可以调出输入历史执行记录,快速执行命令 down(方向键下) 配合 up 选择历史执行记录 Home 移动光标到本行开头 End 移动光标到本行结尾 PgUp 向上翻页 PaDN 向下翻页 ctrl + c 终止当前程序 Tab 补全 Tab 补全是非常有用的一个功能,可以用来自动补全命令或文件名,省时准确。 未输入状态下连按两次 Tab 列出所有可用命令 已输入部分命令名或文件名,按 Tab 进行自动补全,多用你就肯定会喜欢的了。 常用命令 以下命令按照通常的使用频度排列。 cd cd 是打开某个路径的命令,也就是打开某个文件夹,并跳转到该处。 $ cd path ### path 为你要打开的路径。 其中 path 有绝对路径和相对路径之分,绝对路径强调从 / 起,一直到所在路径。相对路径则相对于当前路径来说,假设当前家目录有etc 文件夹(绝对路径应为 /home/username/etc),如果直接 cd etc 则进入此文件夹,但若是 cd /etc/ 则是进入系统 etc ,多琢磨一下就可以理解了。另外在 Linux 中, . 代表当前目录, .. 代表上级目录,因此返回上级目录可以 cd .. 。 ls ls 即 list ,列出文件。 $ ls ### 仅列出当前目录可见文件 $ ls -l ### 列出当前目录可见文件详细信息 $ ls -hl ### 列出详细信息并以可读大小显示文件大小 $ ls -al ### 列出所有文件(包括隐藏)的详细信息 注意: Linux 中 以 . 开头的文件或文件夹均为隐藏文件或隐藏文件夹。 pwd pwd 用于返回当前工作目录的名字,为绝对路径名。 $ pwd /home mkdir mkdir 用于新建文件夹。 $ mkdir folder $ mkdir -p folder/subfolder ### -p 参数为当父目录存在时忽略,若不存在则建立,用此参数可建立多级文件夹 rm rm 即 remove ,删除文件。 $ rm filename ### 删除 filename $ rm -i filename ### 删除 filename 前提示,若多个文件则每次提示 $ rm -rf folder/subfolder/ ### 递归删除 subfolder 下所有文件及文件夹,包括 subfolder 自身 $ rm -d folder ### 删除空文件夹 cp cp 即 copy ,复制文件。 $ cp source dest ### 将 source 复制到 dest $ cp folder/* dest ### 将 folder 下所有文件(不含子文件夹中的文件)复制到 dest $ cp -r folder dest ### 将 folder 下所有文件(包含子文件夹中的所有文件)复制到 dest mv mv 即 move ,移动文件。 $ mv source folder ### 将 source 移动到 folder 下,完成后则为 folder/source $ mv -i source folder ### 在移动时,若文件已存在则提示 **是否覆盖** $ mv source dest ### 在 dest 不为目录的前提下,重命名 source 为 dest cat cat 用于输出文件内容到 Terminal 。 $ cat /etc/locale.gen ### 输出 locale.gen 的内容 $ cat -n /etc/locale.gen ### 输出 locale.gen 的内容并显示行号 more more 与 cat 相似,都可以查看文件内容,所不同的是,当一个文档太长时, cat 只能展示最后布满屏幕的内容,前面的内容是不可见的。这时候可用 more 逐行显示内容。 $ more /etc/locale.gen $ more +100 /etc/locale.gen ### 从 100 行开始显示 less less 与 more 相似,不过 less 支持上下滚动查看内容,而 more 只支持逐行显示。 $ less /etc/locale.gen $ less +100 /etc/locale.gen nano nano 是一个简单实用的文本编辑器,使用简单。 $ nano filename ### 编辑 filename 文件,若文件不存在,则新打开一个文件,若退出时保存,则创建该文件 编辑完后,ctrl + X 提示是否保存,按 y 确定保存即可。 注意:在使用过程中可用 ctrl + G 获取帮助。 reboot reboot 为重启命令。 # reboot ### '$' 和 '#' 的区别在于 '$' 普通用户即可执行 ### 而 '#' 为 root 用户才可执行,或普通用户使用 'sudo' poweroff poweroff 为关机命令。 # poweroff ### 马上关机 ping ping 主要用于测试网络连通,通过对目标机器发送数据包来测试两台主机是否连通,及延时情况。 $ ping locez.com ### 通过域名 ping,若 DNS 未设置好,可能无法 ping 通 $ ping linux.cn PING linux.cn (211.157.2.94) 56(84) bytes of data. 64 bytes from 211.157.2.94.static.in-addr.arpa (211.157.2.94): icmp_seq=1 ttl=53 time=41.5 ms 64 bytes from 211.157.2.94.static.in-addr.arpa (211.157.2.94): icmp_seq=2 ttl=53 time=40.4 ms 64 bytes from 211.157.2.94.static.in-addr.arpa (211.157.2.94): icmp_seq=3 ttl=53 time=41.9 ms ^C --- linux.cn ping statistics --- 3 packets transmitted, 3 received, 0% packet loss, time 2002ms rtt min/avg/max/mdev = 40.406/41.287/41.931/0.644 ms $ ping 211.157.2.94 ### 通过 IP 地址 ping ,若无法 ping 通可能是网络连接出现问题 grep grep 主要用于返回匹配的项目,支持正则表达式。 $ grep PATTERN filename ### 返回所有含有 PATTERN 的行 $ grep zh_CN /etc/locale.gen ### 返回所有含 zh_CN 的行 mount mount 用于挂载一个文件系统,需要 root 用户执行。一个磁盘可分为若干个分区,在分区上面可以创建文件系统,而挂载点则是提供一个访问的入口,将一个分区的文件系统挂载到某个目录中,称这个目录为挂载点,并且可以通过这个挂载点访问该文件系统中的内容。 例如一块硬盘在 Linux 中表示为 /dev/sda 那么它上面的分区应该表示为 /dev/sda1 、/dev/sda2 。 # mount ### 输出系统目前的挂载信息 # mount /dev/sda1 /mnt ### 将 sda1 挂载到 /mnt 中 # cd /mnt ### 直接通过 /mnt 访问内容 # mount -o remount,rw /mnt ### 重新挂载 sda1 到 /mnt 并设置为 可读写 # mount -a ### 挂载 fstab 文件配置好的文件系统 umount umount 与 mount 相反,是卸载一个挂载点,即取消该入口。 # umount /mnt ### 卸载 /mnt 这个挂载点的文件系统 # umount -a ### 卸载所有已挂载的文件系统 tar tar 主要用于创建归档文件,和解压归档文件,其本身是没有压缩功能的,但可以调用 gzip 、 bzip2 进行压缩处理。 参数解释: -c 创建归档 -x 解压归档 -v 显示处理过程 -f 目标文件,其后必须紧跟 目标文件 -j 调用 bzip2 进行解压缩 -z 调用 gzip 进行解压缩 -t 列出归档中的文件 $ tar -cvf filename.tar . ### 将当前目录所有文件归档,但不压缩,注意后面有个 ’.‘ ,不可省略,代表当前目录的意思 $ tar -xvf filename.tar ### 解压 filename.tar 到当前文件夹 $ tar -cvjf filename.tar.bz2 . ### 使用 bzip2 压缩 $ tar -xvjf filename.tar.bz2 ### 解压 filename.tar.bz2 到当前文件夹 $ tar -cvzf filename.tar.gz ### 使用 gzip 压缩 $ tar -xvzf filename.tar.gz ### 解压 filename.tar.gz 到当前文件夹 $ tar -tf filename ### 只查看 filename 归档中的文件,不解压 ln ln 主要用于在两个文件中创建链接,链接又分为 Hard Links (硬链接)和 Symbolic Links (符号链接或软链接),其中默认为创建硬链接,使用 -s 参数指定创建软链接。 硬链接主要是增加一个文件的链接数,只要该文件的链接数不为 0 ,该文件就不会被物理删除,所以删除一个具有多个硬链接数的文件,必须删除所有它的硬链接才可删除。 软链接简单来说是为文件创建了一个类似快捷方式的东西,通过该链接可以访问文件,修改文件,但不会增加该文件的链接数,删除一个软链接并不会删除源文件,即使源文件被删除,软链接也存在,当重新创建一个同名的源文件,该软链接则指向新创建的文件。 硬链接只可链接两个文件,不可链接目录,而软链接可链接目录,所以软链接是非常灵活的。 $ ln source dest ### 为 source 创建一个名为 dest 的硬链接 $ ln -s source dest ### 为 source 创建一个名为 dest 的软链接 chown chown 用于改变一个文件的所有者及所在的组。 # chown user filename ### 改变 filename 的所有者为 user # chown user:group filename ### 改变 filename 的所有者为 user,组为 group # chown -R root folder ### 改变 folder 文件夹及其子文件的所有者为 root chmod chmod 永远更改一个文件的权限,主要有 读取 、 写入 、 执行 ,三种权限,其中 所有者 、 用户组 、 其他 各占三个,因此 ls -l 可以看到如下的信息 -rwxr--r-- 1 locez users 154 Aug 30 18:09 filename 其中 r=read , w=write , x=execute # chmod +x filename ### 为 user ,group ,others 添加执行权限 # chmod -x filename ### 取消 user , group ,others 的执行权限 # chmod +w filename ### 为 user 添加写入权限 # chmod ugo=rwx filename ### 设置 user ,group ,others 具有 读取、写入、执行权限 # chmod ug=rw filename ### 设置 user ,group 添加 读取、写入权限 # chmod ugo=--- filename ### 取消所有权限 useradd useradd 用于添加一个普通用户。 # useradd -m -g users -G audio -s /usr/bin/bash newuser ### -m 创建 home 目录, -g 所属的主组, -G 指定该用户在哪些附加组, -s 设定默认的 shell ,newuser 为新的用户名 passwd passwd 用于改变用户登录密码。 $ passwd ### 不带参数更改当前用户密码 # passwd newuser ### 更改上述新建的 newuser 的用户密码 whereis whereis 用于查找文件、手册等。 $ whereis bash bash: /usr/bin/bash /etc/bash.bashrc /etc/bash.bash_logout /usr/share/man/man1/bash.1.gz /usr/share/info/bash.info.gz $ whereis -b bash ### 仅查找 binary bash: /usr/bin/bash /etc/bash.bashrc /etc/bash.bash_logout $ whereis -m bash ### 仅查找 manual bash: /usr/share/man/man1/bash.1.gz /usr/share/info/bash.info.gz find find 也用于查找文件,但更为强大,支持正则,并且可将查找结果传递到其他命令。 $ find . -name PATTERN ### 从当前目录查找符合 PATTERN 的文件 $ find /home -name PATTERN -exec ls -l {} \; # 从 /home 文件查找所有符合 PATTERN 的文件,并交由 ls 输出详细信息 wget wget 是一个下载工具,简单强大。 $ wget -O newname.md https://github.com/LCTT/TranslateProject/blob/master/README.md ### 下载 README 文件并重命名为 newname.md $ wget -c url ### 下载 url 并开启断点续传
设计模式 Sunny在CSDN技术博客中陆续发表了100多篇与设计模式学习相关的文章,涵盖了七个面向对象设计原则和24个设计模式(23个GoF设计模式 + 简单工厂模式),为了方便大家学习,http://quanke.name 现将所有文章的进行了整理,方便大家下载阅读,希望能给各位带来帮助! 阅读地址:http://gof.quanke.name/ 下载地址:https://www.gitbook.com/book/quanke/design-pattern-java/ 源码下载地址:https://github.com/quanke/design-pattern-java-source-code.git 课件下载地址:http://www.chinasa.info/download/DP-Slides.rar 作者:刘伟 http://blog.csdn.net/lovelion 本书编辑:http://quanke.name 刘伟(Sunny),中南大学计算机应用技术博士,国家认证系统分析师(2005年),国家认证系统架构设计师(2009年,全国第四名),高级程序员,数据库系统工程师,MCSE,MCDBA,CASI专业顾问与企业内训讲师。具有十多年软件开发、项目管理及教育培训经验,曾在NIIT(印度国家信息技术学院)担任高级讲师,主持和参与30多个软件项目的开发工作,并给国内多家公司提供软件开发、软件设计等培训服务,现主要致力于软件工程、数据挖掘等领域的教学、推广和研究工作。技术专长:软件架构、设计模式、UML、OOAD、数据挖掘等。已出版设计模式书籍四本:《设计模式》(清华大学出版社,2011年)、《设计模式实训教程》(清华大学出版社,2012年)、《设计模式的艺术——软件开发人员内功修炼之道》(清华大学出版社,2013年)、《C#设计模式》(清华大学出版社,2013年)。架构师之家www.chinasa.info站长。 E-mail:weiliu_china@126.com 微博地址:http://weibo.com/csusunny 更多干货,请关注:http://quanke.name
Java 8中的新功能特性改变了游戏规则。对Java开发者来说这是一个全新的世界,并且是时候去适应它了。 在这篇文章里,我们将会去了解传统循环的一些替代方案。在Java 8的新功能特性中,最棒的特性就是允许我们去表达我们想要完成什么而不是要怎样做。这正是循环的不足之处。要确保循环的灵活性是需要付出代价的。return、break 或者 continue都会显著地改变循环的实际表现。这迫使我们不仅要清楚我们要实现怎样的代码,还要了解循环是怎样工作的。 在介绍Java 8的流(Stream)时,我们学会了一些集合操作的实用技巧。现在我们要看看怎样把这些循环转换为更简洁,可读性更高的代码。 开始编码! 好吧,讲的够多了,是时候展示一些例子了! 这次我们要以文章为例子。一篇文章拥有一个标题,一个作者和几个标签。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 private class Article { private final String title; private final String author; private final List<String> tags; private Article(String title, String author, List<String> tags) { this.title = title; this.author = author; this.tags = tags; } public String getTitle() { return title; } public String getAuthor() { return author; } public List<String> getTags() { return tags; } } 每个例子都会包含一个使用传统循环的方案和一个使用Java 8新特性的方案。 在第一个例子里,我们要在集合中查找包含“Java”标签的第一篇文章。 看一下使用for循环的解决方案。 1 2 3 4 5 6 7 8 9 10 public Article getFirstJavaArticle() { for (Article article : articles) { if (article.getTags().contains("Java")) { return article; } } return null; } 现在我们使用Stream API的相关操作来解决这个问题。 1 2 3 4 5 public Optional<Article> getFirstJavaArticle() { return articles.stream() .filter(article -> article.getTags().contains("Java")) .findFirst(); } 是不是很酷?我们首先使用 filter 操作去找到所有包含Java标签的文章,然后使用 findFirst() 操作去获取第一次出现的文章。因为Stream是“延迟计算”(lazy)的并且filter返回一个流对象,所以这个方法仅在找到第一个匹配元素时才会处理元素。 现在,让我们获取所有匹配的元素而不是仅获取第一个。 首先使用for循环方案。 1 2 3 4 5 6 7 8 9 10 11 12 public List<Article> getAllJavaArticles() { List<Article> result = new ArrayList<>(); for (Article article : articles) { if (article.getTags().contains("Java")) { result.add(article); } } return result; } 使用Stream操作的方案。 1 2 3 4 5 public List<Article> getAllJavaArticles() { return articles.stream() .filter(article -> article.getTags().contains("Java")) .collect(Collectors.toList()); } 在这个例子里我们使用 collection 操作在返回流上执行少量代码而不是手动声明一个集合并显式地添加匹配的文章到集合里。 到目前为止还不错。是时候举一些突出Stream API强大的例子了。 根据作者来把所有的文章分组。 照旧,我们使用循环方案。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public Map<String, List<Article>> groupByAuthor() { Map<String, List<Article>> result = new HashMap<>(); for (Article article : articles) { if (result.containsKey(article.getAuthor())) { result.get(article.getAuthor()).add(article); } else { ArrayList<Article> articles = new ArrayList<>(); articles.add(article); result.put(article.getAuthor(), articles); } } return result; } 我们能否找到一个使用流操作的简洁方案来解决这个问题? 1 2 3 4 public Map<String, List<Article>> groupByAuthor() { return articles.stream() .collect(Collectors.groupingBy(Article::getAuthor)); } 很好!使用 groupingBy 操作和 getAuthor 方法,我们得到了更简洁、可读性更高的代码。 现在,我们查找集合中所有不同的标签。 我们从使用循环的例子开始。 1 2 3 4 5 6 7 8 9 10 public Set<String> getDistinctTags() { Set<String> result = new HashSet<>(); for (Article article : articles) { result.addAll(article.getTags()); } return result; } 好,我们来看看如何使用Stream操作来解决这个问题。 1 2 3 4 5 public Set<String> getDistinctTags() { return articles.stream() .flatMap(article -> article.getTags().stream()) .collect(Collectors.toSet()); } 棒极了!flatmap 帮我把标签列表转为一个返回流,然后我们使用 collect 去创建一个集合作为返回值。 一切皆有可能 以上的就是如何使用可读性更高的代码代替循环的例子。务必仔细看看Stream API,因为这篇文章仅仅提到它的一些皮毛而已。 更新 收到solarfuse和dhruvgairola的评论后,更新了getDistinctTags()例子,使用集合(Set)作为返回集合。
每个Linux新手都应该记住的10个基本Linux命令 Linux对我们的生活有着很大的影响。对于新手来说,头一次入手Linux只会让你觉得不适。因为在Linux上,你通常应该使用终端命令,而不是只要点击启动器图像(就像你在Windows上操作那样)。不过别担心,本文介绍的这10个基本的Linux命令和重要命令会帮助你尽快入门。 【51CTO.com快译】Linux对我们的生活有着很大的影响。至少,你的安卓手机上面就有Linux内核。然而,头一次入手Linux只会让你觉得不适。因为在Linux上,你通常应该使用终端命令,而不是只要点击启动器图像(就像你在Windows上操作那样)。不过别担心,下面介绍的这10个基本的Linux命令和重要命令会帮助你尽快入门。 我们在谈论Linux命令时,其实是在谈论Linux系统本身。就这么区区10个基本的Linux命令不会让你成为天才或Linux专家。它会帮助Linux新手使用这些Linux基本命令或者说Linux常用命令来处理日常的基本任务。 好了,下面不妨逐一介绍这10个Linux基本命令。 1.sudo 这个SuperUserDo是Linux新手要使用的最重要的命令。需要根权限的每一个命令都需要这个sudo命令。你可以在需要根权限的每个命令之前使用sudo。 $ sudo su 2.ls(list) 就跟别人一样,你常常想要看到目录里面的任何内容。借助list命令,终端就会显示你正在处理的那个目录里面的所有文件和文件夹。假设我在/home文件夹里面,想查看/home里面的目录和文件。 /home$ ls /home中的ls返回下列结果: imad lost+found 3.cd 更改目录(cd)是始终在终端中使用的主要命令。它是最基本的Linux命令之一。使用这个命令很简单。只要输入你想要从当前目录进入到的那个文件夹的名称。如果想要返回上一级,只要将双圆点(..)作为参数。 假设我在/home目录中,想进入到始终在/home里面的usr目录。下面是我可以使用cd命令的方法: /home $ cd usr /home/usr $ 4.mkdir 仅仅更改目录还不全面。有时候,你想要创建一个新的文件夹或子文件夹。可以使用mkdir命令来做到这一点。只要在终端中将你的文件夹名称放在mkdir命令的后面即可。 ~$ mkdir folderName 5.cp 拷贝粘贴是我们为了组织整理文件而需要完成的重要任务。使用cp将帮助你从终端拷贝粘贴文件。首先,你确定想要拷贝的那个文件,然后输入目的地位置,即可粘贴文件。 $ cp src des 注意:如果你将文件拷贝到任何新文件都需要根权限的目录,那么你就需要使用sudo命令。 6.rm rm这个命令可以移除你的文件,甚至移除你的目录。如果文件需要根权限才能移除,可以使用-f。你还可以使用-r来进行递归移除,从而移除你的文件夹。 $ rm myfile.txt 7.apt-get 就不同的发行版而言,这个命令各不相同。在基于Debian的Linux发行版中,想安装、移除和升级任何软件包,我们可以使用高级包装工具(APT)软件包管理器。apt-get命令可帮助你安装需要在Linux中运行的软件。这是个功能强大的命令行工具,可以执行安装、升级、甚至移除软件这类任务。 在其他发行版(比如Fedora和Centos)中,有不同的软件包管理器。Fedora过去有yum,但现在它有dnf。 $ sudo apt-get update $ sudo dnf update 8.grep 你需要找到一个文件,但是又记不得它的确切位置或路径。grep可以帮助你解决这个问题。你可以使用grep命令,根据给定的关键字帮助找到文件。 $ grep user /etc/passwd 9.cat 作为用户,你常常需要查看来自脚本的一些文档或代码。同样,其中一个Linux基本命令是cat命令。它会为你显示文件里面的文本。 $ cat CMakeLists.txt 10.poweroff 最后一个命令是poweroff。有时候,你需要直接从终端来关机。这个命令就能完成这项任务。别忘了在命令的开头添加sudo,因为它需要根权限才能执行poweroff。 $ sudo poweroff 结束语 正如我在文章开头提到:这10个基本的Linux命令不会立马让你变成Linux极客。它会帮助你在这个早期阶段开始使用Linux。借助这些基本的Linux命令,开始使用Linux,并且定个目标:每天学会使用1个至3个命令。这就是本文的目的,但愿对你有所帮助。 原文标题:10 Basic Linux Commands That Every Linux Newbies Should Remember,作者:Mohd Sohail 【51CTO译稿,合作站点转载请注明原文译者和出处为51CTO.com】
一、策略模式 设计原则: 1.把变化的部分封装起来,好让不变的部分不受影响 2.针对接口编程(针对超类型编程),而不是针对实现编程,接口行为单独实现变化部分做接口,作为成员变量使用,在构造方法或者setter中赋予具体的实现类,每次变化只需要改实现类3.多用组合,少用继承
一、class类的使用 1.类也是对象,是java.lang.Class的实例对象 2.任何一个类都是Class类的实例对象,有三种表示方式:(Class的构造方法是私有的,只允许JVM调用,因此无法通过关键词new创建Class的实例对象)• 通过类名.class创建,也即说明任何一个类都有一个静态的成员变量class 如:Class c1 = Foo.class;• 通过类的对象的getClass方法 如:Foo foo1 = new Foo(); Class c2 = foo1.getClass(); • 通过Class的forName方法传入类的全限定名获取 如:Class c3 = null; c3 = Class.forName("com.imooc.reflect.Foo");(会有 ClassNotFoundException) • c1、c2表示的Foo类的类类型(class type) 万事万物皆对象,类也是对象,是Class类的实例对象,这个对象称为类的类类型 可以通过类的类类型 创建该类的对象§ 如:Foo foo2 = (Foo)c1.newInstance(); • 一个类只能是Class的一个实例对象,无论通过三种方法中的哪一种获取都一样 • 三种方法中,c1、c2为静态加载类(编译时加载),而c3为动态加载类(运行时加载) 二、动态加载类 1.编译时刻加载类是静态加载类、运行时刻加载类是动态加载类 2.new 创建对象是静态加载类,在编译时刻就需要加载所有的可能使用到的类。 3.Class c=Class.foName(args[0])为动态加载类,在运行时刻加载 4.通过类类型,创建该对象 OfficeAble oa=c.newInstance();//OfficeAble是一个接口 三、获取方法信息 1.void等关键字都存在类类型,比如 Class c=int.class;Class c1=String.class; 2.Method类,方法对象,一个成员方法就是一个Method对象,getMethods()方法获取的就是所有的public的方法,包括父类继承而来的, getDeclaredMethods()获取的是所有该类自己声明的方法,不会访问继承而来的方法; 3.Method[]ms=c.getMethods();获取c类中所有的方法,Class returnType=ms[i].getReturnType();获取方法的返回值类型的类类型; ms[i].getName();获取方法的名称 4.Class[]paramTypes=ms[i].getParameterTypes();获取参数类型--->得到的是参数列表的类型的类类型; Class c1 = int.class; // int基本类型的类类型 Class c2 = String.class; // String类的类类型 c1.getName(); // 返回int c2.getName(); // 返回java.lang.String c2.getSimpleName(); // 返回String Methods[] ms = c1.getMethods(); // 获取该类的所有公有方法,包括继承的方法 Methods[] ms = c1.getDeclaredMethods(); / 获取所有自己声明的方法,包括私有方法 for(int i=0;i<ms.length;i++){ Class returnType = ms[i].getReturnType; // 获取这个方法的返回值的类类型 returnType.getName(); // 该方法的返回值的类类型的名称 ms[i].getName(); // 该方法的名称 Class[] paramTypes = ms[i].getParameterType(); // 获取该方法的参数的类型的类类型的数组 for(Class class1: paramType){ class1.getName(); // 获取该参数的类型的名称 } } 四、获取成员变量&构造函数 一、成员变量是java.lang.reflect.Field的对象 1、Field类封装了关于成员变量的操作 2、Field[] fs = c.getFields()方法获取所有public的成员变量Field[]信息 3、c.getDeclaredFields获取的是该类自己声明的成员变量信息 4、field.getType()获得成员类型的类类型 5、field.getName()获得成员的名称 二、构造函数是java.lang.Constructor类的对象 1、通过Class.getConstructor()获得Constructor[]所有公有构造方法信息 2、建议getDeclaredConstructors()获取自己声明的构造方法 3、Constructor.getName():String 4、Constructor.getParameterTypes():Class[] 成员变量也是对象,是java.lang.reflect.Field的对象; 五、方法的反射操作 方法的反射: 1.获取A类中的print(int,int)方法: ①要获取一个方法就是获取类的信息,获取类的信息首先要获取类的类类型 A a1=new A(); Class c= a1.getClass(); ②获取方法 由名称和参数列表来决定,getMethod获取的是public方法,getDelcaredMethod获取自己声明的方法 Method m =c.getMethod(methodName,paramtypes);//paramtypes可以用数组的形式 表示new Class[]{int.class,int.class},也可以直接列举类类型 2.方法的反射操作:是用m对象来进行方法调用,和a1.print(10,20)调用的方法相同 m.invoke(a1,new Object[]{10,20}) Object o=m.invoke(对象名,参数);//方法如果没有返回值返回null,如果有返回值返回具体值,参数可用数组的方式表示,也可以直接列举,没 有参数就不写 public Class A{ public void print(){}; public void Print(Sting a,String b){} public void Print(int a,int b){}; } public Class B{ public static void main(String[] args){ A a1 = new A(); Class c= a1.getclass; Method getMet=c.getMethod("print",String.class,String.class);//忘了加引号 Object obj=getMet.invoke(a1,"df","df"); } } 六、通过反射了解集合泛型的本质 1:反射的操作都是编译之后的操作;就是运行阶段 2:java中集合的泛型是防止错误输入的;只在编译阶段有效,只要绕过编译就无效啦 我们可以通过方法的反射来操作,绕过编译 ArrayList list1=new ArrayList(); ArrayList<String> list2=new ArrayList<String>(); Class c1=list1.getClass(); Class c2=list2.getClass(); System.out.print(c1==c2);//true Method m=c2.getMethod("add",Object.class); m.invoke(list2,20);//向list2集合中添加一个int 型的值;绕过编译 当然是不能直接foreach list2集合的,会报类型转换错误
Nodejs 内置的npm默认会把模块安装在c盘的用户AppData目录下(吐槽一下:不明白为啥现在的软件都喜欢把资源装在这里) C盘这么小,肯定是不行的,下面一步步修改到D盘 1.打开cmd命令行,查看当前配置 输入 npm config ls 先看一下当前npm的配置环境,由于我已经修改过,所以可以看到修改后的路径 2.修改路径 这里需要修改两个路径,module路径和cache路径 module对应prefix cache对应cache 首先在别的盘新建两个目录 D:\nodejs\node_modules\npm\node_global_modules D:\nodejs\node_modules\npm\node_cache 然后依次执行 npm config set prefix="D:\nodejs\node_modules\npm\node_global_modules" npm config set cache="D:\nodejs\node_modules\npm\node_cache" 3.修改环境变量 新增环境变量 NODE_HOME 修改Path,追加 %NODE_HOME%\;%NODE_HOME%\node_modules;%NODE_HOME%\node_modules\npm\node_global_modules\; 4.测试一下 重新打开一个cmd命令行,安装一个插件试试 执行 npm install cordova -g// -g意思是安装到全局目录下 安装完毕后打开设置的安装路径看下是否成功
, { xtype: 'button', text: '格式化', iconCls: 'icon-edit', colspan: 2, width: 100, margin: "-23 0 0 70", listeners: { 'click': { fn: this.formatJson, scope: this } } } formatJson : function(options) { var dataform = this.down('dataform'); var json=dataform.down("[name='actConfigValue']").getValue(); var reg = null, formatted = '', pad = 0, PADDING = ' '; // one can also use '\t' or a different number of spaces // optional settings options = options || {}; // remove newline where '{' or '[' follows ':' options.newlineAfterColonIfBeforeBraceOrBracket = (options.newlineAfterColonIfBeforeBraceOrBracket === true) ? true : false; // use a space after a colon options.spaceAfterColon = (options.spaceAfterColon === false) ? false : true; // begin formatting... if (typeof json !== 'string') { // make sure we start with the JSON as a string json = JSON.stringify(json); } else { // is already a string, so parse and re-stringify in order to remove extra whitespace json = JSON.parse(json); json = JSON.stringify(json); } // add newline before and after curly braces reg = /([\{\}])/g; json = json.replace(reg, '\r\n$1\r\n'); // add newline before and after square brackets reg = /([\[\]])/g; json = json.replace(reg, '\r\n$1\r\n'); // add newline after comma reg = /(\,)/g; json = json.replace(reg, '$1\r\n'); // remove multiple newlines reg = /(\r\n\r\n)/g; json = json.replace(reg, '\r\n'); // remove newlines before commas reg = /\r\n\,/g; json = json.replace(reg, ','); // optional formatting... if (!options.newlineAfterColonIfBeforeBraceOrBracket) { reg = /\:\r\n\{/g; json = json.replace(reg, ':{'); reg = /\:\r\n\[/g; json = json.replace(reg, ':['); } if (options.spaceAfterColon) { reg = /\:/g; json = json.replace(reg, ': '); } $.each(json.split('\r\n'), function(index, node) { var i = 0, indent = 0, padding = ''; if (node.match(/\{$/) || node.match(/\[$/)) { indent = 1; } else if (node.match(/\}/) || node.match(/\]/)) { if (pad !== 0) { pad -= 1; } } else { indent = 0; } for (i = 0; i < pad; i++) { padding += PADDING; } formatted += padding + node + '\r\n'; pad += indent; }); dataform.down("[name='actConfigValue']").setValue(formatted); // return formatted; },
在VMware里克隆出来的CentOS Linux。。 ifconfig...没有看到eth0.。然后重启网卡又报下面错误。 故障现象: service network restart Shutting down loopback insterface: [ OK ] Bringing up loopback insterface: [ OK ] Bringing up interface eth0: Device eth0 does not seem to be present,delaying initialization. [FAILED] 解决办法: 首先,打开/etc/udev/rules.d/70-persistent-net.rules内容如下面例子所示: # vi /etc/udev/rules.d/70-persistent-net.rules # This file was automatically generated by the /lib/udev/write_net_rules # program, run by the persistent-net-generator.rules rules file. # # You can modify it, as long as you keep each rule on a single # line, and change only the value of the NAME= key. # PCI device 0x1022:0x2000 (pcnet32) SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:0c:29:8f:89:9 7", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0" # PCI device 0x1022:0x2000 (pcnet32) SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="00:0c:29:50:bd:1 7", ATTR{type}=="1", KERNEL=="eth*", NAME="eth1" 记录下,eth1网卡的mac地址00:0c:29:50:bd:17 接下来,打开/etc/sysconfig/network-scripts/ifcfg-eth0 # vi /etc/sysconfig/network-scripts/ifcfg-eth0 将 DEVICE="eth0" 改成 DEVICE="eth1" , 将 HWADDR="00:0c:29:8f:89:97" 改成上面的mac地址 HWADDR="00:0c:29:50:bd:17" 最后,重启网络 # service network restart 或者 # /etc/init.d/network restart 正常了。
单点登录SSO(Single Sign On)说得简单点就是在一个多系统共存的环境下,用户在一处登录后,就不用在其他系统中登录,也就是用户的一次登录能得到其他所有系统的信任。单点登录在大型网站里使用得非常频繁,例如像阿里巴巴这样的网站,在网站的背后是成百上千的子系统,用户一次操作或交易可能涉及到几十个子系统的协作,如果每个子系统都需要用户认证,不仅用户会疯掉,各子系统也会为这种重复认证授权的逻辑搞疯掉。实现单点登录说到底就是要解决如何产生和存储那个信任,再就是其他系统如何验证这个信任的有效性,因此要点也就以下两个: 存储信任 验证信任 如果一个系统做到了开头所讲的效果,也就算单点登录,单点登录有不同的实现方式,本文就罗列我开发中所遇见过的实现方式。 以Cookie作为凭证媒介 最简单的单点登录实现方式,是使用cookie作为媒介,存放用户凭证。 用户登录父应用之后,应用返回一个加密的cookie,当用户访问子应用的时候,携带上这个cookie,授权应用解密cookie并进行校验,校验通过则登录当前用户。 Auth via cookie 不难发现以上方式把信任存储在客户端的Cookie中,这种方式很容易令人质疑: Cookie不安全 不能跨域实现免登 对于第一个问题,通过加密Cookie可以保证安全性,当然这是在源代码不泄露的前提下。如果Cookie的加密算法泄露,攻击者通过伪造Cookie则可以伪造特定用户身份,这是很危险的。 对于第二个问题,更是硬伤。 通过JSONP实现 对于跨域问题,可以使用JSONP实现。 用户在父应用中登录后,跟Session匹配的Cookie会存到客户端中,当用户需要登录子应用的时候,授权应用访问父应用提供的JSONP接口,并在请求中带上父应用域名下的Cookie,父应用接收到请求,验证用户的登录状态,返回加密的信息,子应用通过解析返回来的加密信息来验证用户,如果通过验证则登录用户。 Auth via jsonp 这种方式虽然能解决跨域问题,但是安全性其实跟把信任存储到Cookie是差不多的。如果一旦加密算法泄露了,攻击者可以在本地建立一个实现了登录接口的假冒父应用,通过绑定Host来把子应用发起的请求指向本地的假冒父应用,并作出回应。 因为攻击者完全可以按照加密算法来伪造响应请求,子应用接收到这个响应之后一样可以通过验证,并且登录特定用户。 通过页面重定向的方式 最后一种介绍的方式,是通过父应用和子应用来回重定向中进行通信,实现信息的安全传递。 父应用提供一个GET方式的登录接口,用户通过子应用重定向连接的方式访问这个接口,如果用户还没有登录,则返回一个的登录页面,用户输入账号密码进行登录。如果用户已经登录了,则生成加密的Token,并且重定向到子应用提供的验证Token的接口,通过解密和校验之后,子应用登录当前用户。 Auth via redirect 这种方式较前面两种方式,接解决了上面两种方法暴露出来的安全性问题和跨域的问题,但是并没有前面两种方式方便。 安全与方便,本来就是一对矛盾。 使用独立登录系统 一般说来,大型应用会把授权的逻辑与用户信息的相关逻辑独立成一个应用,称为用户中心。 用户中心不处理业务逻辑,只是处理用户信息的管理以及授权给第三方应用。第三方应用需要登录的时候,则把用户的登录请求转发给用户中心进行处理,用户处理完毕返回凭证,第三方应用验证凭证,通过后就登录用户。
一、UML5个互联视图 UML(Unified Modeling Language)中常用5个互联的视图来描述系统的体系结构。如图 (1)用例视图(Use-case View) 由专门描述可被最终用户、分析人员、测试人员看到的系统行为的用例图组成。 最终用户使用用例图——理解要完成的系统的功能,确认是否符合自己的要求。 分析人员使用用例图——描述用户需求。 测试人员使用用例图——根据用例图验证实现后的系统是否符合用户需求。 (2)设计视图(Logical View) 包含了主要的设计包、子系统、类和接口,主要从软件角度描述系统要解决的问题和解决方案。 (3)进程视图(Process View) 主要针对系统性能、可扩展行和吞吐量。 (4)实现视图(Implementation View) 在UML实现视图用类图、包图、对象图、顺序图、合作图、状态图、和活动图来描述。编程人员根据设计视图和进程视图来最终实现系统。 (5)部署视图(Deployment View) 包含了系统硬件拓扑结构点各种软件模块和构件模块。 二、UML在软件开发各个阶段的应用 经典的软件工程思想将软件开发分成5个阶段:可行性分析与项目开发计划、需求分析(系统分析)、系统设计、系统实现、测试、维护六个阶段。其中UML在各个阶段都有不同的应用,除了学习每种图的具体画法,还要注意学习UML图在软件开发过程中每个阶段的应用。 需求 --采用用例图描述需求。 分析 --采用类图描述静态结构 --采用顺序图、合作图、活动图、状态图描 述动态行为 设计 --采用类图、包,对类的接口进行设计 实现 --将类用某现象对象语言实现 继承与交付 --构件图、包、部署图 --单元测试——类图和类的说明书 --继承测试——类图、包、构件图、合作图 --系统测试——例图 ————————————————————————————————————————————————————————————————————————————— 首先,UML建模分为: 类图、对象图、顺序图、合作图、使用案例图、状态图、活动图、组件图、部署图等。 其次,它又分为以下三大点: 一 概念级建模 1.事务用例建模(事务用例模型、活动图) 2.事务对象建模(事务对象模型、序列图) 二 逻辑级建模、 1.需求定义(系统用例建模、系统用例描述) 2.分析和初步设计(类图、序列图、陈述图) 三 物理级建模 1.详细设计(类图、数据库设计模型、DDL脚本、数据库、部件图、分布图)
1、用例图(use case diagrams) 【概念】描述用户需求,从用户的角度描述系统的功能 【描述方式】椭圆表示某个用例;人形符号表示角色 【目的】帮组开发团队以一种可视化的方式理解系统的功能需求 【用例图】 2、静态图 类图(class diagrams) 【概念】显示系统的静态结构,表示不同的实体是如何相关联的 【描述方式】三个矩形 【目的】表示一个逻辑类或实现类,逻辑类通常是用户的业务所涉及的事物;实现类是程序员处理的实体 【类图】 对象图(object diagrams) 【概念】类图的一个实例,描述系统在具体时间点上所包含的对象以及各个对象的关系 【对象图】 3、交互图 用来描述对象之间的交互关系 序列图(顺序图) 【概念】描述对象之间的交互顺序,着重体现对象间消息传递的时间顺序 【描述方式】横跨图的顶部,每个框表示每个类的实例或对象;类实例名称和类名称使用冒号分开 【目的】显示流程中不同对象之间的调用关系,还可以显示不同对象的不同调用。 【序列图】 协作图(Collaboration diagrams) 【概念】描述对象之间的合作关系,侧重对象之间的消息传递 4、行为图:描述系统的动态模型和对象之间的交互关系 1.状态图(Statechart diagrams) 【概念】描述对象的所有状态以及事件发生而引起的状态之间的转移 【描述方式】 起始点:实心圆 状态之间的转换:使用开箭头的线段 状态:圆角矩形 判断点:空心圆 一个或多个终止点:内部包含实心圆的圆 【目的】表示某个类所处的不同状态以及该类在这些状态中的转换过程 2.活动图(Activity diagrams) 【概念】描述满足用例要求所要进行的活动以及活动时间的约束关系 【描述方式】 起始点:实心圆 活动:圆角矩形 终止点:内部包含实心圆的圆 泳道:实际执行活动的对象 【目的】表示两个或多个对象之间在处理某个活动时的过程控制流程 【活动图】 活动图和状态图区别: 5、实现图 构件图(Component diagrams) 【概念】描述代码构件的物理结构以及各构件之间的依赖关系 【描述方式】构件 【目的】提供系统的物理视图,根据系统的代码构件显示系统代码的整个物理结构 【构架图】 部署图(Deployment diagrams) 【概念】系统中硬件的物理体系结构 【描述方式】 三维立方体表示部件 节点名称位于立方体上部 【目的】显示系统的硬件和软件的物理结构 【部署图】
1.Ext.onReady 说明:onReady内的语句块会在页面上下文加载后再执行 例子: Ext.onReady(function () { //获取页面元素,元素ID为“onReady” var input = Ext.get("onReady"); Ext.Msg.alert("隐藏控件内容",input.dom.value); }); 执行结果: 如果代码段没有放入Ext.onReady中,会报错: 2.Ext.define 说明:创建类,可以继承其他类,也可以被继承 例子1: Ext.onReady(function () { //创建一个类,类名:TextClass,具有两个属性:A、B Ext.define('TextClass', { A: 'a', B: 'b' }); //实例化类 var textClass = new TextClass(); //输出属性名 Ext.Msg.alert('类属性', textClass.A + " " + textClass.B); }); 执行结果: 例子2: Ext.onReady(function () { //创建一个类,类名:TextClass,具有两个属性:A、B Ext.define('TextClass', { A: 'a', B: 'b' }); //创建一个类,继承TextClass Ext.define("TextClass2", { extend: 'TextClass',//继承TextClass C: 'c'//TextClass2特有的属性 }) var textClass2 = new TextClass2(); Ext.Msg.alert("TextClass2属性",textClass2.A+ " "+textClass2.B+ " "+textClass2.C) }); 执行结果: 3.Ext.create 说明:实例化类,在EXTJS4中建议用create方法实例化类 Ext.onReady(function () { //创建一个类,类名:TextClass,具有两个属性:A、B Ext.define('TextClass', { A: 'a', B: 'b' }); var textClass = Ext.create("TextClass") Ext.Msg.alert('textClass属性', textClass.A + ' ' + textClass.B ) }); 执行结果:
(问题关键词: kindeditor 上传图片失败 kindeditor上传图片成功,但是页面上却提示失败 kindeditor得到Json正确,确提示失败) 今天又遇到了一个比较无奈的问题,在我编写SSM框架的项目时,遇到了kindeditor上传图片失败的问题。 如图: 最开始我以为是服务器的问题,比如返回值,但是发现最后返回的Json数据是正确的,虽然提示失败, 但是图片服务器中其实已经成功上传了文件,并且可以利用json中的url访问到, 之后又排查是否浏览器收到正确的Json,答案是肯定的。。。 如图: 其他都没问题,那么问题就到了Kindeditor这边,也许是它本身的逻辑出现了问题。 事实证明确实是这样 原因在于 kindeditor 的 K.json 方法并没有做强制类型转换,并且做了原型校验,导致了该问题的发生。 解决方案有两个: 1.使用的是 kindeditor-all-min.js 脚本: 修改脚本268行标记处为error!=0 2.使用的是 kindeditor-all.js 脚本: 修改脚本7948行标记处为 error!=0 修改以后完美解决:
在将nginx目录设置为ftp目录访问时会报错:403 forbidden 这是权限问题,解决方法是在配置文件中增加User vi /usr/local/nginx/conf/nginx.conf 增加user可以是root,也可以使ftp用户名,例如:user ftpuser;
设置Linux防火墙允许端口通过 问题: (CentOS Nginx80端口不通,windows浏览器无法访问虚拟机CentOS中的Nginx -A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 80 -j ACCEPT无效 报错) 一、对于端口不通,最粗暴的解决办法就是关掉防火墙, 输入命令:service iptables stop 但是这种方法有个弊端,就是每次启动CentOS的时候都得再次关闭,没有从根本上解决问题。 二、根本的办法是让CentOS的防火墙允许80端口通过 般装好的服务器都是没有设置防火墙的,现在我们假设就设置了ssh,开放20端口,其它的都没有设置 1.打开iptables的配置文件:vi /etc/sysconfig/iptables 可以理解为定义了一个链: :RH-Firewall-1-INPUT - [0:0] 这里是把INPUT和FORWARD的所有包都转发到RH-Firewall-1-INPUT,这是重点,也就意味着, 只要定义好RH-Firewall-1-INPUT,就定义好了INPUT和FORWARD两个链 -A INPUT -j RH-Firewall-1-INPUT -A FORWARD -j RH-Firewall-1-INPUT 允许80端口的数据包传输: -A RH-Firewall-1-INPUT -m state –state NEW -m tcp -p tcp –dport 80 -j ACCEPT 按esc 然后 :wq 保存退出 2.然后停止并重新启动: service iptables stop service iptables start 验证一下是否规则都已经生效:iptables -L 3.访问服务器,记得写80端口,如:192.168.1.127:80 访问成功 其他端口的设置也同样
一、需要连接外网,配置ip (可以解决以下报错: 1.vmnet8 无internet访问权限 2.CentOS ping不通外网 3.CentOS ping unknown hostname 4.Error: Cannot find a valid baseurl for repo: base 5.CentOS yum Couldn't resolve host 'mirrorlist.centos.org 6.CentOS yum Could not retrieve mirrorlist http://mirrorlist.centos.org/?release=6&arch=x ) 1.配置ip文件 命令: vi /etc/sysconfig/network-scripts/ifcfg-eth0 DEVICE=eth0 HWADDR=00:0C:29:70:F3:E3TYPE=EthernetUUID=405d5ad6-b134-4a60-b2f8-7d99c716a561ONBOOT=yes #开机启动NM_CONTROLLED=yesBOOTPROTO=dhcpIPV6INIT=no #禁用ipv6IPADDR=192.168.0.128#静态ip地址NETMASK=255.255.255.0#子网掩码 GATEWAY=192.168.0.1#网关DNS1=192.168.0.1#配置网关地址为dns1DNS2=8.8.8.8 #配置google dns 2. 设置网关 命令:vi /etc/sysconfig/network NETWORKING=yes#系统是否使用网络 HOSTNAME=XXXX#可修改host名 GATEWAY=192.168.0.1#设置网关 3.打开网络连接设置 4.将可以上网的网卡设置为共享(我的是最后一个“无线网络连接”) 属性->共享->选择VMnet8 5.共享网络后,虚拟网卡VMnet8网卡的IP地址变成如下 选择ipv4然后修改: ip地址为你所设置的网关地址(我的数据只是示例) 6.然后将虚拟机的网络连接设置为VMnet8(NAT): 3.# service network restart 重启网络服务和网卡 # ifconfig 可以查看网络设置 #ping 192.168.0.1 检查网关是否通 #ping www.baidu.com 检查域名解析是否正常 连接成功,大功告成!
1.jdbc报错:java.lang.ClassNotFoundException: com.mysql.jdbc.driver 严重: Servlet.service() for servlet [daleystore-manager] in context with path [] threw exception [Request processing failed; nested exception is org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException: ### Error querying database. Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: com.mysql.jdbc.driver ### The error may exist in com/daleystore/mapper/TbItemMapper.xml ### The error may involve com.daleystore.mapper.TbItemMapper.selectByExample ### The error occurred while executing a query ### Cause: org.springframework.jdbc.CannotGetJdbcConnectionException: Could not get JDBC Connection; nested exception is java.sql.SQLException: com.mysql.jdbc.driver] with root cause java.lang.ClassNotFoundException: com.mysql.jdbc.driver 起初我以为是框架的错误,从头到尾检查了一遍,最后才发现是.properties的配置文件中出错。 如果你也出现这种错误,百分之90%是这里写错了,有可能是用户名密码写错, 这里注意:在写username,password的时候要加上前缀 jdbc. 如 jdbc.username ,否则会认为是JVM系统环境变量的username,password同理。 也有可能是其他一些属性出错。 我这里的问题是jdbc.driver=com.mysql.jdbc.Driver的driver小写了。
在使用GitHub push最新的文件时,有时可能会出错,下面列出解决方案,这时需要用到GitShell 1.报错如下 fatal: The remote end hung up unexpectedly fatal: The remote end hung up unexpectedly error: RPC failed; curl 56 SSL read: error:00000000:lib(0):func(0):reason(0), er rno 10053 Everything up-to-date 这时,很有可能是上传的文件太大,缓存不够,默认只有1M,现在我们改为500M 执行如下命令 git config http.postBuffer 524288000,然后git push
作用:实现了Aware接口的bean在初始化后可以获取相应资源并进行相应的操作 举例: ApplicationContextAware:向实现了该接口的bean提供IOC容器的上下文信息(ApplicationContext),实现了该接口的bean必须配置到配置文件中并由spring的bean容器加载 BeanNameAware:向实现了该接口的bean提供关于beanName定义的相关内容 其他就做不介绍了,官网文档内容如下: Table 7.4. Aware interfaces Name Injected Dependency Explained in… ApplicationContextAware Declaring ApplicationContext Section 7.6.2, “ApplicationContextAware and BeanNameAware” ApplicationEventPublisherAware Event publisher of the enclosing ApplicationContext Section 7.15, “Additional Capabilities of the ApplicationContext” BeanClassLoaderAware Class loader used to load the bean classes. Section 7.3.2, “Instantiating beans” BeanFactoryAware Declaring BeanFactory Section 7.6.2, “ApplicationContextAware and BeanNameAware” BeanNameAware Name of the declaring bean Section 7.6.2, “ApplicationContextAware and BeanNameAware” BootstrapContextAware Resource adapter BootstrapContext the container runs in. Typically available only in JCA aware ApplicationContexts Chapter 32, JCA CCI LoadTimeWeaverAware Defined weaver for processing class definition at load time Section 11.8.4, “Load-time weaving with AspectJ in the Spring Framework” MessageSourceAware Configured strategy for resolving messages (with support for parametrization and internationalization) Section 7.15, “Additional Capabilities of the ApplicationContext” NotificationPublisherAware Spring JMX notification publisher Section 31.7, “Notifications” PortletConfigAware Current PortletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext Chapter 25, Portlet MVC Framework PortletContextAware Current PortletContext the container runs in. Valid only in a web-aware Spring ApplicationContext Chapter 25, Portlet MVC Framework ResourceLoaderAware Configured loader for low-level access to resources Chapter 8, Resources ServletConfigAware Current ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext Chapter 22, Web MVC framework ServletContextAware Current ServletContext the container runs in. Valid only in a web-aware Spring ApplicationContext Chapter 22, Web MVC framework Resource Resources (针对于资源文件的统一接口) A、UrlResource:URL 对应的资源,根据一个 URL 地址即可获取 B、ClassPathResource:获取类路径下的资源 C、FileSystemResource:获取文件系统里面的资源 D、ServletContextResource:ServletContext 封装的资源,用于访问 ServletContext 环境下的资源 E、InputStreamResource:获取输入流封装的资源 F、ByteArrayResource:获取字节数组封装的资源 通过实现 ApplicationContextAware 接口中的方法 ,因为需要实现ResourceLoader,而 ApplicationContextAware 实现了ResourceLoader setApplicationContext(ApplicationContext applicationContext)Resource resource = applicationContext.getResource("xxx");具体演示示例见github 项目地址:https://github.com/DaleyChao/SpringPractice/commit/e84bffe0d2c72fa853d9bb1ffc4d4186e5de0e10
Bean的自动装配(Autowiring) 四种类型: 1.No:不做任何操作 2.byname:根据属性名自动装配。此选项将检查容器并根据名字查找与属性完全一致的bean,并将其与属性自动装配 3.byType:如果容器中存在一个与指定属性类型相同的bean,那么将与该属性自动装配;如果存在多个该类型的bean,那么抛出异常,并指出不能使用byType方式进行自动装配;如果没有找到相匹配的bean,则什么事都不发生 4.Constructor:与byType方式类似,不同之处在于它应用于构造器参数。如果容器中没有找到与构造器参数类型一致的bean,那么抛出异常 Bean的自动装配:在beans标签配置属性 default-autowire="no/byName/byType/constructor" 作用为:省去了在Spring的xml中配置property标签和constructor-arg标签,只需要配置bean标签即可 PS:byName和byType为设值注入,constructor为构造注入; byName要求bean标签的id属性需要和成员变量的名称一致, byType和constructor则跟id无关 xml示例: <?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" default-autowire="constructor"> <!-- default-autowire="byName" --> <bean id="autoWiringService" class="com.daley.springprac.prac1.autowiring.AutoWiringService" /> <bean id="autoWiringDAO" class="com.daley.springprac.prac1.autowiring.AutoWiringDAO" /> </beans> 演示项目地址:https://github.com/DaleyChao/SpringPractice/commit/de3a447e668236aebd0fcddb1a2272310d58c3e2
Java集合---HashMap源码剖析 一、HashMap概述二、HashMap的数据结构三、HashMap源码分析 1、关键属性 2、构造方法 3、存储数据 4、调整大小 5、数据读取 6、HashMap的性能参数 7、Fail-Fast机制 一、HashMap概述 HashMap基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了不同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 值得注意的是HashMap不是线程安全的,如果想要线程安全的HashMap,可以通过Collections类的静态方法synchronizedMap获得线程安全的HashMap。 Map map = Collections.synchronizedMap(new HashMap()); 二、HashMap的数据结构 HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是因为它是通过计算散列码来决定存储的位置。HashMap中主要是通过key的hashCode来计算hash值的,只要hashCode相同,计算出来的hash值就一样。如果存储的对象对多了,就有可能不同的对象所算出来的hash值是相同的,这就出现了所谓的hash冲突。学过数据结构的同学都知道,解决hash冲突的方法有很多,HashMap底层是通过链表来解决hash冲突的。 图中,紫色部分即代表哈希表,也称为哈希数组,数组的每个元素都是一个单链表的头节点,链表是用来解决冲突的,如果不同的key映射到了数组的同一位置处,就将其放入单链表中。 我们看看HashMap中Entry类的代码: /** Entry是单向链表。 * 它是 “HashMap链式存储法”对应的链表。 *它实现了Map.Entry 接口,即实现getKey(), getValue(), setValue(V value), equals(Object o), hashCode()这些函数 **/ static class Entry<K,V> implements Map.Entry<K,V> { final K key; V value; // 指向下一个节点 Entry<K,V> next; final int hash; // 构造函数。 // 输入参数包括"哈希值(h)", "键(k)", "值(v)", "下一节点(n)" Entry(int h, K k, V v, Entry<K,V> n) { value = v; next = n; key = k; hash = h; } public final K getKey() { return key; } public final V getValue() { return value; } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } // 判断两个Entry是否相等 // 若两个Entry的“key”和“value”都相等,则返回true。 // 否则,返回false public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; } // 实现hashCode() public final int hashCode() { return (key==null ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode()); } public final String toString() { return getKey() + "=" + getValue(); } // 当向HashMap中添加元素时,绘调用recordAccess()。 // 这里不做任何处理 void recordAccess(HashMap<K,V> m) { } // 当从HashMap中删除元素时,绘调用recordRemoval()。 // 这里不做任何处理 void recordRemoval(HashMap<K,V> m) { } } HashMap其实就是一个Entry数组,Entry对象中包含了键和值,其中next也是一个Entry对象,它就是用来处理hash冲突的,形成一个链表。 三、HashMap源码分析 1、关键属性 先看看HashMap类中的一些关键属性: 1 transient Entry[] table;//存储元素的实体数组 2 3 transient int size;//存放元素的个数 4 5 int threshold; //临界值 当实际大小超过临界值时,会进行扩容threshold = 加载因子*容量 6 7 final float loadFactor; //加载因子 8 9 transient int modCount;//被修改的次数 其中loadFactor加载因子是表示Hsah表中元素的填满的程度. 若:加载因子越大,填满的元素越多,好处是,空间利用率高了,但:冲突的机会加大了.链表长度会越来越长,查找效率降低。 反之,加载因子越小,填满的元素越少,好处是:冲突的机会减小了,但:空间浪费多了.表中的数据将过于稀疏(很多空间还没用,就开始扩容了) 冲突的机会越大,则查找的成本越高. 因此,必须在 "冲突的机会"与"空间利用率"之间寻找一种平衡与折衷. 这种平衡与折衷本质上是数据结构中有名的"时-空"矛盾的平衡与折衷. 如果机器内存足够,并且想要提高查询速度的话可以将加载因子设置小一点;相反如果机器内存紧张,并且对查询速度没有什么要求的话可以将加载因子设置大一点。不过一般我们都不用去设置它,让它取默认值0.75就好了。 2、构造方法 下面看看HashMap的几个构造方法: public HashMap(int initialCapacity, float loadFactor) { 2 //确保数字合法 3 if (initialCapacity < 0) 4 throw new IllegalArgumentException("Illegal initial capacity: " + 5 initialCapacity); 6 if (initialCapacity > MAXIMUM_CAPACITY) 7 initialCapacity = MAXIMUM_CAPACITY; 8 if (loadFactor <= 0 || Float.isNaN(loadFactor)) 9 throw new IllegalArgumentException("Illegal load factor: " + 10 loadFactor); 11 12 // Find a power of 2 >= initialCapacity 13 int capacity = 1; //初始容量 14 while (capacity < initialCapacity) //确保容量为2的n次幂,使capacity为大于initialCapacity的最小的2的n次幂 15 capacity <<= 1; 16 17 this.loadFactor = loadFactor; 18 threshold = (int)(capacity * loadFactor); 19 table = new Entry[capacity]; 20 init(); 21 } 22 23 public HashMap(int initialCapacity) { 24 this(initialCapacity, DEFAULT_LOAD_FACTOR); 25 } 26 27 public HashMap() { 28 this.loadFactor = DEFAULT_LOAD_FACTOR; 29 threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); 30 table = new Entry[DEFAULT_INITIAL_CAPACITY]; 31 init(); 32 } 我们可以看到在构造HashMap的时候如果我们指定了加载因子和初始容量的话就调用第一个构造方法,否则的话就是用默认的。默认初始容量为16,默认加载因子为0.75。我们可以看到上面代码中13-15行,这段代码的作用是确保容量为2的n次幂,使capacity为大于initialCapacity的最小的2的n次幂,至于为什么要把容量设置为2的n次幂,我们等下再看。 重点分析下HashMap中用的最多的两个方法put和get 3、存储数据 下面看看HashMap存储数据的过程是怎样的,首先看看HashMap的put方法: public V put(K key, V value) { // 若“key为null”,则将该键值对添加到table[0]中。 if (key == null) return putForNullKey(value); // 若“key不为null”,则计算该key的哈希值,然后将其添加到该哈希值对应的链表中。 int hash = hash(key.hashCode()); //搜索指定hash值在对应table中的索引 int i = indexFor(hash, table.length); // 循环遍历Entry数组,若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出! for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { //如果key相同则覆盖并返回旧值 V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } //修改次数+1 modCount++; //将key-value添加到table[i]处 addEntry(hash, key, value, i); return null; } 上面程序中用到了一个重要的内部接口:Map.Entry,每个 Map.Entry 其实就是一个 key-value 对。从上面程序中可以看出:当系统决定存储 HashMap 中的 key-value 对时,完全没有考虑 Entry 中的 value,仅仅只是根据 key 来计算并决定每个 Entry 的存储位置。这也说明了前面的结论:我们完全可以把 Map 集合中的 value 当成 key 的附属,当系统决定了 key 的存储位置之后,value 随之保存在那里即可。 我们慢慢的来分析这个函数,第2和3行的作用就是处理key值为null的情况,我们看看putForNullKey(value)方法: 1 private V putForNullKey(V value) { 2 for (Entry<K,V> e = table[0]; e != null; e = e.next) { 3 if (e.key == null) { //如果有key为null的对象存在,则覆盖掉 4 V oldValue = e.value; 5 e.value = value; 6 e.recordAccess(this); 7 return oldValue; 8 } 9 } 10 modCount++; 11 addEntry(0, null, value, 0); //如果键为null的话,则hash值为0 12 return null; 13 } 注意:如果key为null的话,hash值为0,对象存储在数组中索引为0的位置。即table[0] 我们再回去看看put方法中第4行,它是通过key的hashCode值计算hash码,下面是计算hash码的函数: 1 //计算hash值的方法 通过键的hashCode来计算 2 static int hash(int h) { 3 // This function ensures that hashCodes that differ only by 4 // constant multiples at each bit position have a bounded 5 // number of collisions (approximately 8 at default load factor). 6 h ^= (h >>> 20) ^ (h >>> 12); 7 return h ^ (h >>> 7) ^ (h >>> 4); 8 } 得到hash码之后就会通过hash码去计算出应该存储在数组中的索引,计算索引的函数如下: 1 static int indexFor(int h, int length) { //根据hash值和数组长度算出索引值 2 return h & (length-1); //这里不能随便算取,用hash&(length-1)是有原因的,这样可以确保算出来的索引是在数组大小范围内,不会超出 3 } 这个我们要重点说下,我们一般对哈希表的散列很自然地会想到用hash值对length取模(即除法散列法),Hashtable中也是这样实现的,这种方法基本能保证元素在哈希表中散列的比较均匀,但取模会用到除法运算,效率很低,HashMap中则通过h&(length-1)的方法来代替取模,同样实现了均匀的散列,但效率要高很多,这也是HashMap对Hashtable的一个改进。 接下来,我们分析下为什么哈希表的容量一定要是2的整数次幂。首先,length为2的整数次幂的话,h&(length-1)就相当于对length取模,这样便保证了散列的均匀,同时也提升了效率;其次,length为2的整数次幂的话,为偶数,这样length-1为奇数,奇数的最后一位是1,这样便保证了h&(length-1)的最后一位可能为0,也可能为1(这取决于h的值),即与后的结果可能为偶数,也可能为奇数,这样便可以保证散列的均匀性,而如果length为奇数的话,很明显length-1为偶数,它的最后一位是0,这样h&(length-1)的最后一位肯定为0,即只能为偶数,这样任何hash值都只会被散列到数组的偶数下标位置上,这便浪费了近一半的空间,因此,length取2的整数次幂,是为了使不同hash值发生碰撞的概率较小,这样就能使元素在哈希表中均匀地散列。 这看上去很简单,其实比较有玄机的,我们举个例子来说明: 假设数组长度分别为15和16,优化后的hash码分别为8和9,那么&运算后的结果如下: h & (table.length-1) hash table.length-1 8 & (15-1): 0100 & 1110 = 0100 9 & (15-1): 0101 & 1110 = 0100 ----------------------------------------------------------------------------------------------------------------------- 8 & (16-1): 0100 & 1111 = 0100 9 & (16-1): 0101 & 1111 = 0101 从上面的例子中可以看出:当它们和15-1(1110)“与”的时候,产生了相同的结果,也就是说它们会定位到数组中的同一个位置上去,这就产生了碰撞,8和9会被放到数组中的同一个位置上形成链表,那么查询的时候就需要遍历这个链 表,得到8或者9,这样就降低了查询的效率。同时,我们也可以发现,当数组长度为15的时候,hash值会与15-1(1110)进行“与”,那么 最后一位永远是0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!而当数组长度为16时,即为2的n次方时,2n-1得到的二进制数的每个位上的值都为1,这使得在低位上&时,得到的和原hash的低位相同,加之hash(int h)方法对key的hashCode的进一步优化,加入了高位计算,就使得只有相同的hash值的两个值才会被放到数组中的同一个位置上形成链表。 所以说,当数组长度为2的n次幂的时候,不同的key算得得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。 根据上面 put 方法的源代码可以看出,当程序试图将一个key-value对放入HashMap中时,程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储位置:如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry 的 value,但key不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部——具体说明继续看 addEntry() 方法的说明。 1 void addEntry(int hash, K key, V value, int bucketIndex) { 2 Entry<K,V> e = table[bucketIndex]; //如果要加入的位置有值,将该位置原先的值设置为新entry的next,也就是新entry链表的下一个节点 3 table[bucketIndex] = new Entry<>(hash, key, value, e); 4 if (size++ >= threshold) //如果大于临界值就扩容 5 resize(2 * table.length); //以2的倍数扩容 6 } 参数bucketIndex就是indexFor函数计算出来的索引值,第2行代码是取得数组中索引为bucketIndex的Entry对象,第3行就是用hash、key、value构建一个新的Entry对象放到索引为bucketIndex的位置,并且将该位置原先的对象设置为新对象的next构成链表。 第4行和第5行就是判断put后size是否达到了临界值threshold,如果达到了临界值就要进行扩容,HashMap扩容是扩为原来的两倍。 4、调整大小 resize()方法如下: 重新调整HashMap的大小,newCapacity是调整后的单位 1 void resize(int newCapacity) { 2 Entry[] oldTable = table; 3 int oldCapacity = oldTable.length; 4 if (oldCapacity == MAXIMUM_CAPACITY) { 5 threshold = Integer.MAX_VALUE; 6 return; 7 } 8 9 Entry[] newTable = new Entry[newCapacity]; 10 transfer(newTable);//用来将原先table的元素全部移到newTable里面 11 table = newTable; //再将newTable赋值给table 12 threshold = (int)(newCapacity * loadFactor);//重新计算临界值 13 } 新建了一个HashMap的底层数组,上面代码中第10行为调用transfer方法,将HashMap的全部元素添加到新的HashMap中,并重新计算元素在新的数组中的索引位置 当HashMap中的元素越来越多的时候,hash冲突的几率也就越来越高,因为数组的长度是固定的。所以为了提高查询的效率,就要对HashMap的数组进行扩容,数组扩容这个操作也会出现在ArrayList中,这是一个常用的操作,而在HashMap数组扩容之后,最消耗性能的点就出现了:原数组中的数据必须重新计算其在新数组中的位置,并放进去,这就是resize。 那么HashMap什么时候进行扩容呢?当HashMap中的元素个数超过数组大小*loadFactor时,就会进行数组扩容,loadFactor的默认值为0.75,这是一个折中的取值。也就是说,默认情况下,数组大小为16,那么当HashMap中元素个数超过16*0.75=12的时候,就把数组的大小扩展为 2*16=32,即扩大一倍,然后重新计算每个元素在数组中的位置,扩容是需要进行数组复制的,复制数组是非常消耗性能的操作,所以如果我们已经预知HashMap中元素的个数,那么预设元素的个数能够有效的提高HashMap的性能。 5、数据读取 1.public V get(Object key) { 2. if (key == null) 3. return getForNullKey(); 4. int hash = hash(key.hashCode()); 5. for (Entry<K,V> e = table[indexFor(hash, table.length)]; 6. e != null; 7. e = e.next) { 8. Object k; 9. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) 10. return e.value; 11. } 12. return null; 13.} 有了上面存储时的hash算法作为基础,理解起来这段代码就很容易了。从上面的源代码中可以看出:从HashMap中get元素时,首先计算key的hashCode,找到数组中对应位置的某一元素,然后通过key的equals方法在对应位置的链表中找到需要的元素。 6、HashMap的性能参数: HashMap 包含如下几个构造器: HashMap():构建一个初始容量为 16,负载因子为 0.75 的 HashMap。 HashMap(int initialCapacity):构建一个初始容量为 initialCapacity,负载因子为 0.75 的 HashMap。 HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的负载因子创建一个 HashMap。 HashMap的基础构造器HashMap(int initialCapacity, float loadFactor)带有两个参数,它们是初始容量initialCapacity和加载因子loadFactor。 initialCapacity:HashMap的最大容量,即为底层数组的长度。 loadFactor:负载因子loadFactor定义为:散列表的实际元素数目(n)/ 散列表的容量(m)。 负载因子衡量的是一个散列表的空间的使用程度,负载因子越大表示散列表的装填程度越高,反之愈小。对于使用链表法的散列表来说,查找一个元素的平均时间是O(1+a),因此如果负载因子越大,对空间的利用更充分,然而后果是查找效率的降低;如果负载因子太小,那么散列表的数据将过于稀疏,对空间造成严重浪费。 HashMap的实现中,通过threshold字段来判断HashMap的最大容量: threshold = (int)(capacity * loadFactor); 结合负载因子的定义公式可知,threshold就是在此loadFactor和capacity对应下允许的最大元素数目,超过这个数目就重新resize,以降低实际的负载因子。默认的的负载因子0.75是对空间和时间效率的一个平衡选择。当容量超出此最大容量时, resize后的HashMap容量是容量的两倍:
所谓事务是用户定义的一个数据库操作序列,这些操作要么全做要么全不做,是一个不可分割的工作单位。例如,在关系数据库中,一个事务可以是一条SQL语句、一组SQL语句或整个程序。 简单举个例子就是你要同时修改数据库中两个不同表的时候,如果它们不是一个事务的话,当第一个表修改完,可是第二表改修出现了异常而没能修改的情况下,就只有第二个表回到未修改之前的状态,而第一个表已经被修改完毕。 而当你把它们设定为一个事务的时候,当第一个表修改完,可是第二表改修出现了异常而没能修改的情况下,第一个表和第二个表都要回到未修改的状态!这就是所谓的事务回滚。
基于Spring+SpringMVC+MyBatis实现高并发秒杀API 项目地址:https://github.com/DaleyChao/SecondKill 项目下载链接:https://github.com/DaleyChao/SecondKill/archive/master.zip 一、项目概述 一、为什么使用SSM框架 1.互联网公司常用框架 2.框架易于使用和轻量级 3.低代码倾入性 4.成熟的社区和用户群 二、相关技术 MySQL:1.表设计2.SQL技巧3.事务和行级锁 MyBatis:1.DAO层的设计和开发2.MyBatis合理使用3.MyBatis和Spring的整合和使用 Spring:1.Spring IOC整合Service2.声明式事务 SpringMVC:1.Restful接口设计和使用技巧2.框架运作流程3.Controller开发技巧 前端:1.交互设计2.Bootstrap3.JQuery 高并发:1.高并发点和高并发分析2.优化思路并实现 三、项目效果 二、建立项目 一、使用Maven建立项目 1.使用maven建立项目:mvn archetype:generate -DgroupId=org.seckill -DartifactId=seckill -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false -DarchetypeCatalog=internal 也可以使用mvn archetype:generate命令根据提示建立项目。 2.使用Eclipse导入项目 3.修改web.xml文件schema,使用servlet4.0版本,默认2.3版本EL表达式不工作。 <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_3_1.xsd" version="3.1" metadata-complete="true"> <!--修改servlet版本为3.1 --> </web-app> 4.补全目录 5.补全pom.xml,导入相关依赖 二、相关依赖 部分说明: 1.Java日志:slf4j,log4j,logback,common-logging等等(commons-logging 和 slf4j 都是日志规范/接口,区别是slf4j在加载时寻找接口的实现,而 commons-logging 在运行时寻找接口的实现) slf4j是规范/接口,log4j,logback,common-logging是日志实现 这里使用slf4j+logback 2.c3p0数据库连接池 pom.xml <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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.seckill</groupId> <artifactId>seckill</artifactId> <packaging>war</packaging> <version>1.0.0SNAPSHOT</version> <name>seckill Maven Webapp</name> <url>http://maven.apache.org</url> <dependencies> <!-- https://mvnrepository.com/artifact/junit/junit --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <!-- 日志 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.12</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.1.1</version> </dependency> <!-- 实现slf4j接口并整合 --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.1.1</version> </dependency> <!-- 数据库相关的依赖 --> <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.40</version> <scope>runtime</scope> </dependency> <!-- 数据库链接池 --> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <!-- Dao层 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.1</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.0</version> </dependency> <!-- Servlet web 相关依赖 --> <!-- https://mvnrepository.com/artifact/taglibs/standard --> <dependency> <groupId>taglibs</groupId> <artifactId>standard</artifactId> <version>1.1.2</version> </dependency> <!-- https://mvnrepository.com/artifact/jstl/jstl --> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-core</artifactId> <version>2.8.5</version> </dependency> <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.8.5</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.0-b01</version> <scope>provided</scope> </dependency> <!-- Spring --> <!-- https://mvnrepository.com/artifact/org.springframework/spring-core --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.3.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.3.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.3.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-web --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.3.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-test --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.3.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/redis.clients/jedis --> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.8.1</version> </dependency> <!-- protostuff序列化依赖 --> <!-- https://mvnrepository.com/artifact/io.protostuff/protostuff-core --> <dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-core</artifactId> <version>1.5.2</version> </dependency> <!-- https://mvnrepository.com/artifact/io.protostuff/protostuff-runtime --> <dependency> <groupId>io.protostuff</groupId> <artifactId>protostuff-runtime</artifactId> <version>1.5.2</version> </dependency> <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections --> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.2.2</version> </dependency> </dependencies> <build> <finalName>seckill</finalName> </build> </project> MySQL等关系型数据库提供的事务机制是目前可靠的数据落地方案 三、秒杀功能 1.秒杀接口暴露 2.执行秒杀 3.相关的查询 四、项目阶段 一、DAO层设计和编码 1.数据库表设计 2.DAO的接口 3.MyBatis实现DAO 二、Service设计编码 1.Service层设计以及编码实现 2.通过Spring管理Service,通过声明式事务标注事务操作(通过Spring声明式事务简化事务控制) 三、web设计 1.RESTful接口设计 2.前端交互 四、优化项目 三、详细设计 一、数据库设计 --数据库初始化脚本 CREATE DATABASE seckill; --使用数据库 use seckill; --创建秒杀库存表 CREATE TABLE seckill1( `seckill_id` bigint NOT NULL AUTO_INCREMENT COMMENT '商品库存id', `name` varchar(120) NOT NULL COMMENT '商品名称', `number` int NOT NULL COMMENT '库存数量', `start_time` timestamp NOT NULL COMMENT '秒杀开启时间', `end_time` timestamp NOT NULL COMMENT '秒杀结束时间', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (seckill_id), KEY idx_start_time(start_time), KEY idx_end_time(end_time), KEY idx_create_time(create_time) )ENGINE=InnoDB AUTO_INCREMENT=1000 DEFAULT CHARSET=utf8 COMMENT="秒杀库存表"; --初始化数据 insert into seckill(name,number,start_time,end_time) values ('1000元秒杀iphone6s',100,'2016-12-19 00:00:00','2016-12-20 00:00:00'), ('100元秒杀iphone6',200,'2016-12-19 00:00:00','2016-12-20 00:00:00'), ('200元秒杀iphone5',300,'2016-12-19 00:00:00','2016-12-20 00:00:00'), ('10元秒杀iphone4',400,'2016-12-19 00:00:00','2016-12-20 00:00:00'); --秒杀成功明细表 --用户登录认证相关的信息 CREATE TABLE success_kill( `seckill_id` bigint NOT NULL COMMENT '秒杀商品id', `user_phone` bigint NOT NULL COMMENT '用户手机号', `state` tinyint NOT NULL DEFAULT -1 COMMENT '创建标识:-1 无效 0 成功 1:已付款 2:已发货', `create_time` timestamp NOT NULL COMMENT '创建时间', PRIMARY KEY (seckill_id,user_phone), KEY idx_create_time(create_time) )ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT="秒杀成功明细表"; --连接数据库控制台 mysql -uroot -p 未完……
the method mergeFrom(byte[], T, Schema<T>) in the type is not applicable for the。。。。。 如果出现以上报错,说参数类型不匹配,RuntimeSchema和Schema类型无法转换,有很大的概率是倒包倒错了。 要注意,需要导入的是: <dependency><groupId>io.protostuff</groupId><artifactId>protostuff-core</artifactId><version>1.5.2</version></dependency><!-- https://mvnrepository.com/artifact/io.protostuff/protostuff-runtime --><dependency><groupId>io.protostuff</groupId><artifactId>protostuff-runtime</artifactId><version>1.5.2</version></dependency> 而不是: <!-- https://mvnrepository.com/artifact/com.dyuproject.protostuff/protostuff-core --> <dependency> <groupId>com.dyuproject.protostuff</groupId> <artifactId>protostuff-core</artifactId> <version>1.0.12</version> </dependency> 注意groupId是io.protostuff,不是com.dyuproject.protostuff。
报错信息如下: [10036] 30 Dec 10:23:49.616 # Creating Server TCP listening socket 127.0.0.1:637 9: bind: No error [8660] 30 Dec 10:23:57.132 # Creating Server TCP listening socket *:6379: listen : Unknown error 解决方案如下 按顺序输入如下命令就可以连接成功 1. redis-cli.exe 2. shutdown 3. exit 4. redis-server.exe redis.windows.conf D:\Program Files\Redis>redis-server.exe redis.windows.conf _._ _.-``__ ''-._ _.-`` `. `_. ''-._ Redis 3.2.100 (00000000/0) 64 bit .-`` .-```. ```\/ _.,_ ''-._ ( ' , .-` | `, ) Running in standalone mode |`-._`-...-` __...-.``-._|'` _.-'| Port: 6379 | `-._ `._ / _.-' | PID: 9764 `-._ `-._ `-./ _.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | http://redis.io `-._ `-._`-.__.-'_.-' _.-' |`-._`-._ `-.__.-' _.-'_.-'| | `-._`-._ _.-'_.-' | `-._ `-._`-.__.-'_.-' _.-' `-._ `-.__.-' _.-' `-._ _.-' `-.__.-'
HashMap和Hashtable的区别 HashMap和Hashtable都实现了Map接口,但决定用哪一个之前先要弄清楚它们之间的分别。主要的区别有:线程安全性,同步(synchronization),以及速度。 HashMap几乎可以等价于Hashtable,除了HashMap是非synchronized的,并可以接受null(HashMap可以接受为null的键值(key)和值(value),而Hashtable则不行)。 HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程可以共享一个Hashtable;而如果没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。 另一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。所以当有其它线程改变了HashMap的结构(增加或者移除元素),将会抛出ConcurrentModificationException,但迭代器本身的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并不是一个一定发生的行为,要看JVM。这条同样也是Enumeration和Iterator的区别。 由于Hashtable是线程安全的也是synchronized,所以在单线程环境下它比HashMap要慢。如果你不需要同步,只需要单一线程,那么使用HashMap性能要好过Hashtable。 HashMap不能保证随着时间的推移Map中的元素次序是不变的。 要注意的一些重要术语: 1) sychronized意味着在一次仅有一个线程能够更改Hashtable。就是说任何线程要更新Hashtable时要首先获得同步锁,其它线程要等到同步锁被释放之后才能再次获得同步锁更新Hashtable。 2) Fail-safe和iterator迭代器相关。如果某个集合对象创建了Iterator或者ListIterator,然后其它的线程试图“结构上”更改集合对象,将会抛出ConcurrentModificationException异常。但其它线程可以通过set()方法更改集合对象是允许的,因为这并没有从“结构上”更改集合。但是假如已经从结构上进行了更改,再调用set()方法,将会抛出IllegalArgumentException异常。 3) 结构上的更改指的是删除或者插入一个元素,这样会影响到map的结构。 我们能否让HashMap同步? HashMap可以通过下面的语句进行同步: Map m = Collections.synchronizeMap(hashMap);
1、以下程序执行的结果是: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class X{ Y y=new Y(); public X(){ System.out.print("X"); } } class Y{ public Y(){ System.out.print("Y"); } } public class Z extends X{ Y y=new Y(); public Z(){ System.out.print("Z"); } public static void main(String[] args) { new Z(); } } 正确答案: C 你的答案: 空 (错误) ZYXX ZYXY YXYZ XYZX 初始化过程: 1. 初始化父类中的静态成员变量和静态代码块 ; 2. 初始化子类中的静态成员变量和静态代码块 ; 3.初始化父类的普通成员变量和代码块,再执行父类的构造方法; 4.初始化子类的普通成员变量和代码块,再执行子类的构造方法; (1)初始化父类的普通成员变量和代码块,执行 Y y=new Y(); 输出Y (2)再执行父类的构造方法;输出X (3) 初始化子类的普通成员变量和代码块,执行 Y y=new Y(); 输出Y (4)再执行子类的构造方法;输出Z 所以输出YXYZ
在使用Maven构建项目时,生成的maven项目jdk默认使用的是jdk1.5。 在手动修改了jdk之后,update project之后jdk又会变为1.5. 或者用eclipse的Maven插件生成的也是1.5 对于这种情况有两种办法,一是修改settings.xml,二是修改pom文件 1、配置settings.xml 打开 settings.xml 文件并编辑它(一般放在你的仓库目录目录) : (不知道的话参见我这篇文章:Maven简介·安装·配置) <profile> <id>jdk-1.8</id> <activation> <activeByDefault>true</activeByDefault> <jdk>1.8</jdk> </activation> <properties> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion> </properties> </profile> 找到 <profiles> 节点,并添加如上配置(本机 jdk 1.8.0——25 版本,配置时修改成你本机的 jdk 版本),保存后生效。 修改Eclipse中的设置,使配置生效。 2、配置pom.xml文件 在 <build> 节点添加如下配置(本机 jdk 1.7.0_79 版本,配置时修改成你本机的 jdk 版本): <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> </plugins></build> 配置完成后,需要执行一次更新项目配置的动作。选中项目 --> 右键 --> Maven --> Update Project 方法一是全局生效,方法二只对本项目生效。
最近在学习Spring+SpringMVC+MyBatis,一个人的挖掘过程确实有点艰难,尤其是有一些神奇的报错让你会很蛋疼。特别是接触一些框架还是最新版本的时候,会因为版本问题出现很多错误,欢迎大家一起学习交流 这篇就说一下困扰我昨晚2小时的一个报错,nitializationError(org.junit.runner.manipulation.Filter)或者No tests found matching异常,查阅了很多资料,总结一下这些情况和解决办法。 1.最容易发现的错误,就如报错所说,没有找到test方法就是因为忘记在方法前加 @Test 注解了。 应该是这样: public class TestCase { @Test public void checkSomething() { //... } } 2.使用Maven构建项目时候,pom文件中的JUnit版本和classpath中的版本不一致,删掉一个就好(这种应该没什么人吧。。。)。 3.你可以尝试重启Eclipse或者重建或者刷新项目,重新清理,关闭重新打开项目……有时候是Eclipse的问题。 4.如果是测试方法命名不规范的问题,你可以尝试把方法统一改为 testXXX(),这是JUnit3风格。 5.测试类所在文件夹必须为源文件夹source files,如果不是,选择 'Build path' -> 'Use as a source folder'。 6.看你的测试类是否继承TestCase,如果是,删除继承,并不需要继承,例如: public class MyTestCase extends TestCase{ @Test public void checkSomething() { //... } } //Result> AssertionFailedError: No test Found in MyTestCase 应该是下面的TestCase public class MyTestCase { @Test public void checkSomething() { //... } } //Works fine 7.有些小伙伴右键选择特定的测试方法会报错,但是运行整个测试类却不会,不信可以试试 8.如果你的Eclipse版本太旧,也会导致,更新版本。 9.最后一个就是我的这个问题:版本不兼容,需要更换JUnit或者spring版本 报错信息具体如下,会有两种 java.lang.Exception: No tests found matching [{ExactMatcher:fDisplayName=testQueryById], {ExactMatcher:fDisplayName=testQueryById(org.seckill.dao.SeckillDaoTest)], {LeadingIdentifierMatcher:fClassName=org.seckill.dao.SeckillDaoTest,fLeadingIdentifier=testQueryById]] from org.junit.internal.requests.ClassRequest@1698c449 at org.junit.internal.requests.FilterRequest.getRunner(FilterRequest.java:35) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.createFilteredTest(JUnit4TestLoader.java:77) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.createTest(JUnit4TestLoader.java:68) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.loadTests(JUnit4TestLoader.java:43) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:444) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192) 或者: java.lang.ExceptionInInitializerError at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:408) at org.junit.internal.builders.AnnotatedBuilder.buildRunner(AnnotatedBuilder.java:29) at org.junit.internal.builders.AnnotatedBuilder.runnerForClass(AnnotatedBuilder.java:21) at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59) at org.junit.internal.builders.AllDefaultPossibilitiesBuilder.runnerForClass(AllDefaultPossibilitiesBuilder.java:26) at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:59) at org.junit.internal.requests.ClassRequest.getRunner(ClassRequest.java:26) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.createUnfilteredTest(JUnit4TestLoader.java:84) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.createTest(JUnit4TestLoader.java:70) at org.eclipse.jdt.internal.junit4.runner.JUnit4TestLoader.loadTests(JUnit4TestLoader.java:43) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:444) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382) at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192) Caused by: java.lang.IllegalStateException: SpringJUnit4ClassRunner requires JUnit 4.12 or higher. at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.<clinit>(SpringJUnit4ClassRunner.java:102) ... 17 more No tests found matching和ExceptionInInitializerError这两个错误其实都是一个,都是初始化错误,测试用例没有成功。 package org.seckill.dao; import javax.annotation.Resource; import org.junit.Test; import org.junit.runner.RunWith; import org.seckill.entity.Seckill; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * project:seckill * @author Daley 下午10:20:28 2016年12月20日 2016 * 配置Spring和JUnit整合 ,JUnit启动时加载SpringIOC容器 */ @RunWith(SpringJUnit4ClassRunner.class) //告诉JUnitSpring配置文件 @ContextConfiguration({"classpath:spring/spring-dao.xml"}) public class SeckillDaoTest { //注入Dao实现类依赖 @Resource private SeckillDao seckillDao; @Test public void testQueryById() { long id=1000; Seckill seckill=seckillDao.queryById(id); System.out.println(seckill.getName()); System.out.println(seckill); } } 由于试过了很多方法,这时我有了一个最不愿意的怀疑,难道是版本不兼容问题? 于是我把 @RunWith注解删掉,发现这时报的是空指针错误,说明没有加载spring容器,我使用的SpringFramework版本是 4.3.4.RELEASE ,这个是最终版本应该是比较稳定和兼容性好呀,那么问题可能就是JUnit不兼容了,我使用的是JUnit4.10 当我换成4.11时候,还是报错,但是换到最新的4.12版本的时候,这个报错消失了!出现了这个可爱的颜色。 嗯……绿色一定是我的最爱 顺便贴出配置信息: <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.3.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-beans --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.3.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-tx --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.3.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-web --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>4.3.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-test --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.3.4.RELEASE</version> </dependency>
一、TIMESTAMP的变体 TIMESTAMP时间戳在创建的时候可以有多重不同的特性,如: 1.在创建新记录和修改现有记录的时候都对这个数据列刷新: TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 2.在创建新记录的时候把这个字段设置为当前时间,但以后修改时,不再刷新它: TIMESTAMP DEFAULT CURRENT_TIMESTAMP 3.在创建新记录的时候把这个字段设置为0,以后修改时刷新它: TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 4.在创建新记录的时候把这个字段设置为给定值,以后修改时刷新它: TIMESTAMP DEFAULT ‘yyyy-mm-dd hh:mm:ss' ON UPDATE CURRENT_TIMESTAMP MySQL目前不支持列的Default 为函数的形式,如达到你某列的默认值为当前更新日期与时间的功能,你可以使用TIMESTAMP列类型,下面就详细说明TIMESTAMP列类型 二、TIMESTAMP列类型 TIMESTAMP值可以从1970的某时的开始一直到2037年,精度为一秒,其值作为数字显示。 TIMESTAMP值显示尺寸的格式如下表所示: : +---------------+----------------+ | 列类型 | 显示格式 | | TIMESTAMP(14) | YYYYMMDDHHMMSS | | TIMESTAMP(12) | YYMMDDHHMMSS | | TIMESTAMP(10) | YYMMDDHHMM | | TIMESTAMP(8) | YYYYMMDD | | TIMESTAMP(6) | YYMMDD | | TIMESTAMP(4) | YYMM | | TIMESTAMP(2) | YY | +---------------+----------------+ “完整”TIMESTAMP格式是14位,但TIMESTAMP列也可以用更短的显示尺寸,创造最常见的显示尺寸是6、8、12、和14。 你可以在创建表时指定一个任意的显示尺寸,但是定义列长为0或比14大均会被强制定义为列长14。 列长在从1~13范围的奇数值尺寸均被强制为下一个更大的偶数。 列如: 定义字段长度 强制字段长度 TIMESTAMP(0) -> TIMESTAMP(14) TIMESTAMP(15)-> TIMESTAMP(14) TIMESTAMP(1) -> TIMESTAMP(2) TIMESTAMP(5) -> TIMESTAMP(6) 所有的TIMESTAMP列都有同样的存储大小,使用被指定的时期时间值的完整精度(14位)存储合法的值不考虑显示尺寸。不合法的日期,将会被强制为0存储 这有几个含意: 1.虽然你建表时定义了列TIMESTAMP(8),但在你进行数据插入与更新时TIMESTAMP列实际上保存了14位的数据(包括年月日时分秒),只不过在你进行查询时MySQL返回给你的是8位的年月日数据。如果你使用ALTER TABLE拓宽一个狭窄的TIMESTAMP列,以前被“隐蔽”的信息将被显示。 2.同样,缩小一个TIMESTAMP列不会导致信息失去,除了感觉上值在显示时,较少的信息被显示出。 3.尽管TIMESTAMP值被存储为完整精度,直接操作存储值的唯一函数是UNIX_TIMESTAMP();由于MySQL返回TIMESTAMP列的列值是进过格式化后的检索的值,这意味着你可能不能使用某些函数来操作TIMESTAMP列(例如HOUR()或SECOND()),除非TIMESTAMP值的相关部分被包含在格式化的值中。 例如,一个TIMESTAMP列只有被定义为TIMESTAMP(10)以上时,TIMESTAMP列的HH部分才会被显示,因此在更短的TIMESTAMP值上使用HOUR()会产生一个不可预知的结果。 4.不合法TIMESTAMP值被变换到适当类型的“零”值(00000000000000)。(DATETIME,DATE亦然) 例如你可以使用下列语句来验证: CREATE TABLE test ('id' INT (3) UNSIGNED AUTO_INCREMENT, 'date1' TIMESTAMP (8) PRIMARY KEY('id')); INSERT INTO test SET id = 1; SELECT * FROM test; +----+----------------+ | id | date1 | +----+----------------+ | 1 | 20021114 | +----+----------------+ ALTER TABLE test CHANGE 'date1' 'date1' TIMESTAMP(14); SELECT * FROM test; +----+----------------+ | id | date1 | +----+----------------+ | 1 | 20021114093723 | +----+----------------+ 你可以使用TIMESTAMP列类型自动地用当前的日期和时间标记INSERT或UPDATE的操作。 如果你有多个TIMESTAMP列,只有第一个自动更新。自动更新第一个TIMESTAMP列在下列任何条件下发生: 1.列值没有明确地在一个INSERT或LOAD DATA INFILE语句中指定。 2.列值没有明确地在一个UPDATE语句中指定且另外一些的列改变值。(注意一个UPDATE设置一个列为它已经有的值,这将不引起TIMESTAMP列被更新,因为如果你设置一个列为它当前的值,MySQL为了效率而忽略更改。) 3.你明确地设定TIMESTAMP列为NULL. 4.除第一个以外的TIMESTAMP列也可以设置到当前的日期和时间,只要将列设为NULL,或NOW()。 CREATE TABLE test ( 'id' INT (3) UNSIGNED AUTO_INCREMENT, 'date1' TIMESTAMP (14), 'date2' TIMESTAMP (14), PRIMARY KEY('id') ); INSERT INTO test (id, date1, date2) VALUES (1, NULL, NULL); INSERT INTO test SET id= 2; +----+----------------+----------------+ | id | date1 | date2 | +----+----------------+----------------+ | 1 | 20021114093723 | 20021114093723 | | 2 | 20021114093724 | 00000000000000 | +----+----------------+----------------+ 第一条指令因设date1、date2为NULL,所以date1、date2值均为当前时间第二条指令因没有设date1、date2列值,第一个TIMESTAMP列date1为更新为当前时间,而二个TIMESTAMP列date2因日期不合法而变为“00000000000000” UPDATE test SET id= 3 WHERE id=1; +----+----------------+----------------+ | id | date1 | date2 | +----+----------------+----------------+ | 3 | 20021114094009 | 20021114093723 | | 2 | 20021114093724 | 00000000000000 | +----+----------------+----------------+ 这条指令没有明确地设定date2的列值,所以第一个TIMESTAMP列date1将被更新为当前时间 UPDATE test SET id= 1,date1=date1,date2=NOW() WHERE id=3; +----+----------------+----------------+ | id | date1 | date2 | +----+----------------+----------------+ | 1 | 20021114094009 | 20021114094320 | | 2 | 20021114093724 | 00000000000000 | +----+----------------+----------------+ 这条指令因设定date1=date1,所以在更新数据时date1列值并不会发生改变而因设定date2=NOW(),所以在更新数据时date2列值会被更新为当前时间此指令等效为: UPDATE test SET id= 1,date1=date1,date2=NULL WHERE id=3; 因MySQL返回的 TIMESTAMP 列为数字显示形式,你可以用DATE_FROMAT()函数来格式化 TIMESTAMP 列,如下所示: SELECT id,DATE_FORMAT(date1,'%Y-%m-%d %H:%i:%s') As date1, DATE_FORMAT(date2,'%Y-%m-%d %H:%i:%s') As date2 FROM test; +----+---------------------+---------------------+ | id | date1 | date2 | +----+---------------------+---------------------+ | 1 | 2002-11-14 09:40:09 | 2002-11-14 09:43:20 | | 2 | 2002-11-14 09:37:24 | 0000-00-00 00:00:00 | +----+---------------------+---------------------+ SELECT id,DATE_FORMAT(date1,'%Y-%m-%d') As date1, DATE_FORMAT(date2,'%Y-%m-%d') As date2 FROM test; +----+-------------+-------------+ | id | date1 | date2 | +----+-------------+-------------+ | 1 | 2002-11-14 | 2002-11-14 | | 2 | 2002-11-14 | 0000-00-00 | +----+-------------+-------------+ 在某种程度上,你可以把一种日期类型的值赋给一个不同的日期类型的对象。 然而,而尤其注意的是:值有可能发生一些改变或信息的损失: 1.如果你将一个DATE值赋给一个DATETIME或TIMESTAMP对象,结果值的时间部分被设置为'00:00:00',因为DATE值中不包含有时间信息。 2.如果你将一个DATETIME或TIMESTAMP值赋给一个DATE对象,结果值的时间部分被删除,因为DATE类型不存储时间信息。 3.尽管DATETIME, DATE和TIMESTAMP值全都可以用同样的格式集来指定,但所有类型不都有同样的值范围。 例如,TIMESTAMP值不能比1970早,也不能比2037晚,这意味着,一个日期例如'1968-01-01',当作为一个DATETIME或DATE值时它是合法的,但它不是一个正确TIMESTAMP值!并且如果将这样的一个对象赋值给TIMESTAMP列,它将被变换为0。 三、当指定日期值时,当心某些缺陷: 1.允许作为字符串指定值的宽松格式能被欺骗。例如,因为“:”分隔符的使用,值'10:11:12'可能看起来像时间值,但是如果在一个日期中使用,上下文将作为年份被解释成'2010-11-12'。值'10:45:15'将被变换到'0000-00-00',因为'45'不是一个合法的月份。 2.以2位数字指定的年值是模糊的,因为世纪是未知的。MySQL使用下列规则解释2位年值: 在00-69范围的年值被变换到2000-2069。 在范围70-99的年值被变换到1970-1999。 转载至http://www.jb51.net/article/51794.htm
Bean生命周期:定义 --- 初始化 --- 使用 --- 销毁 一.初始化: 方法1.实现org.springframework.beans.foctory.InitializingBean接口,覆盖afterPropertiesSet方法。系统会自动查找afterPropertiesSet方 法,执行其中的初始化操作 方法2.配置init-method 例如设置bean中init-method="init"那么在初始化过程中就会调用相应class指定类的init()方法进行初始化工作 二 销毁(与初始化类似) 方法1.实现org.springframework.beans.foctory.DisposableBean接口,覆盖destory方法。 方法2.配置destory-method 三 配置全局初始化、销毁方法(属于默认配置,参考截图) 注意: 1.当三种方式同时使用时,全局(默认的)初始化销毁方法会被覆盖。 2.另外实现接口(InitializingBean,DisposableBean)的初始化/销毁方式会先于配置文件中的初始化/销毁方式执行。 3.xml中配置了,但是类文件即使没有以上接口和全局初始化方法也是可以编译执行的,bean中配置了却没有实现bean中的init和destory等会报错。 例子: public class BeanLifeCycle implements InitializingBean,DisposableBean{ public void start(){ System.out.println("Bean start"); } public void stop(){ System.out.println("Bean stop"); } public void destroy() throws Exception { System.out.println("destory"); } public void afterPropertiesSet() throws Exception { System.out.println("after properties"); } public void defaultInit(){ System.out.println("defaultInit"); } public void defaultDestory(){ System.out.println("defaultDestory"); } }XML文件: <?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:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" default-init-method="defaultInit" default-destroy-method="defaultDestory"> <bean id="beanLifeCycle" class="com.daley.springprac.prac1.lifecyle.BeanLifeCycle" init-method="start" destroy-method="stop"/> </beans> 优先级:接口>局部(有局部就不执行全局默认方法)demo地址(文件夹prac1):https://github.com/DaleyChao/SpringPractice/tree/a1ee3afc0accc8ae796e17dea5edf8fdbeb61fa7
1.bean的常用配置项:(理论上只有class是必须的) id:唯一标识 class:具体是哪一个类 scope:范围 constructor arguments:构造器的参数 properties:属性 Autowiring mode:自动装配模式 lazy-initialization mode:懒加载模式 initialization/destruction method:初始化/销毁的方法 2.bean的作用域 singletion 单例 bean容器只有唯一的对象(默认模式) prototype 每次请求会创建新的实例,destory方式不生效 request 对于request创建新的实例,只在当前request内有效 session 对于session创建新的实例,只在当前session内有效 global session 基于portlet(例如单点登录的范围)的web中有效,如果在web中同session的作用域 demo地址:https://github.com/DaleyChao/SpringPractice
在建立Spring工程的时候,需要引入Spring的开发包,否则无法建立Spring的开发和运行环境,以下简单介绍一下Spring的核心开发包的基本用途:Spring CoreSpring BeansSpring AOPSpring Context 以下开发包文件虽不是Spring核心开发包,但是提供了各种企业级服务: Spring Aspects Spring Context Support Spring Expression Spring Framework Bom Spring Instrument Spring Instrument Tomcat Spring JDBC Spring JMS Spring orm Spring oxm Spring Struts Spring test Spring tx Spring web Spring webmvc Spring webmvc portlet 创建一个基于Spring IOC的小程序的步骤如下:建立Spring工程编写Java文件编写配置文件运行示例工程 首先,我们需要在Eclipse中建立一个普通Java工程,然后引入Spring的核心jar文件到工程中,当然也可以全部导入。 在这里,我们在工程中逐个建立Java文件: •IHelloMessage:一个接口,用于定义输出问候信息 •HelloWorld:接口的实现类,向用户输出“Hello everybody”信息 •HelloChina:接口的实现类,向用户输出“大家好!”信息 •Person:一个人物类,调用IHelloMessage接口,向用户输出问候信息 •Main:程序的入口类,用于加载配置文件以及启动IOC容器,调用人物类,向用户输出问候信息 接下来,为示例代码建立配置文件helloMessage.xml文件,示例如下: •<?xml version="1.0" encoding="UTF-8"?> •<!DOCTYPE beans PUBLIC "-//SPRING/DTD BEAN/EN""http://www.springframework.org/dtd/spring-beans.dtd"> •<beans> • <bean id="helloWorld"class="com.jike.spring.chapter01.HelloWorld"></bean> • <bean id="helloChina"class="com.jike.spring.chapter01.HelloChina"></bean> • <bean id="person"class="com.jike.spring.chapter01.Person"> • <property name="helloMessage" ref="helloChina"/> • </bean> •</beans> 编译并运行示例工程,在控制台查看输出信息,验证程序运行是否正常: •1. 确认程序输出是否正常 •2.通过配置文件,来控制人的输出信息 •3.当人在国内时,是否输出了“大家好!”的信息 •4.当人在国外时,是否输出了“Hello everybody!”的信息 github示例地址:https://github.com/DaleyChao/SpringPractice(springDemo项目)
AOP 专门用于处理系统中分布于各个模块中的交叉关注点的问题,在 Java EE 应用中,常常通过 AOP 来处理一些具有横切性质的系统级服务,如事务管理、安全检查、缓存、对象池管理等,AOP 已经成为一种非常常用的解决方案: AOP 代理其实是由 AOP 框架动态生成的一个对象,该对象可作为目标对象使用,AOP 代理所包含的方法与目标对象的方法如下图所示: 代理对象的方法 = 增强处理 + 被代理对象的方法 •定义普通业务组件•定义切入点•定义增强处理 •切面- Aspect•连接点- Join Point•通知 - Advice•切入点 - Point Cut•引入 - Introduction•目标对象 - Target Object•AOP代理- AOP Proxy•织入 - Weaving•一个组件A,不关心其他常用的服务组件B,但是这个组件A使用组件B的时候,不是组件A自身去调用,而是通过配置等其他方式,比如Spring中可以通过xml配置文件。这样就使得A压根就不需要知道服务组件B是怎样的,爱存在不存在,爱怎么存在都与A无关。A只关心自己的业务逻辑,具体A使用B的时候,配置文件去做,与具体的A组件无关。 以下是官方文档所给出的AOP的关键概念的解释: AOP通俗的理解:
Spring注入是指在启动Spring容器加载bean配置的时候,完成对变量的赋值行为 常用注入方式:设值注入,构造注入 注意:参数的名称必须保持一致!!!! 一、设值注入 不需要显示地调用set方法,会根据xml的相关配置自动进行调用,利用属性或成员变量的set方法进行注入。 eg: <bean id="beanA" class="com.daley.serviceImple"> <property name="B" ref="beanB"/> </bean> <bean id="beanB" class="com.daley.service"/> 其中property里面的name是需要注入参数的成员变量的名称,ref是注入参数引入bean的名称 如上例:beanA中有一个成员变量名为B,参数类型为beanB,spring的IoC容器会自动的调用beanA中的set方法赋值。 二、构造注入 在spring的IoC容器调用接口的构造方法去创建实例的时候,构造器会自动给成员变量赋值,构造方法中的参数名和成员变量名必须保持一致 <bean id="beanA" class="com.daley.serviceImple"> <constructor-arg name="B" ref="beanB"/> </bean> <bean id="beanB" class="com.daley.service"/>
想说说IOC(Inversion of Control,控制反转)。这是spring的核心,贯穿始终。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。 这是什么意思呢,举个简单的例子,我们是如何找女朋友的?常见的情况是,我们到处去看哪里有长得漂亮身材又好的mm,然后打听她们的兴趣爱好、qq号、电话号、ip号、iq号………,想办法认识她们,投其所好送其所要,然后嘿嘿……这个过程是复杂深奥的,我们必须自己设计和面对每个环节。传统的程序开发也是如此,在一个对象中,如果要使用另外的对象,就必须得到它(自己new一个,或者从JNDI中查询一个),使用完之后还要将对象销毁(比如Connection等),对象始终会和其他的接口或类藕合起来。 那么IOC是如何做的呢?有点像通过婚介找女朋友,在我和女朋友之间引入了一个第三者:婚姻介绍所。婚介管理了很多男男女女的资料,我可以向婚介提出一个列表,告诉它我想找个什么样的女朋友,然后婚介就会按照我们的要求,提供一个mm,我们只需要去和她谈恋爱、结婚就行了。简单明了,如果婚介给我们的人选不符合要求,我们就会抛出异常。整个过程不再由我自己控制,而是有婚介这样一个类似容器的机构来控制。Spring所倡导的开发方式就是如此,所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。 IOC的另外的名字叫做依赖注入(Dependency Injection),所谓的依赖注入,就是由IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。所以,依赖注入(DI)和控制反转(IOC)是从不同的角度的描述的同一件事情,就是指通过引入IOC容器,利用依赖关系注入的方式,实现对象之间的解耦 耦合对象 解耦的过程 IOC在编程过程中不会对业务对象构成很强的侵入性,使用IOC之后,对象具有更好的可实行性,可重用性和可扩展性: •降低组件之间的耦合度 •提高开发效率和产品质量 •统一标准,提高模块的复用性 •模块具有热插拔特性 IOC通俗的理解如下: •IOC控制反转:说的是创建对象实例的控制权从代码控制剥离到IOC容器控制,实际就是你在xml文件控制,侧重于原理 •DI依赖注入:说的是创建对象实例时,为这个对象注入属性值或其它对象实例,侧重于实现
在J2EE项目的开发中,每个异常都单独处理,系统的代码耦合度高,工作量大: •使用SpringMVC提供的简单异常处理器SimpleMappingExceptionResolver •实现Spring的异常处理接口HandlerExceptionResolver自定义自己的异常处理器 •使用@ExceptionHandler注解实现异常处理 简单的异常处理,推荐使用SimpleMappingExceptionResolver即可: 实现自己的HandlerExceptionResolver, 第4个参数表示对哪种类型异常进行处理: 使用@ExceptionHandler注解实现异常处理: •增加BaseController类,并在类中使用@ExceptionHandler注解声明异常处理 •使所有需要异常处理的Controller都继承该类
SpringMVC 中的Interceptor拦截器也是相当重要和相当有用的,它的主要作用是拦截用户的请求并进行相应的处理,通常还有如下作用: •日志记录 •权限检查 •性能监控 •通用行为 SpringMVC 中的Interceptor拦截请求是通过HandlerInterceptor 来实现的: •要定义的Interceptor类要实现了Spring的HandlerInterceptor 接口 •要定义的Interceptor类继承实现了HandlerInterceptor 接口的类(比如HandlerInterceptorAdapter 类) HandlerInterceptor 有三个方法: •preHandle(HttpServletRequest request, HttpServletResponse response, Objecthandle),在请求处理之前进行调用。 •postHandle(HttpServletRequest request, HttpServletResponse response, Object handle,ModelAndView modelAndView), 请求进行处理之后调用。 •afterCompletion(HttpServletRequestrequest, HttpServletResponse response, Object handle, Exception ex),整个请求结束 之后(渲染了对应的视图之后)调用。 在springmvc.xml配置文件中做如下配置,使拦截器生效:
1、pageEncoding="UTF-8"的作用是设置JSP编译成Servlet时使用的编码。 2、contentType="text/html;charset=UTF-8"的作用是指定对服务器响应进行重新编码的编码。 3、request.setCharacterEncoding("UTF-8")的作用是设置对客户端请求进行重新编码的编码。4、response.setCharacterEncoding("UTF-8")的作用是指定对服务器响应进行重新编码的编码。
2022年11月
2022年01月