数据专栏

智能大数据搬运工,你想要的我们都有

科技资讯:

科技学院:

科技百科:

科技书籍:

网站大全:

软件大全:

介绍 ProtoBuf 是google团队开发的用于高效存储和读取结构化数据的工具。什么是结构化数据呢,正如字面上表达的,就是带有一定结构的数据。比如电话簿上有很多记录数据,每条记录包含姓名、ID、邮件、电话等,这种结构重复出现。
同类
XML、JSON 也可以用来存储此类结构化数据,但是使用ProtoBuf表示的数据能更加高效,并且将数据压缩得更小。
原理
ProtoBuf 是通过ProtoBuf编译器将与编程语言无关的特有的 .proto 后缀的数据结构文件编译成各个编程语言(Java,C/C++,Python)专用的类文件,然后通过Google提供的各个编程语言的支持库lib即可调用API。(关于proto结构体怎么编写,可自行查阅文档)
总结
编解码性能 次数在1千以下,ProtoBuf 的编码与解码性能,都与JSON不相上下,甚至还有比JSON差的趋势。 次数在2千以上,ProtoBuf的编码解码性能,都比JSON高出很多。 次数在10万以上,ProtoBuf的编解码性能就很明显了,远远高出JSON的性能。
内存占用
ProtoBuf的内存34,而JSON到达106 ,ProtoBuf的内存占用只有JSON的1/3.
原文链接: https://my.oschina.net/xiaolei123/blog/3085607 详细内容要看原文,这边只是截一小部分
软件开发
2020-07-05 16:00:00
编写程序,读取在1到100 之间的整数,然后计算每个数出现的次数。假定输入是以0 结束的。
下面是这个程序的一个运行示例:
Write a program that reads the integers between 1and 100 and counts the occurrences of each. Assume the input ends with 0.Note that if a number occurs more than one time, the plural word “times” is used
in the output. Enter the integers between 1 and 100: 2 5 6 5 4 3 23 43 2 0 2 occurs 2 times 3 occurs 1 time 4 occurs 1 time 5 occurs 2 times 6 occurs 1 time 23 occurs 1 time 43 occurs 1 time
下面是参考答案代码: // https://cn.fankuiba.com import java.util.Scanner; public class Ans7_3_page236 { public static void main(String[] args) { int[] number = new int [101]; Scanner input = new Scanner(System.in); int num; System.out.print("Enter the integers between 1 and 100: "); do { num = input.nextInt(); number[num] = number[num] + 1; } while (num != 0); for (int i = 1; i < number.length; i++) { if (number[i] == 1) { System.out.println(i + " occurs " + number[i] + " time"); }else if (number[i] > 1) System.out.println(i + " occurs " + number[i] + " times"); } } }
适用Java语言程序设计与数据结构(基础篇)(原书第11版)Java语言程序设计(基础篇)(原书第10/11版)
发布在博客:( https://cn.fankuiba.com )
软件开发
2020-07-03 17:33:00
springboot读取classpath下的文件方法
获取文件或流 this . getClass ( ) . getResource ( "/" ) + fileName ; this . getClass ( ) . getResourceAsStream ( fileName ) ;
获取文件 File file = org . springframework . util . ResourceUtils . getFile ( "classpath:test.txt" ) ;
获取文件或流 ClassPathResource classPathResource = new ClassPathResource ( "test.txt" ) ; classPathResource . getFile ( ) ; classPathResource . getInputStream ( ) ;
下面方法可以读取jar包下文件 InputStream io = Thread . currentThread ( ) . getContextClassLoader ( ) . getResourceAsStream ( "test.txt" ) ; InputStream io = ClassLoader . getSystemResourceAsStream ( "test.txt" ) ;
获取跟目录 File path = new File ( ResourceUtils . getURL ( "classpath:" ) . getPath ( ) ) ; if ( ! path . exists ( ) ) path = new File ( "" ) ; System . out . println ( "path:" + path . getAbsolutePath ( ) ) ;
如果上传目录为/static/images/upload/,则可以如下获取: File upload = new File ( path . getAbsolutePath ( ) , "static/images/upload/" ) ; if ( ! upload . exists ( ) ) upload . mkdirs ( ) ; System . out . println ( "upload url:" + upload . getAbsolutePath ( ) ) ; * 在开发测试模式时,得到的地址为:{项目跟目录}/target/static/images/upload/ * 在打包成jar正式发布时,得到的地址为:{发布jar包目录}/static/images/upload/
软件开发
2020-07-03 10:54:00
0.基础环境
CentOS Linux release 7.8.2003 (Core)
1.下载源码安装包,下面两个包是一样的;只是压缩格式不同
wget https://www.python.org/ftp/python/3.8.3/Python-3.8.3.tgz
wget https://www.python.org/ftp/python/3.8.3/Python-3.8.3.tar.xz
2.解压
tar zxvf Python-3.8.3.tgz
3.编译
#yum install openssl-devel
#yum install libffi-devel
#./configure --prefix=/usr/local/python
#make
#make install
4.安装pip
#curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py # 下载安装脚本
#python get-pip.py
5.沙盒启动python3.8
详见:
https://my.oschina.net/guiguketang/blog/3154693
6.通过pip安装django
# pip install Django
默认最新版本,也可以指定django版本安装,
注意django与python有版本对应关系,请到官网自行确认。
# pip install Django==3.0.8
查看django版本
# django-admin --version

# python -m django --version

显示已经成功部署了django环境
***************************分割线django环境安装完毕!!!******************
下一篇:创建Django项目
https://my.oschina.net/guiguketang/blog/4334259

【官方网址】
https://www.python.org/downloads/
https://docs.djangoproject.com/en/3.0/intro/install/
软件开发
2020-07-03 09:47:00
class、object、case class、case object区别 class 类似 Java 中的 class ; object Scala 不能定义静态成员,用定义单例对象代之; case class 被称为样例类,是一种特殊的类,常被用于模式匹配。 一、 class 和 object 关系: 1 . 单例对象不能带参数,类可以 2 . 对象可以和类名一样时, object 被称为伴生对象, class 被称为伴生类; 3 . 类和伴生对象可以相互访问其私有属性,但是它们必须在一个源文件当中; 4 . 类只会被编译,不会被执行。要执行,必须在 Object 中。 二、 case class 与 class 区别: 1 . 初始化的时候可以不用 new ,也可以加上,但是普通类必须加 new ; 2 . 默认实现了 equals 、 hashCode 方法; 3 . 默认是可以序列化的,实现了 Serializable ; 4 . 自动从 scala.Product 中继承一些函数 ; 5 . case class 构造函数参数是 public 的,我们可以直接访问; 6 . case class 默认情况下不能修改属性值; 7 . case class 最重要的功能,支持模式匹配,这也是定义 case class 的重要原因。 三、 case class 和 case object 区别: 类中有参和无参,当类有参数的时候,用 case class ,当类没有参数的时候那么用 case object 。 四、当一个类被声名为 case class 的时候, scala 会帮助我们做下面几件事情: 1 . 构造器中的参数如果不被声明为 var 的话,它默认的话是 val 类型的,但一般不推荐将构造器中的参数声明为 var 2 . 自动创建伴生对象,同时在里面给我们实现子 apply 方法,使得我们在使用的时候可以不直接显示地 new 对象 3 . 伴生对象中同样会帮我们实现 unapply 方法,从而可以将 case class 应用于模式匹配,关于 unapply 方法我们在后面的 “ 提取器 ” 那一节会重点讲解 4 . 实现自己的 toString 、 hashCode 、 copy 、 equals 方法 除此之此, case class 与其它普通的 scala 类没有区别
233
软件开发
2020-07-03 07:51:00
引入依赖 ... com.baomidou mybatis-plus-boot-starter 3.1.0 org.springframework.boot spring-boot-starter-thymeleaf org.apache.shiro shiro-spring 1.4.1 com.github.theborakompanioni thymeleaf-extras-shiro 2.0.0 ... 配置Realm:认证/授权 此处的各种service对应的实体类 是经典的RBAC权限三表 user表/role表/permission表(另外还有user_role表和role_permission表) public class UserRealm extends AuthorizingRealm { @Autowired private UserService userService; @Autowired private RoleService roleService; @Autowired private PermissionService permissionService; @Override public String getName() { return this.getClass().getSimpleName(); } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); ActiveUser activeUser = (ActiveUser) principals.getPrimaryPrincipal(); Integer type = activeUser.getUser().getType(); List permissions = activeUser.getPermissions(); if (CarrentConstast.USER_TYPE_SUPER.equals(type)) { info.addStringPermission("*:*"); } else { if(null!= permissions && permissions.size()>0) { info.addStringPermissions(permissions); } } return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { UsernamePasswordToken token1 = (UsernamePasswordToken)token; String loginname = token1.getPrincipal().toString(); QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("loginname",loginname); User one = userService.getOne(queryWrapper); List list = new ArrayList<>(); if(null != one) { ActiveUser activeUser = new ActiveUser(); QueryWrapper qw = new QueryWrapper<>(); qw.eq("type", CarrentConstast.TYPE_PERMISSION); qw.eq("available",CarrentConstast.AVAILABLE_TRUE); Integer uid = one.getId(); Set set = new HashSet<>(); List rids = roleService.getAllRoleByUserId(uid); for (Integer rid : rids) { List pids = roleService.getAllPermissionByRid(rid); set.addAll(pids); } if(null != set && set.size()>0) { qw.in("id",set); list = permissionService.list(qw); } List percode = new ArrayList<>(); list.forEach(p->{ percode.add(p.getPercode()); }); activeUser.setPermissions(percode); activeUser.setUser(one); ByteSource credentialsSalt = ByteSource.Util.bytes(one.getSalt()); return new SimpleAuthenticationInfo(activeUser,one.getPwd(),credentialsSalt,this.getName()); } return null; } } shiro配置类 @Configuration @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) @ConditionalOnClass(value = { SecurityManager.class }) @ConfigurationProperties(prefix = "shiro") @Data public class ShiroAutoConfiguration { private static final String SHIRO_DIALECT = "shiroDialect"; private static final String SHIRO_FILTER = "shiroFilter"; // 加密方式 private String hashAlgorithmName = "md5"; // 散列次数 常量 // 默认的登陆页面 private String loginUrl = "/index.html"; // 在yml配置中已经配置了 通过注解@ConfigurationProperties(prefix = "shiro") 自动注入 private String[] anonUrls; private String logOutUrl; private String[] authcUlrs; /** * 声明凭证匹配器 */ @Bean("credentialsMatcher") public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher(); credentialsMatcher.setHashAlgorithmName(hashAlgorithmName); credentialsMatcher.setHashIterations(CarrentConstast.HASHITERATIONS); return credentialsMatcher; } /** * 声明userRealm */ @Bean("userRealm") public UserRealm userRealm(CredentialsMatcher credentialsMatcher) { UserRealm userRealm = new UserRealm(); // 注入凭证匹配器 userRealm.setCredentialsMatcher(credentialsMatcher); return userRealm; } /** * 配置SecurityManager */ @Bean("securityManager") public DefaultWebSecurityManager securityManager(UserRealm userRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 注入userRealm securityManager.setRealm(userRealm); return securityManager; } /** * 配置shiro的过滤器 */ @Bean(SHIRO_FILTER) public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager securityManager) { ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean(); // 设置安全管理器 factoryBean.setSecurityManager(securityManager); // 设置未登陆的时要跳转的页面 factoryBean.setLoginUrl(loginUrl); Map filterChainDefinitionMap = new HashMap<>(); // 设置放行的路径 if (anonUrls != null && anonUrls.length > 0) { for (String anon : anonUrls) { filterChainDefinitionMap.put(anon, "anon"); } } // 设置登出的路径 if (null != logOutUrl) { filterChainDefinitionMap.put(logOutUrl, "logout"); } // 设置拦截的路径 if (authcUlrs != null && authcUlrs.length > 0) { for (String authc : authcUlrs) { filterChainDefinitionMap.put(authc, "authc"); } } Map filters=new HashMap<>(5); // filters.put("authc", new ShiroLoginFilter()); //配置过滤器 factoryBean.setFilters(filters); factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return factoryBean; } /** * 注册shiro的委托过滤器,相当于之前在web.xml里面配置的 * * @return */ @Bean public FilterRegistrationBean delegatingFilterProxy() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); DelegatingFilterProxy proxy = new DelegatingFilterProxy(); proxy.setTargetFilterLifecycle(true); proxy.setTargetBeanName(SHIRO_FILTER); filterRegistrationBean.setFilter(proxy); return filterRegistrationBean; } /* 加入注解的使用,不加入这个注解不生效--开始 */ /** * * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass(true); return advisorAutoProxyCreator; } /* 加入注解的使用,不加入这个注解不生效--结束 */ /** * 这里是为了能在html页面引用shiro标签,上面两个函数必须添加,不然会报错 * * @return */ @Bean(name = SHIRO_DIALECT) public ShiroDialect shiroDialect() { return new ShiroDialect(); } } yml配置 #配置数据源的属性 spring: datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/erp?useUnicode=true&characterEncoding=utf8&useSSL=true&serverTimezone=UTC username: root password: 123456 max-active: 20 max-wait: 5000 initial-size: 1 filters: stat,log4j,wall validationQuery: SELECT 'x' #验证连接 enable: true #监控配置 stat-view-servlet: enabled: true login-username: root login-password: 123456 allow: deny: url-pattern: /druid/* #thymeleaf的配置 thymeleaf: cache: false enabled: true # web-stat-filter: # enabled: true # exclusions: "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*" # 接受前端json jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 #配置mybatisplus mybatis-plus: mapper-locations: - classpath:mappers/*/*Mapper.xml global-config: db-config: id-type: auto banner: true configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #shiro的配置 shiro: hash-algorithm-name: md5 hash-iterations: 2 anon-urls: - /index.html* - /sys/toLogin* - /login/login* - /resources/** login-url: /index.html log-out-url: /login/logout* authc-ulrs: - /** 登录控制器 @RestController @RequestMapping("login") public class LoginController { @Autowired private LoginfoService loginfoService; @RequestMapping("/login") public ResponseData login(Model model, String loginname, String pwd) { UsernamePasswordToken token = new UsernamePasswordToken(loginname,pwd); try { Subject subject = SecurityUtils.getSubject(); subject.login(token); ActiveUser activeUser = (ActiveUser) subject.getPrincipal(); WebUtil.getSession().setAttribute("activeUser",activeUser); Loginfo loginfo = new Loginfo(); loginfo.setLoginname(activeUser.getUser().getName()+"-"+activeUser.getUser().getLoginname()); loginfo.setLogintime(LocalDateTime.now()); loginfo.setLoginip(WebUtil.getRequest().getRemoteAddr().toString()); loginfoService.save(loginfo); return ResponseData.LOGIN_SUCCESS; } catch (AuthenticationException e) { e.printStackTrace(); return ResponseData.LOGIN_ERROR; } } } 分页插件配置 @Configuration @ConditionalOnClass(value = {PaginationInterceptor.class}) public class MyBatisConfig { @Bean public PaginationInterceptor paginationInterceptor() { return new PaginationInterceptor(); } }
软件开发
2020-07-03 06:40:00
小白的进阶之路总是充满着荆棘
假设表结构: create table test
(
zp blob;
id varchar2(30)
)
python读取操作: sql = "select zp from test" cr.execute(sql) result = type(cr.fetcharr().read()) print(result)
python写入操作: sql = "insert into test(zp) values(:blobData)" cr.setinputsizes(blobData=cx_Oracle.BLOB)
软件开发
2020-07-02 16:16:00
Tableau 有两个模块:tableau desktop 用来创建分析;tableau server 用来发布分析。集成的时候需要在 tableau server 中进行二次开发,创建票证、创建视图、兑换票证等一个完整流程都需要用户写代码开发实现。
另外,由于 tableau 是一个完整的平台,无法单独拆出来 BI 模块让用户进行深度集成,所以集成时,需进行组织架构信息同步、单点登陆、权限同步等配置操作,工作量不减反而增加很多,实际用起来还是要跳转到 BI 平台上去单独使用。
除了 tableau 以外,其他的一些 BI 工具也都差不多是这样,不太好集成。
那有没有方便被 java 应用集成的 BI 产品呢,也有,可以看下这个 怎样在应用中集成自助报表功能
润乾报表的 BI 模块是专门面向集成设计,很方便嵌入到 java 应用中。
另外,涉及到一些工具选型,前期经验少可能会遇到一些问题不知道怎么去综合考虑,这里推荐一个报表工具选型的注意事项供参考:
https://wenku.baidu.com/view/bf6cd182b94ae45c3b3567ec102de2bd9705de39
软件开发
2020-07-02 15:35:00
题目描述
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
思路:
还是利用hashmap存储值和出现的次数。
代码: import java.util.HashMap; public class Solution { public int MoreThanHalfNum_Solution(int [] array) { HashMap map=new HashMap<>(); int len=array.length/2; int l=0 ; for(int i=0;ilen) return array[i]; } return 0; } }
软件开发
2020-07-02 11:48:00
题目描述
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
思路:
题目抽象:找出数组中和为s的两个乘积最小的数字。
核心:排序数组一般采用双指针法,由外向里遍历,最外层的就是乘积最小的。
1)初始化头和尾的两个指针,开始遍历
2)如果两者之和sum3)如果sum>s,就--j
4)第一次刚好相等就是乘积最小的,直接返回即可。
代码: import java.util.ArrayList; public class Solution { public ArrayList FindNumbersWithSum(int [] array,int sum) { ArrayList res=new ArrayList<>(); int i=0; int j=array.length-1; while(i
软件开发
2020-07-02 10:46:00
关于可信代码数字签名如何计算 PE 文件哈希值的细节,参阅本文档后面的 “Calculating the PE Image Hash” 。
“数字签名”已成为黑客绕过安全防御措施的常用工具,例如臭名昭著的“勒索软件Petya”,他就是选用合法的“数字签名”应用于恶意软件中,以逃避基本的签名验证工具检测。针对如此热门的入侵技术,作为一个逆向工程师或者安全程序开发者,知道如何使用“数字签名”应用于那些未签名、攻击者代码的方法就显得非常重要。

下面 亚信安全网络监测实验室 将为大家解读“数字签名”如何应用于未签名的PE文件中,以及关于代码签名机制、数字签名二进制格式的背景知识。
什么是 软 件“数字签名”
首先呢,要了解下什么是PE文件!PE文件的全称是Portable Executable,意为可移植的可执行的文件,常见的EXE、DLL、OCX、SYS、COM都是PE文件,PE文件是微软Windows操作系统上的程序文件(可能是间接被执行,如DLL)。
如何知道一个PE文件是否被签名?
对于许多人来说,简单的答案就是打开该PE文件的属性,如果存在“数字签名”选项卡,那么就意味着该文件是被签名的。当你看到“数字签名”标签存在于一个文件属性中时,它实际上是表示该PE文件是被验证签名过的,这也意味着该文件本身会存在一个由证书和签名组成的哈希文件的二进制数据,该验证签名的存储格式被记录在PE验证签名规范文档中。
然而,有一些文件是要求被签名的,另外有的文件(记事本程序)是没有“数字签名”标签的,这是否意味着文件没有签名,但微软实际上是在发布未签名的代码呢?这得看具体情况,虽然记事本程序没有验证签名被植入到本身,但是实际上,它是通过另一种方法进行被签名的---目录签名。
Windows包含一个由许多目录文件组成的目录储蓄,这些目录文件主要是验证签名哈希值的列表。每个目录文件然后去签署证实匹配来源于这些目录文件的签名任何文件的哈希值(这是微软在几乎所有的情况下),所以,浏览器UI并不试图去查找目录签名,但是几乎所有的验证签名工具都会执行目录签名查找,例如PowerShell和Sysinternals Sigcheck。
(说明:这个目录存储文件位于 %windir%System32CatRoot{F750E6C3-38EE-11D1-85E5-00C04FC295EE})
在上面的截图中,我们可以从签名属性中看出notepad.exe程序是目录签名,还有值得注意的是IsOSBinary属性,因为执行是不被记录的,所以他将会显示“true”以表明一个签名已经连接到微软的根证书。有兴趣了解是如何实现的应该逆向CertVerifyCertificateChainPolicy函数。
使用 Sigcheck “-i”这个命令可以执行目录证书验证和显示包含验证哈希值的目录文件路径,“-h”这个命令是计算和显示PE文件的验证哈希的SHA1和SHA256.
我们知道验证哈希是允许你查找目录文件,你也可以双击一个目录文件已查看其条目,我还写了catalogtools PowerShell模块解析目录文件。“hint”的元数据字段给出了notepad.exe相应的条目。

数字签名的二进制格式
现在你已经知道了一个PE文件被签名的方法(验证和目录),知道一些签名的二进制格式是非常有用的,无论是验证签名还是目录签名,这两个签名都是存储为PKCS #7签名数据,这种数据是ASN.1格式的二进制数据。ASN.1是一个标准,它说明了不同数据类型的二进制数据应该如何存储。在观察、解析数字签名的字节之前,你必须首先知道它是如何存储在文件中的。目录文件本身是由PKCS #7数据组成,在线有一个ASN.1解码器,可以解析出ASN.1数据并以直观的方式呈现。例如,尝试加载包含notepad.exe散列到解码器的目录文件,你会得到一个意义上的数据布局。
ASN.1的每一个属性的解码都开始于一个对象标识符(OID),该OID是一个唯一的数字序列。如下图所示:
花时间探索数字签名中包含的所有字段是值得的。然而,目前所有的字段不在这个博客文章的范围之内。额外的加密、签名相关的OID是列在这里。
嵌入到PE中的验证签名检索
验证签名的签名数据是被植入到PE文件的末尾,操作系统也需要更多的信息以便于可以得到精确的便宜,我们可以见一下kernel32.dll在我最喜欢的PE编辑工具的情况吧。(CFF Explorer)
被嵌入的验证签名的便宜和大小都存储在可选标头中的“数据目录”数组内的“安全目录”偏移中,该数据目录包含在PE文件中各种接受的偏移和大小,比如导入表、导出表和重定位等等,数据目录中的所有偏移都是相对虚拟地址偏移(RVA),意思是当加载到内存时,他们是PE中各个部分的偏移量,但是有一个反面的例子,安全目录将其偏移量作为文件偏移量存储。这样做的原因是Windows加载程序实际上不在加载安全目录中的内容到内存。
在安全目录中文件偏移二进制数据是一个win_certificate结构。
PE验证签名结构中总有一个字段wRevision,存在于WIN_CERT_TYPE_PKCS_SIGNED_DATA中,这个字节矩阵和PKCS #7是一样的,ASN.1编码的数据正是你在目录文件中看到的,仅仅不同的是你不会找到1.3.6.1.4.1.311.12.1.1 OID,而是显示存在目录哈希。
数字签名应用到无签名的PE中
现在你已经对数字签名的二进制格式和存储位置有了基本的概念,你可以开始将现有签名应用到未签名的代码中。
应用被植入的验证签名
将一个被植入的验证签名从一个签名文件应用到无签名的文件中是非常简单的,因为这个过程很明显是可以自动化的,接下来我将介绍如何使用hex editor 和CFF Explorer工具来进行操作。
步骤1: 获取我们需要的一个验证签名,比如我用kernel32.dll的签名。
步骤2: 获取该签证签名在安全目录中WIN_CERTIFICATE结构的偏移量和大小。
【由上图可知,该RVA是0x000A9600,大小是x00003A68】
步骤 3: 在二进制编辑器中打开kernel32.dll,选择从0x000A9600地址开始x00003A68个字节大小的内容,复制他们。
步骤4: 在二进制编辑器中打开这个无签名的文件(如HelloWord.exe文件),调至最后位置,粘贴来自于上一步骤中的内容,注意现在的该内容的偏移量就是这个签名的偏移量(现在是0x00000E00),然后保存文件。
步骤5: 用CFFExplorer文件打开HelloWorld.exe文件,然后更新安全目录中的偏移和大小(目前是RVA = 0x00000E00 大小不变)修改完成之后保存文件,忽略不合法的警告,这不会对其有影响的。
现在成功了,签名验证程序将可以解析和显示出该文件的验证签名证书,唯一要注意的是,他将会显示这个签名是不合法的,因为这个被计算出来的验证签名码和被保存在证书里的不匹配。
现在,如果你想知道为什么这个验证签名值是不匹配的,有人在想我们是使用的同一个验证签名,为何会出现不一样呢?
那是因为GetAuthenticodeSignature函数首先会尝试去kernel32.dll中查找目录文件。在这个案件中,他在kernel32.dll中找到了一个目录入口,并且显示了这个目录文件中的签名信息,为了让这个验证签名特征值是完全一样的变得合法,需要临时关闭CryptSvc service,这个服务的职责就是去执行哈希目录查询。现在你看到的则是匹配的,这个说明了目前这个被签名的哈希目录与之前kernel32.dll签名的是不同的。
在PE中应用目录签名
实际上,CryptSvc是一直运行的并且执行目录查询操作。假如你要记住OPSEC和匹配用于签署你的目标二进制文件的验证证书,事实证明,你可以通过交换WIN_CERTIFICATE结构体中的bCertificate内容和更新相应的dwlength的方式去申请一个目录文件到被植入签名的二进制中,同时你要注意,我们的目标是(在这个案件中)申请一个签名验证码到我们没有签名的二进制文件中,这种方式与我们签包含目录文件是一样的,在这个案件中,证书特征值为:
AFDD80C4EBF2F61D3943F18BB566D6AA6F6E5033.
步骤1: 获取包含目标二进制验证码哈希的目录文件,在这个案件中是kernel32.dll,如果一个文件是被验证码签署过的,sigcheck实际上不能操获取这个目录文件,但是这个Signtool工具是可以的。
步骤2: 使用二进制文件编辑器打开目录文件并且注释其大小:0x000137C7
步骤3: 我们将在编辑器中手动地制作WIN_CERTIFICATE结构体,使用我们申请到的字段。
dwLength:这个是结构体的长度,fields = 4(size of DWORD) + 2 (size of WORD) + 2 (size of WORD) + 0x000137C7(bCertificate - the file size of the .cat file) = 0x000137CF.
wRevision:0x0200
wCertificateType:0x0002
bCertificate:目录文件的原始字节。
当在十六进制编辑器制作时,谨记字段存储在小端格式。
步骤4: 复制你手工制作的WIN_CERTIFICATE结构体的字节内容,追加到未签名的PE文件中并且更新相应的安全目录中的偏移量和大小。
现在,如果你的计算和操作步骤都正确的话,你应该可以看到一个目录文件的特征配了。

培养“异常”检测的思维
通过以上的解析,希望大家能思考关于二进制数字签名的滥用问题,大家可从以下几点去调查和编写潜在的异常签名的检测: PE时间戳与证书有效期之间是否存在相关性? 攻击者提供代码的PE时间戳是否与前面提到的相关性不符? 你对具有哈希不匹配的“签名”文件的信任程度是什么? 你将会如何去检测一个被植入签名的PE文件? 如果在数字签名之外有附加数据怎么办? 什么样的影响可能停止/禁用安全产品进行局部签名验证cryptsvc服务吗?
软件开发
2020-07-01 17:25:00
创建JWT: String jwt=Jwts.builder().setSubject("Ag47").signWith(key).compact();
SignKey: 在枚举 io.jsonwebtoken.SignatureAlgorithm 中定义了常用的加密算法。
因为JWT对密码长度的要求,JJWT提供了相应的API: keys // 创建:对称加密 SecretKey key = Keys . secretKeyFor( SignatureAlgorithm . HS256 ); // 创建:不对称加密 KeyPair keyPair = Keys.keyPairFor(SignatureAlgorithm.RS256); /** 不对称加密,包括私钥[ keyPair.getPrivate() ]和公钥[ keyPair.getPublic() ]; 私钥用于加密。 公钥用于解密。 */
在Jwts.builder().signWith(key)中,也可以明确指定加密算法,如: signWith(key,SignatureAlgorithm.RS512).compact();
Header:
# 设置header时,不需要设置typ和alg两个key,JJWT会自动设置,即使手工设置也会被覆盖。
1. 使用setHeaderParam设置key-value header: Jwts.builder().setHeaderParam("name","Ag47");
2. 使用Header或Map 设置key-value header: Header header=Jwts.header();
// Map header=new Map();
header.put("name","Ag47");
Jwts.builder().setHeader(header);
Claims:
JWT提供了系列标准的Claim,建议但不强制使用。 setIssuer:设置发布人信息,对应key=iss
setSubject:设置发布主题,对应key=sub
setAudience:设置接收人信息,如客户端Url数组,对应key=aud
setExpiration:设置到期时间,超期失效,对应key=exp
setNotBefore:设置这个时间之后才生效,对应key=nbf
setIssuedAt:该jwt的生成时间,对应key=iat
setId:该jwt的唯一标识,对应key=jti
例子: Jwts.builder().setIssuer("Ag47").setIssuedAt(new Date()).....
除了上述提供了之外,还可以自定义claims: Jwts.builder().claim("name","Ag47").claim("id","haha")....
或者: Claims claims=Jwts.claims();
// Map claims=new Map();
claims.put("name","Ag47");
Jwts.builder().setClaims(claims)...

关于exp和nbf时间差的问题:
如果生成jwt的机器和使用的机器时钟有偏差,可能会断言失败,这时候需要通过以下方式处理: long seconds=60*3;
Jwts.parserBuilder().setAllowedClockSkewSeconds(seconds)....builder().parseClaimsJws(jwt);
如果时差在3分钟之内,会忽略不计,如果差的很多,说明电脑时钟应该同步了。
Jwts解析时,默认使用的new Date() ,如果需要自定义,可以使用: Clock myClock=new MyClock();
Jwts.parserBuilder().setClock(myClock);
软件开发
2020-07-01 14:11:00
Tombstone日志的生成
Android默认是不会抓取coredump文件的,AOSP在进程发生内存访问异常的时候一般会在 data/tombstones/ 下面生成“tombstone_0x”形式命名的文件,这个文件是debuggerd进程来抓取的,在可执行文件被加载的时候会执行到bionic/linker/debugger.cpp这个文件的debuggerd_init函数,在这个函数里面注册了信号处理函数为debuggerd_signal_handler,所以当这个进程接收到SIGABRT、SIGBUS、SIGSEGV等signal的时候kernel都会回调debuggerd_signal_handler这个函数来处理. __LIBC_HIDDEN__ void debuggerd_init ( ) { struct sigaction action ; memset ( & action , 0 , sizeof ( action ) ) ; sigemptyset ( & action . sa_mask ) ; action . sa_sigaction = debuggerd_signal_handler ; action . sa_flags = SA_RESTART | SA_SIGINFO ; // Use the alternate signal stack if available so we can catch stack overflows. action . sa_flags |= SA_ONSTACK ; sigaction ( SIGABRT , & action , nullptr ) ; sigaction ( SIGBUS , & action , nullptr ) ; sigaction ( SIGFPE , & action , nullptr ) ; sigaction ( SIGILL , & action , nullptr ) ; sigaction ( SIGSEGV , & action , nullptr ) ; # if defined(SIGSTKFLT) sigaction ( SIGSTKFLT , & action , nullptr ) ; # endif sigaction ( SIGTRAP , & action , nullptr ) ; }
debuggerd_signal_handler的实现如下,它的作用就是给守护进程发送一个socket请求,由后者通过ptrace调用来获取出错进程的出错地址,出错信号,线程堆栈、/proc/*/maps等信息,也就是上面所讲述的tombstone文件,zygote进程的app_process可执行文件在第一次被加载的时候也会执行上面的debuggerd_init逻辑,得益于Linux的fock调用,所有的zygote的子进程都有这个信号处理过程,不需要再另外添加代码. static void debuggerd_signal_handler ( int signal_number , siginfo_t * info , void * ) { // It's possible somebody cleared the SA_SIGINFO flag, which would mean // our "info" arg holds an undefined value. if ( ! have_siginfo ( signal_number ) ) { info = nullptr ; } log_signal_summary ( signal_number , info ) ; { send_debuggerd_packet ( info ) ; // We need to return from the signal handler so that debuggerd can dump the // thread that crashed, but returning here does not guarantee that the signal // will be thrown again, even for SIGSEGV and friends, since the signal could // have been sent manually. Resend the signal with rt_tgsigqueueinfo(2) to // preserve the SA_SIGINFO contents. signal ( signal_number , SIG_DFL ) ; struct siginfo si ; if ( ! info ) { memset ( & si , 0 , sizeof ( si ) ) ; si . si_code = SI_USER ; si . si_pid = getpid ( ) ; si . si_uid = getuid ( ) ; info = & si ; } else if ( info -> si_code >= 0 || info -> si_code == SI_TKILL ) { // rt_tgsigqueueinfo(2)'s documentation appears to be incorrect on kernels // that contain commit 66dd34a (3.9+). The manpage claims to only allow // negative si_code values that are not SI_TKILL, but 66dd34a changed the // check to allow all si_code values in calls coming from inside the house. } int rc = syscall ( SYS_rt_tgsigqueueinfo , getpid ( ) , gettid ( ) , signal_number , info ) ; if ( rc != 0 ) { __libc_format_log ( ANDROID_LOG_FATAL , "libc" , "failed to resend signal during crash: %s" , strerror ( errno ) ) ; _exit ( 0 ) ; } }
Tombstone日志 pid : 27730 , tid : 27736 , name : thermalloadalgo >> > / vendor / bin / thermalloadalgod << < signal 6 ( SIGABRT ) , code - - - - - - - - , fault addr - - - - - - - - r0 00000000 r1 00006c52 r2 00000006 r3 00000008 r4 f0ce558c r5 00000006 r6 f0ce5534 r7 0000010c r8 05f481e2 r9 f0bfca90 sl 00000000 fp ffea81cc ip 00000002 sp ffea7f70 lr f0896507 pc f0898d64 cpsr 20070010 backtrace : #00 pc 0004ad64 /system/lib/libc.so (tgkill+12) #01 pc 00048503 /system/lib/libc.so (pthread_kill+34) #02 pc 0001d4a5 /system/lib/libc.so (raise+10) #03 pc 00018fe1 /system/lib/libc.so (__libc_android_abort+34) #04 pc 00017044 /system/lib/libc.so (abort+4) #05 pc 0001b49f /system/lib/libc.so (__libc_fatal+22) #06 pc 0001b47f /system/lib/libc.so (__fortify_chk_fail+26) #07 pc 00050cbb /system/lib/libc.so (__sprintf_chk+66) #08 pc 000039fb /system/vendor/lib/libthermalalgo.so (_Z12get_uid_tputi+54) #09 pc 00003b3b /system/vendor/lib/libthermalalgo.so (_Z18update_avg_fg_tputv+14) #10 pc 00002df1 /system/vendor/lib/libthermalalgo.so (_Z3spav+324) #11 pc 000020ed /system/vendor/lib/libthermalalgo.so (libthermal_algo_setup+244) #12 pc 000008df /system/vendor/bin/thermalloadalgod #13 pc 00016c5d /system/lib/libc.so (__libc_init+48) #14 pc 00000770 /system/vendor/bin/thermalloadalgod stack : ffea7f30 f0bfd114 / system / vendor / lib / libthermalalgo . so ffea7f34 00000000 ffea7f38 ffea81cc [ stack ] ffea7f3c f0893dd3 / system / lib / libc . so ( vsnprintf + 138 ) ffea7f40 ffffffff ffea7f44 898045b6 ffea7f48 00000000 ffea7f4c 00000000 ffea7f50 00000000 ffea7f54 00000000 ffea7f58 00000000 ffea7f5c 00000000 ffea7f60 ffffffff ffea7f64 00004000 ffea7f68 f0ce558c ffea7f6c f08964f7 / system / lib / libc . so ( pthread_kill + 22 ) #00 ffea7f70 00000000 . . . . . . . . . . . . . . . . #01 ffea7f70 00000000 ffea7f74 00000006 ffea7f78 00000000 ffea7f7c f0bff168 / system / vendor / lib / libthermalalgo . so ffea7f80 f0bff160 / system / vendor / lib / libthermalalgo . so ffea7f84 f086b4a9 / system / lib / libc . so ( raise + 14 ) . . . . . .
debuggerd守护进程抓取的tombstone文件内容大概如上所示,这个Log具体说明了如下信息: 出错的进程名字为 /vendor/bin/thermalloadalgod ,它的pid是 27730 ,而具体导致接收到这次信号的tid也为 27736 . 进程收到的是SIGABRT信号. 抓取这个日志的时候,每个寄存器的内容,分析问题的时候一般需要结合反汇编代码和这里的寄存器内容分析. 这个线程的函数栈帧信息,也就是backtrace,从中我们可以看到这个线程的函数调用流程,因为tombstone文件一般是在终端抓取的,由于终端的可执行文件和so文件并没有保存有可供调试的符号信息,所以只能看到是哪个函数,但是对应不到对应的代码行,如果有对应的符号信息,可以用addr2line等工具把这里的地址信息对应的代码行,建议用add2line的时候 加上 “-ie”参数,可以把inline函数也打印出来. 这个线程对应的函数栈的内容,函数栈用来保存局部变量、返回地址、SP寄存器等内容.
常见信号 SIGABRT 在Android里面,这个信号一般是进程自己检测到不可恢复的错误之后,主动调用abort函数之后会接收到的一种信号,一般在log里面会打印出调用abort的原因,这种问题相对来说可能容易解决一些,只要根据错误提示信息,就可以去找相应的原因. SIGSEGV 在POSIX兼容的平台上, SIGSEGV 是当一个进程执行了一个无效的内存引用,或发生 段错误 时发送给它的信号,例如访问没有实际物理内存对应的虚拟地址,或者访问的时候出现权限异常,比较常见的就是空指针,对于SIGSEGV这一类信号,在tombstone log里面会给出fault addr,根据汇编代码和寄存器内容,可以接着往下分析. SIGBUS SIGBUS与SIGSEGV有点类似,也都是访问内存的时候出错,他们之间的区别可能在于如果那个地址是正常的(也就是说正常的虚拟地址,而且它也有对应的物理内存),如果访问出错,那么就会上报SIGBUS,否则就上报的是SIGSEGV,例如有些芯片总线访问需要地址是4字节对齐的,如果那个地址不是对齐的,那么就会上报SIGBUS.
这三种信号在Android稳定性问题分析当中是比较常见的,除了这三种信号之外,其实Linux有64种信号,一般来说前面的32种是标准信号,后面32种是新增加的实时信号,对于标准信号,一个进程里面同一时间只能存在一个,而实时信号却没有这个限制,可以存在多个,这里借用 Unix系统中常用的信号含义 这个文章的内容对标准信号做一些简单的介绍 Signal Description 1 - SIGHUP 本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都属于这个Session。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操作为终止进程,因此前台进程组和后台有终端输出的进程就会中止。不过可以捕获这个信号,比如wget能捕获SIGHUP信号,并忽略它,这样就算退出了Linux登录,wget也能继续下载。此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。
2 - SIGINT 程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。
3 - SIGQUIT 和SIGINT类似, 但由QUIT字符(通常是Ctrl-)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。
4 - SIGILL 执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。
5 - SIGTRAP 由断点指令或其它trap指令产生. 由debugger使用。
6 - SIGABRT 调用abort函数生成的信号。
7 - SIGBUS 非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。
8 - SIGFPE 在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。
9 - SIGKILL 用来立即结束程序的运行.本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。
10 - SIGUSR1 留给用户使用
11 - SIGSEGV 试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.
12 - SIGUSR2 留给用户使用
13 - SIGPIPE 管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。
14 - SIGALRM 时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.
15 - SIGTERM 程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。
17 - SIGCHLD 子进程结束时, 父进程会收到这个信号。如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程来接管)。
18- SIGCONT 让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符
19 - SIGSTOP 停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.
20 - SIGTSTP 停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号
21 - SIGTTIN 当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行.
22 - SIGTTOU 类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.
23 - SIGURG 有"紧急"数据或out-of-band数据到达socket时产生.
24 - SIGXCPU 超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。
25 - SIGXFSZ 当进程企图扩大文件以至于超过文件大小资源限制。
26 - SIGVTALRM 虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.
27 - SIGPROF 类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.
28 - SIGWINCH 窗口大小改变时发出.
29 - SIGIO 文件描述符准备就绪, 可以开始进行输入/输出操作.
30 - SIGPWR
31 - SIGSYS
Power failure
非法的系统调用
objdump工具
tombstone文件抓取的内容比较少,没有coredump文件那么全,分析这种Log的时候,不可缺少的要用到objdump这类反编译工具,objdump能反编译ELF文件,将它的代码段反汇编出来,结合tombstone文件里面的fault addr地址信息、寄存器信息、函数栈帧信息等来分析出问题的原因. Usage : aarch64 - linux - android - objdump < option ( s ) > < file ( s ) > Display information from object < file ( s ) > . At least one of the following switches must be given : - a , -- archive - headers Display archive header information - f , -- file - headers Display the contents of the overall file header - p , -- private - headers Display object format specific file header contents - P , -- private = OPT , OPT . . . Display object format specific contents - h , -- [ section - ] headers Display the contents of the section headers - x , -- all - headers Display the contents of all headers - d , -- disassemble Display assembler contents of executable sections - D , -- disassemble - all Display assembler contents of all sections - S , -- source Intermix source code with disassembly - s , -- full - contents Display the full contents of all sections requested - g , -- debugging Display debug information in object file - e , -- debugging - tags Display debug information using ctags style - G , -- stabs Display ( in raw form ) any STABS info in the file - W [ lLiaprmfFsoRt ] or -- dwarf [ = rawline , = decodedline , = info , = abbrev , = pubnames , = aranges , = macro , = frames , = frames - interp , = str , = loc , = Ranges , = pubtypes , = gdb_index , = trace_info , = trace_abbrev , = trace_aranges , = addr , = cu_index ] Display DWARF info in the file - t , -- syms Display the contents of the symbol table ( s ) - T , -- dynamic - syms Display the contents of the dynamic symbol table - r , -- reloc Display the relocation entries in the file - R , -- dynamic - reloc Display the dynamic relocation entries in the file @ < file > Read options from < file > - v , -- version Display this program's version number - i , -- info List object formats and architectures supported - H , -- help Display this information The following switches are optional : - b , -- target = BFDNAME Specify the target object format as BFDNAME - m , -- architecture = MACHINE Specify the target architecture as MACHINE - j , -- section = NAME Only display information for section NAME - M , -- disassembler - options = OPT Pass text OPT on to the disassembler - EB -- endian = big Assume big endian format when disassembling - EL -- endian = little Assume little endian format when disassembling -- file - start - context Include context from start of file ( with - S ) - I , -- include = DIR Add DIR to search list for source files - l , -- line - numbers Include line numbers and filenames in output - F , -- file - offsets Include file offsets when displaying information - C , -- demangle [ = STYLE ] Decode mangled / processed symbol names The STYLE , if specified , can be `auto ', `gnu' , `lucid ', `arm' , `hp ', `edg' , `gnu - v3 ', `java' or `gnat' - w , -- wide Format output for more than 80 columns - z , -- disassemble - zeroes Do not skip blocks of zeroes when disassembling -- start - address = ADDR Only process data whose address is >= ADDR -- stop - address = ADDR Only process data whose address is <= ADDR -- prefix - addresses Print complete address alongside disassembly -- [ no - ] show - raw - insn Display hex alongside symbolic disassembly -- insn - width = WIDTH Display WIDTH bytes on a single line for - d -- adjust - vma = OFFSET Add OFFSET to all displayed section addresses -- special - syms Include special symbols in symbol dumps -- prefix = PREFIX Add PREFIX to absolute paths for - S -- prefix - strip = LEVEL Strip initial directory names for - S -- dwarf - depth = N Do not display DIEs at depth N or greater -- dwarf - start = N Display DIEs starting with N , at the same depth or deeper -- dwarf - check Make additional dwarf internal consistency checks . aarch64 - linux - android - objdump : supported targets : elf64 - littleaarch64 elf64 - bigaarch64 elf32 - littleaarch64 elf32 - bigaarch64 elf32 - littlearm elf32 - bigarm elf64 - little elf64 - big elf32 - little elf32 - big plugin srec symbolsrec verilog tekhex binary ihex aarch64 - linux - android - objdump : supported architectures : aarch64 aarch64 : ilp32 arm armv2 armv2a armv3 armv3m armv4 armv4t armv5 armv5t armv5te xscale ep9312 iwmmxt iwmmxt2 plugin The following AARCH64 specific disassembler options are supported for use with the - M switch ( multiple options should be separated by commas ) : no - aliases Don't print instruction aliases . aliases Do print instruction aliases . The following ARM specific disassembler options are supported for use with the - M switch : reg - names - special - atpcs Select special register names used in the ATPCS reg - names - atpcs Select register names used in the ATPCS reg - names - apcs Select register names used in the APCS reg - names - std Select register names used in ARM 's ISA documentation reg - names - gcc Select register names used by GCC reg - names - raw Select raw register names force - thumb Assume all insns are Thumb insns no - force - thumb Examine preceding label to determine an insn's type
作者:HuangTao_Zoey
链接:https://www.jianshu.com/p/7bf796415210
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
软件开发
2020-07-01 11:47:00
现在距离 Python 3.9.0 的最终版本还有 3 个月,官方公布的时间线是:
> 3.9.0 beta 4: Monday, 2020-06-29 > > 3.9.0 beta 5: Monday, 2020-07-20 > > 3.9.0 candidate 1: Monday, 2020-08-10 > > 3.9.0 candidate 2: Monday, 2020-09-14 > > 3.9.0 final: Monday, 2020-10-05
详情参见 PEP-596
这其实也意味着 Python 3.10 的第一个 alpha 版本快要面世了——因为它会随着 3.9.0 的 final 版本一起发布!
之前有读者问下个版本是不是 4.0,或者误以为下个版本是 4.0,还担心会再次出现 2 升 3 这种大版本不兼容的情况。其实官方早就有消息出来了,下个版本只会是 3.10,是个小版本的特性升级。
Python 采用了 语义化版本(Semantic Versioning) 命名风格,也就是“ 主版本号.次版本号.修订号 ”,在可预见的未来,主版本号还会保持为 3,继续 3.11、3.12 这样排下去……
在上个月末(即 5 月 25 日),Python 官方公布了 3.10 版本的发布计划:
详情参见: https://www.python.org/dev/peps/pep-0619/
不仅如此,在 6 月中旬,3.10 版本的第一个正式的 PEP 也被采纳了。标题: PEP-618 Add Optional Length-Checking To zip
zip 内置函数非常有用,通常用来把两个序列拼出元组,过程就像用拉链把两边拉起来一般,所以也被称为“拉链函数”。
简单而言,PEP-618 会给 zip 函数增加一个可选参数,当拉链两边的序列不等长时,方便进行异常处理。更多具体内容,可查阅 PEP 文档。
它的中文翻译工作正在紧锣密鼓地进行中,预计两天内会发布。
等译完后,我会发布在 Python猫 公众号上,同时归档到 PEP中文翻译计划 ,敬请大家期待。
除了这个已被采纳的 PEP,社区里还有几个 PEP 引起了广泛的讨论,例如国内的华蟒社区邮件组这几天就正在讨论 PEP-622 ,还有 PEP-620、PEP-621、PEP-623 这些编号相近的提案都处在草案阶段,估计很快就会出现第二个被采纳的 PEP 了。
按照 3.9.0 版本的开发情况,它最终采纳了 7 个 PEP,详情参见《 Python 3.9 的 7 个 PEP 介绍 》。
那么,3.10 版本又会引入多少个 PEP 呢?Python猫 会持续关注,尽量在第一时间把最新的 PEP 翻译出来,如果你也感兴趣的话,欢迎关注我在 Github 发布的 PEP中文翻译计划 。
推荐阅读 3.9 版本的最新特性解读:
Python 3.9 新特性:任意表达式可作为装饰器!
Python 3.9 性能优化:更快的 list()、dict() 和 range() 等内置类型
Python 3.9 beta2 版本发布了,看看这 7 个新的 PEP 都是什么?
软件开发
2020-06-30 23:15:00
cp命令取消提示的方法   Linux默认cp命令带参数-i如果有重复的文件会提示覆盖
  查看cp别名
  在大量复制的时候这个提示不友好,在脚本写复制命令也无法使用交互式输入
  解决办法
  1,修改别名
1 vi ~/.bashrc 注释掉 alias cp=‘cp -i’   2,使用全路径命令拷贝
1 /usr/bin/cp /etc/hosts .   3,在命令前加\
1 \cp /etc/hosts .   PS:使用方法2时候命令的路径可能不是这个,可能需要使用which命令查看命令完整路径
    推荐使用方法3
软件开发
2020-06-30 17:47:00
假如,明天你财务自由了,你会想去做些什么?
最近,他二哥坐电梯发现身边的程序员们,除了交流各种棘手的Bug之外,还会讨论股市大盘涨了还是跌了,自己买的股票赚了还是亏了。生活不易,猿猿叹气,谁又没有做过一夜暴富的美梦呢?
待到春来时,洗手作羹汤。 抛弃java C,只把歌来唱。
这样美丽如画的场景在他二哥的梦里不知道出现了几回,然后就从梦里被自己无情地笑醒了。于是,他二哥也问了一圈程序员财富自由后想做什么。
有程序员说一直有个山区支教的梦想,想靠自己的力量建很多很多的希望小学。
有的程序员崇尚小隐隐于野,自己搞一块不大不小的地体验一把归园田居的山林生活。
还有程序员说,代码是真爱,产品经理是意外,有钱的第一件事就是顺理成章地炒了需求,自给自足965…
下面就跟他二哥一起来看看鹅厂程序员们在实现财务自由之后,会有怎样奇奇怪怪可可爱爱的举动呢?



想了解鹅厂程序员有多硬核?
有哪些欢乐沙雕日常?
快来公众号找我们!
原文链接: http://mp.weixin.qq.com/s?__biz=MjM5ODYwMjI2MA==&mid=2649747910&idx=1&sn=3892bdb5ae97a1b17346ad72d8bf526a
软件开发
2020-07-28 22:13:00
文章内容输出来源:拉勾教育Java高薪训练营
前言
对于面试中涉及的一些问题,大多是层次多,知识点杂,不方便记忆,经常是一听就会,一面就忘。 我想到了一个对自己很有效的记忆方法:类比联想记忆法。
其实这个方法大家很熟悉,也很常用,只是需要找到比较贴切且适合自己的事物进行关联。 特此分享一下自己的经验。本文目的不在于知识的讲解,而在于知识的持久化。如有错误,欢迎指正。
不知道大家之前对jvm的内存结构能记住多少,认真看完以后尝试天天回忆下这个场景。 因为除了第一次建立记忆连接以外,还需要经常在脑中反复强化这个连接,才能达到深刻记忆的效果。
先整体浏览下JVM内存结构:大体上分为三个层次,二十多个知识点。
第一层
概念
类加载器:作用是将java 文件经过编译后会得到 .class 文件加载到内存。 本地库接口:用native修饰的,不能和abstract共同使用的,不显示方法体但却是用非Java语言实现方法体的方法。 运行时数据区:java虚拟机在执行Java程序的过程中会把它管理的内存分为若干个不同的数据区域。 执行引擎:jvm核心组成部分之一,建立在处理器、硬件和操作系统层面之上。引擎在执行代码时会有解释执行和编译执行两种选择,输入字节码文件,解析字节码输出结果。
类比
回顾完基本概念后,我们再关联上一个更加容易记忆的场景,左侧为类比事物,右侧为对应知识点。
想象一下一辆刚刚驶入加油站的汽车:
司机拿起加油枪(类加载器) 对准加油口(本地库接口)开始加油(开始类加载的过程,后边会再分析) 加油的目的是供给发动机可以使用的燃料(执行引擎) 然后才可以让让车继续保持行驶状态(运行时数据区)
第二层:类加载器
概念
加载:指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。 链接:当类被加载之后,系统为之生成一个对应的Class对象,接着将会进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。类连接又可分为如下3个阶段。 验证:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。 准备:负责为类的静态变量分配内存,并设置默认初始值。 解析:将类的二进制数据中的符号引用替换成直接引用。 初始化:为类的静态变量赋予正确的初始值。 使用:业务处理中被引用。 销毁:不再使用时被垃圾回收。
类比
再来类比下汽油使用的整个过程:
加载:将汽油添加到油箱。 链接:让发动机的油路中充满汽油。 验证:检查汽油是否符合发动机要求。 准备:汽油和发动机已完成启动前的准备状态。 解析:汽油开始燃烧并产出动力,化学燃料转换成物理的做功。 初始化:发动机启动完毕。 使用:汽车行驶过程中,发动机使用汽油持续运转。 销毁:汽油被消耗。
第二层:运行时数据区
概念
程序计数器:记录的是正在执行的虚拟机字节码指令的地址。 虚拟机栈:Java 方法执行的内存模型。 本地方法栈:区别于Java 虚拟机栈的是,Java 虚拟机栈为虚拟机执行 Java 方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的Native 方法服务。 堆:这块区域是 JVM 所管理的内存中最大的一块。线程共享,主要是存放对象实例和数组。 方法区:属于共享内存区域,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
类比
程序计数器:记录汽车行驶距离的里程表。 虚拟机栈:电动车电池。 本地方法栈:汽油车油箱。 堆:可以堆放大件货物的后备箱。 方法区:可以提供操作汽车的各种方法的驾驶区域。
第三层:虚拟机栈
概念
栈帧( Frame)是用来存储数据和部分过程结果的数据结构,同时也被用来处理动态链接 (Dynamic Linking)、 方法返回值和异常分派( Dispatch Exception)。栈帧随着方法调用而创建,随着方法结束而销毁——无论方法是正常完成还是异常完成(抛出了在方法内未被捕获的异常)都算作方法结束。 局部变量表:一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量。 操作数栈:一个后进先出栈,其最大深度在编译的时候已经确定了。当一个方法刚刚开始执行的时候,这个方法的操作数占是空的,在方法的执行过程中,会有各种字节码指令往操作数占中写入和提取内容,这就是出栈/入栈动作。 动态链接:每一个栈帧都包含一个执行运行时常量池中该栈帧所属方法的引用。持有这个引用是为了支持方法调用过程中的动态连接。 返回出口:方法正常退出,将会返回程序结束其的值给上层方法,经过调整之后以指向方法调用指令后面的一条指令,继续执行上层方法。 正常完成出口:方法正确执行,执行引擎遇到方法返回的指令,回到上层的方法调用者。 异常完成出口:方法执行过程中发生异常,并且没有处理异常,这样是不会给上层调用者产生任何返回值。
类比
每一个方法从调用直至执行完成的过程,就对应着一个栈帧在虚拟机栈中入栈到出栈的过程。对应着每一次快递派送:
局部变量表:每次派送的货物清单。 操作数栈:货车的货箱,只能栈式搬运货物。 动态链接:实时动态规划路径。 返回出口:到达终点以后就可以返回了。
总结
最后,我们再回顾下整个思路,方便平时反复回忆。 货车进入加油站进行加油时需用加油枪(类加载器)对准了加油口(本地库接口)。加过油的车可以让发动机(执行引擎)运转起来,并保持行驶状态(运行时数据区)。 加油过程中,汽油从油枪进入了油箱(加载),并抵达了发动机(链接)。这个过程中车载电脑检查了汽油(验证),并使汽油和发动机都进入启动前的准备状态(准备)。然后汽油开始燃烧,转化为动力势能(解析),发动机启动完成后(初始化)开始使用汽油(使用),当汽油燃烧后就被消耗了(销毁)。 所以驾驶员坐在可以操作汽车的驾驶室(方法区)操纵货箱(堆)载满货物的货车时,会观察记录移动距离的里程表和油箱指示灯(程序计数器),判断什么时候需要再次加油。而油电混合的汽车也会借助油箱(本地方法栈)的汽油燃烧给电池充电(虚拟机栈)。 货车的目的是将货箱(操作数栈)的货物(局部变量)运送到目的地(返回出口),途中会借助导航地图实时调整路线(动态链接)。
软件开发
2020-07-28 12:53:00
双向绑定,也就是说js中的数据传到页面,页面中的内容到js,实现同步更新,简单的演示可以直接复制下放HTML代码运行。
在这个例子中,我们使用defineProperty ,Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。详细信息可以自行查看MDN文档。
简单来说,defineProperty 就是一个监听器,监听对象中某一个属性被访问和修改,在Vue2.0中就是采用defineProperty
注意事项 在使用get函数监听属性的时候,不能直接监听当前属性,否则会出现死循环。所以在使用前我将对象进行浅拷贝的原因 每一个defineProperty只能对一个对象属性进行监听,所以你必须在使用之前就得知道属性的名字,但是很多时候属性是动态生成的,,所以就很麻烦。 Document


defineProperty的弊端很明显,在ES6中提出了Proxy, 在Vue3.0中也将使用Proxy代替defineProperty,在Proxy中,我们可以在监听整一个对象属性的变化。 Document


对比两个例子,眼尖的friend会发现,第一个例子中我访问对象属性使用的是stu.name,而在第二个例子中使用的是stu['name']的方式。在《javascript高级程序设计》
引用类型的那一章提到:一般来说,访问对象属性时使用的都是点表示法,这也是很多面向对象语言中通用的语法。不过,
在 JavaScript 也可以使用方括号表示法来访问对象的属性。除非必须使用变量来访问属性,否则我们建议使用点表示法。在我们第二个例子Proxy中,prop是一个变量,所以我们使用方括号。特别要注意,stu.name和stu[name]是不一样的,当然如果有外部变量name=“name“,就一样了
最后 想要学习java基础或者进阶java 的同学私信回复 资料 领取一线大厂Java面试题总结+阿里巴巴泰山手册+各知识点学习思维导+一份300页pdf文档的Java核心知识点总结!
这些资料的内容都是面试时面试官必问的知识点,篇章包括了很多知识点,其中包括了有基础知识、Java集合、JVM、多线程并发、spring原理、微服务、Netty 与RPC 、Kafka、日记、设计模式、Java算法、数据库、Zookeeper、分布式缓存、数据结构等等。
软件开发
2020-07-27 20:50:00
求:
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。

示例 1:
输入: s = "abcdefg", k = 2
输出: "cdefgab"
示例 2:
输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"

限制:
1 <= k < s.length <= 10000

题目链接: https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/

解:
没有任何难度,通过substring获取子串,然后拼接字符串即可。
也可以通过字符数组的方式来操作,每次循环都把第一个元素放到数组最末尾,其他元素依次向前移动。循环n次后得到结果。
这里面直接使用substring来操作是最快的。 public String reverseLeftWords (String s , int n) { return s.substring(n) + s.substring(0, n); } public String reverseLeftWords (String s , int n) { StringBuilder builder = new StringBuilder() ; for ( int i = n ; i < s.length() ; i++) { builder.append(s.charAt(i)) ; } for ( int i = 0 ; i < n ; i++) { builder.append(s.charAt(i)) ; } return builder.toString() ; } public String reverseLeftWords (String s , int n) { char [] chars = s.toCharArray() ; for ( int i = 0 ; i < n ; i++) { char tmp = chars[ 0 ] ; for ( int j = 0 ; j < chars. length - 1 ; j++) { chars[j] = chars[j + 1 ] ; } chars[chars. length - 1 ] = tmp ; } return String. valueOf (chars) ; }
软件开发
2020-07-26 15:33:00
先用react试了下, 搞了几个小时都没做出来想要的样子, 用vue3半个小时弄好了
react很多时候你觉得这么做没问题, 但是当你写了代码就会出问题, vue很多时候很符合直觉, 就算出错了, 也能根据经验和直觉快速定位
也可能是react用的太少, 毕竟也就不到一个月....


软件开发
2020-07-24 21:32:00
Java内存模型(Java Memory Model,JMM)是java虚拟机规范定义的,用来屏蔽掉java程序在各种不同的硬件和操作系统对内存的访问的差异,这样就可以实现java程序在各种不同的平台上都能达到内存访问的一致性。可以避免像c++等直接使用物理硬件和操作系统的内存模型在不同操作系统和硬件平台下表现不同,比如有些c/c++程序可能在windows平台运行正常,而在linux平台却运行有问题。
物理硬件和内存
首先,在单核电脑中,处理问题要简单的多。对内存和硬件的要求,各种方面的考虑没有在多核的情况下复杂。电脑中,CPU的运行计算速度是非常快的,而其他硬件比如IO,网络、内存读取等等,跟cpu的速度比起来是差几个数量级的。而不管任何操作,几乎是不可能都在cpu中完成而不借助于任何其他硬件操作。所以协调cpu和各个硬件之间的速度差异是非常重要的,要不然cpu就一直在等待,浪费资源。而在多核中,不仅面临如上问题,还有如果多个核用到了同一个数据,如何保证数据的一致性、正确性等问题,也是必须要解决的。
目前基于高速缓存的存储交互很好的解决了cpu和内存等其他硬件之间的速度矛盾,多核情况下各个处理器(核)都要遵循一定的诸如MSI、MESI等协议来保证内存的各个处理器高速缓存和主内存的数据的一致性。 除了增加高速缓存,为了使处理器内部运算单元尽可能被充分利用,处理器还会对输入的代码进行乱序执行(Out-Of-Order Execution)优化,处理器会在乱序执行之后的结果进行重组,保证结果的正确性,也就是保证结果与顺序执行的结果一致。但是在真正的执行过程中,代码执行的顺序并不一定按照代码的书写顺序来执行,可能和代码的书写顺序不同。
Java内存模型
虽然java程序所有的运行都是在虚拟机中,涉及到的内存等信息都是虚拟机的一部分,但实际也是物理机的,只不过是虚拟机作为最外层的容器统一做了处理。虚拟机的内存模型,以及多线程的场景下与物理机的情况是很相似的,可以类比参考。
Java内存模型的主要目标是定义程序中变量的访问规则。即在虚拟机中将变量存储到主内存或者将变量从主内存取出这样的底层细节。需要注意的是这里的变量跟我们写java程序中的变量不是完全等同的。这里的变量是指实例字段,静态字段,构成数组对象的元素,但是不包括局部变量和方法参数(因为这是线程私有的)。这里可以简单的认为主内存是java虚拟机内存区域中的堆,局部变量和方法参数是在虚拟机栈中定义的。但是在堆中的变量如果在多线程中都使用,就涉及到了堆和不同虚拟机栈中变量的值的一致性问题了。
Java内存模型中涉及到的概念有: 主内存:Java 虚拟机规定所有的变量(不是程序中的变量)都必须在主内存中产生,为了方便理解,可以认为是堆区。可以与前面说的物理机的主内存相比,只不过物理机的主内存是整个机器的内存,而虚拟机的主内存是虚拟机内存中的一部分。 工作内存:Java 虚拟机中每个线程都有自己的工作内存,该内存是线程私有的为了方便理解,可以认为是虚拟机栈。可以与前面说的高速缓存相比。线程的工作内存保存了线程需要的变量在主内存中的副本。虚拟机规定,线程对主内存变量的修改必须在线程的工作内存中进行,不能直接读写主内存中的变量。不同的线程之间也不能相互访问对方的工作内存。如果线程之间需要传递变量的值,必须通过主内存来作为中介进行传递。
这里需要说明一下:主内存、工作内存与java内存区域中的java堆、虚拟机栈、方法区并不是一个层次的内存划分。这两者是基本上是没有关系的,上文只是为了便于理解,做的类比
工作内存与主内存交互
物理机高速缓存和主内存之间的交互有协议,同样的,java内存中线程的工作内存和主内存的交互是由java虚拟机定义了如下的8种操作来完成的,每种操作必须是原子性的(double和long类型在某些平台有例外,参考volatile详解和非原子性协定)
java虚拟机中主内存和工作内存交互,就是一个变量如何从主内存传输到工作内存中,如何把修改后的变量从工作内存同步回主内存。 lock(锁定): 作用于主内存的变量,一个变量在同一时间只能一个线程锁定,该操作表示这条线成独占这个变量 unlock(解锁): 作用于主内存的变量,表示这个变量的状态由处于锁定状态被释放,这样其他线程才能对该变量进行锁定 read(读取): 作用于主内存变量,表示把一个主内存变量的值传输到线程的工作内存,以便随后的load操作使用 load(载入): 作用于线程的工作内存的变量,表示把read操作从主内存中读取的变量的值放到工作内存的变量副本中(副本是相对于主内存的变量而言的) use(使用): 作用于线程的工作内存中的变量,表示把工作内存中的一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时就会执行该操作 assign(赋值): 作用于线程的工作内存的变量,表示把执行引擎返回的结果赋值给工作内存中的变量,每当虚拟机遇到一个给变量赋值的字节码指令时就会执行该操作 store(存储): 作用于线程的工作内存中的变量,把工作内存中的一个变量的值传递给主内存,以便随后的write操作使用 write(写入): 作用于主内存的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中
如果要把一个变量从主内存传输到工作内存,那就要顺序的执行read和load操作,如果要把一个变量从工作内存回写到主内存,就要顺序的执行store和write操作。对于普通变量,虚拟机只是要求顺序的执行,并没有要求连续的执行,所以如下也是正确的。对于两个线程,分别从主内存中读取变量a和b的值,并不一样要read a; load a; read b; load b; 也会出现如下执行顺序:read a; read b; load b; load a; (对于volatile修饰的变量会有一些其他规则,后边会详细列出),对于这8中操作,虚拟机也规定了一系列规则,在执行这8中操作的时候必须遵循如下的规则: 不允许read和load、store和write操作之一单独出现,也就是不允许从主内存读取了变量的值但是工作内存不接收的情况,或者不允许从工作内存将变量的值回写到主内存但是主内存不接收的情况 不允许一个线程丢弃最近的assign操作,也就是不允许线程在自己的工作线程中修改了变量的值却不同步/回写到主内存 不允许一个线程回写没有修改的变量到主内存,也就是如果线程工作内存中变量没有发生过任何assign操作,是不允许将该变量的值回写到主内存 变量只能在主内存中产生,不允许在工作内存中直接使用一个未被初始化的变量,也就是没有执行load或者assign操作。也就是说在执行use、store之前必须对相同的变量执行了load、assign操作 一个变量在同一时刻只能被一个线程对其进行lock操作,也就是说一个线程一旦对一个变量加锁后,在该线程没有释放掉锁之前,其他线程是不能对其加锁的,但是同一个线程对一个变量加锁后,可以继续加锁,同时在释放锁的时候释放锁次数必须和加锁次数相同。 对变量执行lock操作,就会清空工作空间该变量的值,执行引擎使用这个变量之前,需要重新load或者assign操作初始化变量的值 不允许对没有lock的变量执行unlock操作,如果一个变量没有被lock操作,那也不能对其执行unlock操作,当然一个线程也不能对被其他线程lock的变量执行unlock操作 对一个变量执行unlock之前,必须先把变量同步回主内存中,也就是执行store和write操作
当然,最重要的还是如开始所说,这8个动作必须是原子的,不可分割的。 针对volatile修饰的变量,会有一些特殊规定。
Volatile修饰的变量的特殊规则
关键字volatile可以说是java虚拟机中提供的最轻量级的同步机制。java内存模型对volatile专门定义了一些特殊的访问规则。这些规则有些晦涩拗口,先列出规则,然后用更加通俗易懂的语言来解释:
假定T表示一个线程,V和W分别表示两个volatile修饰的变量,那么在进行read、load、use、assign、store和write操作的时候需要满足如下规则: 只有当线程T对变量V执行的前一个动作是load,线程T对变量V才能执行use动作;同时只有当线程T对变量V执行的后一个动作是use的时候线程T对变量V才能执行load操作。所以,线程T对变量V的use动作和线程T对变量V的read、load动作相关联,必须是连续一起出现。也就是在线程T的工作内存中,每次使用变量V之前必须从主内存去重新获取最新的值,用于保证线程T能看得见其他线程对变量V的最新的修改后的值。 只有当线程T对变量V执行的前一个动作是assign的时候,线程T对变量V才能执行store动作;同时只有当线程T对变量V执行的后一个动作是store的时候,线程T对变量V才能执行assign动作。所以,线程T对变量V的assign操作和线程T对变量V的store、write动作相关联,必须一起连续出现。也即是在线程T的工作内存中,每次修改变量V之后必须立刻同步回主内存,用于保证线程T对变量V的修改能立刻被其他线程看到。 假定动作A是线程T对变量V实施的use或assign动作,动作F是和动作A相关联的load或store动作,动作P是和动作F相对应的对变量V的read或write动作;类似的,假定动作B是线程T对变量W实施的use或assign动作,动作G是和动作B相关联的load或store动作,动作Q是和动作G相对应的对变量W的read或write动作。如果动作A先于B,那么P先于Q。也就是说在同一个线程内部,被volatile修饰的变量不会被指令重排序,保证代码的执行顺序和程序的顺序相同。
总结上面三条规则,前面两条可以概括为:Volatile类型的变量保证对所有线程的可见性。第三条为:*Volatile类型的变量禁止指令重排序优化。 valatile类型的变量保证对所有线程的可见性
可见性是指当一个线程修改了这个变量的值,新值(修改后的值)对于其他线程来说是立即可以得知的。正如上面的前两条规则规定,volatile类型的变量每次值被修改了就立即同步回主内存,每次使用时就需要从主内存重新读取值。返回到前面对普通变量的规则中,并没有要求这一点,所以普通变量的值是不会立即对所有线程可见的。
误解 :volatile变量对所有线程是立即可见的,所以对volatile变量的所有修改(写操作)都立刻能反应到其他线程中。或者换句话说:volatile变量在各个线程中是一致的,所以基于volatile变量的运算在并发下是线程安全的。
这个观点的论据是正确的,但是根据论据得出的结论是错误的,并不能得出这样的结论。
volatile的规则,保证了read、load、use的顺序和连续行,同理assign、store、write也是顺序和连续的。也就是这几个动作是原子性的,但是对变量的修改,或者对变量的运算,却不能保证是原子性的。如果对变量的修改是分为多个步骤的,那么多个线程同时从主内存拿到的值是最新的,但是经过多步运算后回写到主内存的值是有可能存在覆盖情况发生的。如下代码的例子: public class VolatileTest { public static volatile int race = 0; public static void increase() { race++ } private static final int THREADS_COUNT = 20; public void static main(String[] args) { Thread[] threads = new Thread[THREADS_COUNT); for (int = 0; i < THREADS_COUNT; i++) { threads[i] = new Thread(new Runnable(){ @Override public void run() { for (int j = 0; j < 10000; j++) { increase(); } } }); threads[i].start(); } while (Thread.activeCount() > 1) { Thread.yield(); } System.out.println(race); } }
代码就是对volatile类型的变量启动了20个线程,每个线程对变量执行1w次加1操作,如果volatile变量并发操作没有问题的话,那么结果应该是输出20w,但是结果运行的时候每次都是小于20w,这就是因为race++操作不是原子性的,是分多个步骤完成的。假设两个线程a、b同时取到了主内存的值,是0,这是没有问题的,在进行++操作的时候假设线程a执行到一半,线程b执行完了,这时线程b立即同步给了主内存,主内存的值为1,而线程a此时也执行完了,同步给了主内存,此时的值仍然是1,线程b的结果被覆盖掉了。 volatile变量禁止指令重排序优化
普通的变量仅仅会保证在该方法执行的过程中,所有依赖赋值结果的地方都能获取到正确的结果,但不能保证变量赋值的操作顺序和程序代码的顺序一致。因为在一个线程的方法执行过程中无法感知到这一点,这也就是java内存模型中描述的所谓的“线程内部表现为串行的语义”。
也就是在单线程内部,我们看到的或者感知到的结果和代码顺序是一致的,即使代码的执行顺序和代码顺序不一致,但是在需要赋值的时候结果也是正确的,所以看起来就是串行的。但实际结果有可能代码的执行顺序和代码顺序是不一致的。这在多线程中就会出现问题。 看下面的伪代码举例: Map configOptions; char[] configText; //volatile类型bianliang volatile boolean initialized = false; //假设以下代码在线程A中执行 //模拟读取配置信息,读取完成后认为是初始化完成 configOptions = new HashMap(); configText = readConfigFile(fileName); processConfigOptions(configText, configOptions); initialized = true; //假设以下代码在线程B中执行 //等待initialized为true后,读取配置信息进行操作 while ( !initialized) { sleep(); } doSomethingWithConfig();
如果initialiezd是普通变量,没有被volatile修饰,那么线程A执行的代码的修改初始化完成的结果initialized = true就有可能先于之前的三行代码执行,而此时线程B发现initialized为true了,就执行doSomethingWithConfig()方法,但是里面的配置信息都是null的,就会出现问题了。
现在initialized是volatile类型变量,保证禁止代码重排序优化,那么就可以保证initialized = true执行的时候,前边的三行代码一定执行完成了,那么线程B读取的配置文件信息就是正确的。
跟其他保证并发安全的工具相比,volatile的性能确实会好一些。在某些情况下,volatile的同步机制性能要优于锁(使用synchronized关键字或者java.util.concurrent包中的锁)。但是现在由于虚拟机对锁的不断优化和实行的许多消除动作,很难有一个量化的比较。
与自己相比,就可以确定一个原则:volatile变量的读操作和普通变量的读操作几乎没有差异,但是写操作会性能差一些,慢一些,因为要在本地代码中插入许多内存屏障指令来禁止指令重排序,保证处理器不发生代码乱序执行行为。
long和double变量的特殊规则
Java内存模型要求对主内存和工作内存交换的八个动作是原子的,正如章节开头所讲,对long和double有一些特殊规则。八个动作中lock、unlock、read、load、use、assign、store、write对待32位的基本数据类型都是原子操作,对待long和double这两个64位的数据,java虚拟机规范对java内存模型的规定中特别定义了一条相对宽松的规则:允许虚拟机将没有被volatile修饰的64位数据的读写操作划分为两次32位的操作来进行,也就是允许虚拟机不保证对64位数据的read、load、store和write这4个动作的操作是原子的。这也就是我们常说的long和double的非原子性协定(Nonautomic Treatment of double and long Variables)。
并发内存模型的实质
Java内存模型围绕着并发过程中如何处理原子性、可见性和顺序性这三个特征来设计的。
原子性(Automicity)
由Java内存模型来直接保证原子性的变量操作包括read、load、use、assign、store、write这6个动作,虽然存在long和double的特例,但基本可以忽律不计,目前虚拟机基本都对其实现了原子性。如果需要更大范围的控制,lock和unlock也可以满足需求。lock和unlock虽然没有被虚拟机直接开给用户使用,但是提供了字节码层次的指令monitorenter和monitorexit对应这两个操作,对应到java代码就是synchronized关键字,因此在synchronized块之间的代码都具有原子性。
可见性
有序性从不同的角度来看是不同的。单纯单线程来看都是有序的,但到了多线程就会跟我们预想的不一样。可以这么说:如果在本线程内部观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有的操作都是无序的。前半句说的就是“线程内表现为串行的语义”,后半句值得是“指令重排序”现象和主内存与工作内存之间同步存在延迟的现象。
保证有序性的关键字有volatile和synchronized,volatile禁止了指令重排序,而synchronized则由“一个变量在同一时刻只能被一个线程对其进行lock操作”来保证。
总体来看,synchronized对三种特性都有支持,虽然简单,但是如果无控制的滥用对性能就会产生较大影响。
先行发生原则
如果Java内存模型中所有的有序性都要依靠volatile和synchronized来实现,那是不是非常繁琐。Java语言中有一个“先行发生原则”,是判断数据是否存在竞争、线程是否安全的主要依据。
什么是先行发生原则
先行发生原则是Java内存模型中定义的两个操作之间的偏序关系。比如说操作A先行发生于操作B,那么在B操作发生之前,A操作产生的“影响”都会被操作B感知到。这里的影响是指修改了内存中的共享变量、发送了消息、调用了方法等。个人觉得更直白一些就是有可能对操作B的结果有影响的都会被B感知到,对B操作的结果没有影响的是否感知到没有太大关系。
Java内存模型自带先行发生原则有哪些 程序次序原则 在一个线程内部,按照代码的顺序,书写在前面的先行发生与后边的。或者更准确的说是在控制流顺序前面的先行发生与控制流后面的,而不是代码顺序,因为会有分支、跳转、循环等。 管程锁定规则 一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须注意的是对同一个锁,后面是指时间上的后面 volatile变量规则 对一个volatile变量的写操作先行发生与后面对这个变量的读操作,这里的后面是指时间上的先后顺序 线程启动规则 Thread对象的start()方法先行发生与该线程的每个动作。当然如果你错误的使用了线程,创建线程后没有执行start方法,而是执行run方法,那此句话是不成立的,但是如果这样其实也不是线程了 线程终止规则 线程中的所有操作都先行发生与对此线程的终止检测,可以通过Thread.join()和Thread.isAlive()的返回值等手段检测线程是否已经终止执行 线程中断规则 对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测到是否有中断发生。 对象终结规则 一个对象的初始化完成先行发生于他的finalize方法的执行,也就是初始化方法先行发生于finalize方法 传递性 如果操作A先行发生于操作B,操作B先行发生于操作C,那么操作A先行发生于操作C。 看一个例子: private int value = 0; public void setValue(int value) { this.value = value; } public int getValue() { return this.value; }
如果有两个线程A和B,A先调用setValue方法,然后B调用getValue方法,那么B线程执行方法返回的结果是什么?
我们去对照先行发生原则一个一个对比。首先是程序次序规则,这里是多线程,不在一个线程中,不适用;然后是管程锁定规则,这里没有synchronized,自然不会发生lock和unlock,不适用;后面对于线程启动规则、线程终止规则、线程中断规则也不适用,这里与对象终结规则、传递性规则也没有关系。所以说B返回的结果是不确定的,也就是说在多线程环境下该操作不是线程安全的。
如何修改呢,一个是对get/set方法加入synchronized 关键字,可以使用管程锁定规则;要么对value加volatile修饰,可以使用volatile变量规则。
通过上面的例子可知,一个操作时间上先发生并不代表这个操作先行发生,那么一个操作先行发生是不是代表这个操作在时间上先发生?也不是,如下面的例子: int i = 2; int j = 1;
在同一个线程内,对i的赋值先行发生于对j赋值的操作,但是代码重排序优化,也有可能是j的赋值先发生,我们无法感知到这一变化。
所以,综上所述,时间先后顺序与先行发生原则之间基本没有太大关系。我们衡量并发安全的问题的时候不要受到时间先后顺序的干扰,一切以先行发生原则为准。
作者:_fan凡
https://www.jianshu.com/p/15106e9c4bf3
欢迎关注我的微信公众号「码农突围」,分享Python、Java、大数据、机器学习、人工智能等技术,关注码农技术提升•职场突围•思维跃迁,20万+码农成长充电第一站,陪有梦想的你一起成长
软件开发
2020-07-24 19:27:00
作者:leapMie 的博客 https://blog.leapmie.com/archives/418/
随着 HTTPS 建站的成本下降,现在大部分的网站都已经开始用上 HTTPS 协议。大家都知道 HTTPS 比 HTTP 安全,也听说过与 HTTPS 协议相关的概念有 SSL 、非对称加密、 CA证书等,但对于以下灵魂三拷问可能就答不上了: 为什么用了 HTTPS 就是安全的? HTTPS 的底层原理如何实现? 用了 HTTPS 就一定安全吗?
本文将层层深入,从原理上把 HTTPS 的安全性讲透。
HTTPS 的实现原理
大家可能都听说过 HTTPS 协议之所以是安全的是因为 HTTPS 协议会对传输的数据进行加密,而加密过程是使用了非对称加密实现。但其实,HTTPS在内容传输的加密上使用的是对称加密,非对称加密只作用在证书验证阶段。
HTTPS的整体过程分为证书验证和数据传输阶段,具体的交互过程如下:
① 证书验证阶段 浏览器发起 HTTPS 请求 服务端返回 HTTPS 证书 客户端验证证书是否合法,如果不合法则提示告警
② 数据传输阶段 当证书验证合法后,在本地生成随机数 通过公钥加密随机数,并把加密后的随机数传输到服务端 服务端通过私钥对随机数进行解密 服务端通过客户端传入的随机数构造对称加密算法,对返回结果内容进行加密后传输
为什么数据传输是用对称加密?
首先,非对称加密的加解密效率是非常低的,而 http 的应用场景中通常端与端之间存在大量的交互,非对称加密的效率是无法接受的;
另外,在 HTTPS 的场景中只有服务端保存了私钥,一对公私钥只能实现单向的加解密,所以 HTTPS 中内容传输加密采取的是对称加密,而不是非对称加密。
为什么需要 CA 认证机构颁发证书?
HTTP 协议被认为不安全是因为传输过程容易被监听者勾线监听、伪造服务器,而 HTTPS 协议主要解决的便是网络传输的安全性问题。
首先我们假设不存在认证机构,任何人都可以制作证书,这带来的安全风险便是经典的 “中间人攻击” 问题。
“中间人攻击”的具体过程如下:
过程原理: 本地请求被劫持(如DNS劫持等),所有请求均发送到中间人的服务器 中间人服务器返回中间人自己的证书 客户端创建随机数,通过中间人证书的公钥对随机数加密后传送给中间人,然后凭随机数构造对称加密对传输内容进行加密传输 中间人因为拥有客户端的随机数,可以通过对称加密算法进行内容解密 中间人以客户端的请求内容再向正规网站发起请求 因为中间人与服务器的通信过程是合法的,正规网站通过建立的安全通道返回加密后的数据 中间人凭借与正规网站建立的对称加密算法对内容进行解密 中间人通过与客户端建立的对称加密算法对正规内容返回的数据进行加密传输 客户端通过与中间人建立的对称加密算法对返回结果数据进行解密
由于缺少对证书的验证,所以客户端虽然发起的是 HTTPS 请求,但客户端完全不知道自己的网络已被拦截,传输内容被中间人全部窃取。关于 HTTPS,大家关注微信公众号Java技术栈可以获取更多干货。
浏览器是如何确保 CA 证书的合法性?
1. 证书包含什么信息? 颁发机构信息 公钥 公司信息 域名 有效期 指纹 ……
2. 证书的合法性依据是什么?
首先,权威机构是要有认证的,不是随便一个机构都有资格颁发证书,不然也不叫做权威机构。
另外,证书的可信性基于信任制,权威机构需要对其颁发的证书进行信用背书,只要是权威机构生成的证书,我们就认为是合法的。
所以权威机构会对申请者的信息进行审核,不同等级的权威机构对审核的要求也不一样,于是证书也分为免费的、便宜的和贵的。
3. 浏览器如何验证证书的合法性?
浏览器发起 HTTPS 请求时,服务器会返回网站的 SSL 证书,浏览器需要对证书做以下验证: 验证域名、有效期等信息是否正确。证书上都有包含这些信息,比较容易完成验证; 判断证书来源是否合法。每份签发证书都可以根据验证链查找到对应的根证书,操作系统、浏览器会在本地存储权威机构的根证书,利用本地根证书可以对对应机构签发证书完成来源验证;
判断证书是否被篡改。需要与 CA 服务器进行校验; 判断证书是否已吊销。通过CRL(Certificate Revocation List 证书注销列表)和 OCSP(Online Certificate Status Protocol 在线证书状态协议)实现,其中 OCSP 可用于第3步中以减少与 CA 服务器的交互,提高验证效率
以上任意一步都满足的情况下浏览器才认为证书是合法的。
**这里插一个我想了很久的但其实答案很简单的问题: **
既然证书是公开的,如果要发起中间人攻击,我在官网上下载一份证书作为我的服务器证书,那客户端肯定会认同这个证书是合法的,如何避免这种证书冒用的情况?
其实这就是非加密对称中公私钥的用处,虽然中间人可以得到证书,但私钥是无法获取的,一份公钥是不可能推算出其对应的私钥,中间人即使拿到证书也无法伪装成合法服务端,因为无法对客户端传入的加密数据进行解密。
4. 只有认证机构可以生成证书吗?
如果需要浏览器不提示安全风险,那只能使用认证机构签发的证书。但浏览器通常只是提示安全风险,并不限制网站不能访问,所以从技术上谁都可以生成证书,只要有证书就可以完成网站的HTTPS 传输。例如早期的 12306 采用的便是手动安装私有证书的形式实现 HTTPS 访问。
本地随机数被窃取怎么办?
证书验证是采用非对称加密实现,但是传输过程是采用对称加密,而其中对称加密算法中重要的随机数是由本地生成并且存储于本地的,HTTPS如何保证随机数不会被窃取?
其实 HTTPS 并不包含对随机数的安全保证,HTTPS保证的只是传输过程安全,而随机数存储于本地,本地的安全属于另一安全范畴,应对的措施有安装杀毒软件、反木马、浏览器升级修复漏洞等。
用了 HTTPS 会被抓包吗?
HTTPS 的数据是加密的,常规下抓包工具代理请求后抓到的包内容是加密状态,无法直接查看。
但是,正如前文所说,浏览器只会提示安全风险,如果用户授权仍然可以继续访问网站,完成请求。因此,只要客户端是我们自己的终端,我们授权的情况下,便可以组建中间人网络,而抓包工具便是作为中间人的代理。
通常HTTPS抓包工具的使用方法是会生成一个证书,用户需要手动把证书安装到客户端中,然后终端发起的所有请求通过该证书完成与抓包工具的交互,然后抓包工具再转发请求到服务器,最后把服务器返回的结果在控制台输出后再返回给终端,从而完成整个请求的闭环。
**既然 HTTPS 不能防抓包,那 HTTPS 有什么意义? **
HTTPS可以防止用户在不知情的情况下通信链路被监听,对于主动授信的抓包操作是不提供防护的,因为这个场景用户是已经对风险知情。
要防止被抓包,需要采用应用级的安全防护,例如采用私有的对称加密,同时做好移动端的防反编译加固,防止本地算法被破解。
总结
以下用简短的Q&A形式进行全文总结:
**Q: HTTPS 为什么安全?
**A: 因为 HTTPS 保证了传输安全,防止传输过程被监听、防止数据被窃取,可以确认网站的真实性。
**Q: HTTPS 的传输过程是怎样的?
**A: 客户端发起 HTTPS请求,服务端返回证书,客户端对证书进行验证,验证通过后本地生成用于改造对称加密算法的随机数,通过证书中的公钥对随机数进行加密传输到服务端,服务端接收后通过私钥解密得到随机数,之后的数据交互通过对称加密算法进行加解密。
**Q: 为什么需要证书?
**A: 防止”中间人“攻击,同时可以为网站提供身份证明。
**Q: 使用 HTTPS 会被抓包吗?
**A: 会被抓包,HTTPS 只防止用户在不知情的情况下通信被监听,如果用户主动授信,是可以构建“中间人”网络,代理软件可以对传输内容进行解密。
分享一张详细的过程图:
关注公众号Java技术栈回复"面试"获取我整理的2020最全面试题及答案。
推荐去我的博客阅读更多:
1. Java JVM、集合、多线程、新特性系列教程
2. Spring MVC、Spring Boot、Spring Cloud 系列教程
3. Maven、Git、Eclipse、Intellij IDEA 系列工具教程
4. Java、后端、架构、阿里巴巴等大厂最新面试题
觉得不错,别忘了点赞+转发哦!
软件开发
2020-07-24 16:26:00
对于学习前端开发有前途吗?行情怎么样,好就业吗?这样的问题相信都看了很多很多,每个人的回答都有些差别。但是唯一的一点肯定的,学习前端的前景是很不错的。
接下来,来跟大家分享一下2020年Web前端的发展趋势如何?熟悉web的小伙伴们都了解,自2018年是前端技术的发展相对稳定的一年,就前端主流技术框架的发展而言,前几年里发展极快,在填补原有技术框架空白和不足的同时也渐渐趋于成熟。 未来前端在已经趋向成熟的技术方向上面将会慢慢稳定下来,并进入迭代优化阶段,例如语言标准、前端框架等。 那么Web前端的发展趋势如何?让我们一起来看一看吧~
1.新规范的更新与稳定
前端新标准和草案在不断更新,HTML、CSS、Javascript标准也在渐渐完善,尽管这些新的规范最终会淘汰旧的规范,新的项目也会以最新的标准作为开发依据,但要完全废弃旧规范,还需要一段时间。
目前前端三层结构实现已经形成了HTML5、CSS3、《Ecmascript 6》标准规范,后面的新变化也会越来越小,这就表明了目前前端项目实践规范将会相对稳定一段较长的时间,后面的修改不会像之前一样具有颠覆性。
2.框架技术趋于稳定
从前端应用开发框架上来看,先后经历了DOM API、MVC、MVP、MVVM、Virtual DOM、MNV*阶段,逐步解决了前端开发效率、设计模式、DOM交互性能的问题。 这些问题处理完成后,相关的框架也会进入稳定、有序迭代的时期。
即使有新的框架出来,只要你能打好前端基础,上手任何框架都不成问题。这就是为什么有的人学得快,有的人越学越迷茫!原因就是基础更扎实。
前端的交互框架不会像以前那样变化频繁,相对于之前的频繁更换到现在主流框架的稳定升级。
3.技术工具的探索
前端技术效率和性能的提升当然不是仅靠前端框架都能解决的,还需要其他各方面辅助工具的支持,例如高效的调试工具、构建自动化工具、自动发布部署工具等。
所以未来前端发展过程中,各种高效工具的探索仍会不断地出现,来解决特定场景下的问题。
4.浏览器的新特性
就浏览器端应用而言,以Chrome为代表的浏览器版本和特性发展迭代极其迅速,经过多版本的迭代,浏览器上已经可以实现较多的增强和实用特性,例如:Web Component,Service Worker,IndexDB,WebAssembly,WebRTC、《Ecmascript 6》的支持等等。
但由于浏览器的种类和版本多,我们还不能在业务中直接推广使用这些新的特性,但这些却仍然给了我们很多的可能性,并且未来较多技术也会在这些新特性的基础上优化或改进产生。
5.前端技术开发生态
贯穿浏览器、服务端和移动端,前端正朝着多端、多技术实现的方向发展。这意味着前端这套技术栈能做的事情可能更多,涉及的平台更广。
但作为整套技术开发生态的一部分,每一项技术的出现都必不可少的要去考虑开发效率、维护成本、性能、扩展性这几个方面的问题,所以寻找新的开发生态体系仍是前端未来的大方向。
6.前端新领域的出现
除了目前浏览器、服务器、移动端上的应用开发技术变革和探索外,未来前端也会出现新的应用场景。
例如VR、物联网Web化、Web人工智能等。 这些虽然听着比较远,但一旦到来就会很快被使用,所以前端不仅自身发展快,推广使用也极其迅速,例如移动互联网Web的普及也就两三年时间。
以上是我对前端开发前途和行情的分享,希望对你有所帮助,另外如果你这边对于前端还有什么问题或者想了解的知识,可以私信我,自己也有整理一下关于前端的资料和视频,以及学习路线。 这里推荐一下我的前端学习Q裙:784783012 ,里面都是学习前端的,从最基础的HTML+CSS+JS炫酷特效,web开发,插件封装,设计模式到移动端HTML5的项目实战的学习资料都有整理,2020最新技术,与企业需求同步,每天都会有大牛定时讲解前端技术! 致力打造从零基础小白到月薪上万的前端大神之路
学习前端,我们是认真的
软件开发
2020-07-24 15:22:00
Mybatis学习(一):基础概念和简单自定义持久层框架demo 一、一个简单自定义持久层框架demo 1.1 普通的JDBC连接数据库 1.2 对jdbc存在的问题分析和解决 1.3 简单设计一个持久层框架 1.4 简单测试
一、一个简单自定义持久层框架demo
1.1 普通的JDBC连接数据库 public class Test { public static void main(String[] args) { Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { //加载数据库驱动 Class.forName("com.mysql.jdbc.Driver"); //通过驱动管理类获取连接 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis", "root", "root"); //定义SQL语句,其中?表示占位符 String sql = "select * from user where name = ?"; preparedStatement = connection.prepareStatement(sql); //设置参数,参数是从1开始 preparedStatement.setString(1, "zhangsan"); //执行SQL查询数据库返回查询结果集 resultSet = preparedStatement.executeQuery(); //遍历查询结果集 while (resultSet.next()) { int id = resultSet.getInt("id"); String name = resultSet.getString("name"); User user = new User(); user.setId(id); user.setName(name); System.out.println(user); } } catch (Exception e) { e.printStackTrace(); } finally { if (resultSet != null) { try { resultSet.close(); } catch (SQLException sqlException) { sqlException.printStackTrace(); } } try { if (preparedStatement != null) { preparedStatement.close(); } } catch (SQLException sqlException) { sqlException.printStackTrace(); } try { if (connection != null) { connection.close(); } } catch (SQLException sqlException) { sqlException.printStackTrace(); } } } }
1.2 对jdbc存在的问题分析和解决
从上边1.1的手写JDBC连接可以看出: 频繁创建和释放数据库连接,性能损失; SQL语句是写死的,在实际项目中是会经常变化的; preparedStatement传入参数时也有硬编码的存在,不易维护; 从resultSet获取结果信息封装为Java某个对象时也存在硬编码问题;
我们可以用以下方法解决上述问题: 采用连接池的方式避免频繁创建、释放数据库连接; 对硬编码的问题我们可以用配置文件的方式去替代; 用反射及内省方法完成resultSet结果集到Java对象的封装;
1.3 简单设计一个持久层框架
用户端:引入自定义框架的jar包。
(1)提供一个Mapper.xml存储关于SQL的配置信息文件;
namespace.id来组成statementId唯一标识SQL语句
(2)提供一个MapperConfig.xml存储关于数据源等配置信息文件,引入Mapper.xml;
框架端:
新建一个maven项目,引入相关依赖。 4.0.0 com.customize customizeOrm 1.0-SNAPSHOT UTF-8 UTF-8 1.8 1.8 1.8 mysql mysql-connector-java 8.0.20 c3p0 c3p0 0.9.1.2 log4j log4j 1.2.17 dom4j dom4j 1.6.1 jaxen jaxen 1.1.6 junit junit 4.12 test org.apache.maven.plugins maven-compiler-plugin 8 8 加载配置文件
       配置文件以流的形式被读取出来,放在内存中不好操作,我们创建bean对象的方式来存储。
(1) 创建Resources类,根据配置文件的路径,加载配置文件成字节输入流存在内存中。 import java.io.InputStream; public class Resources { public static InputStream getResourcesAsStream(String path){ return Resources.class.getResourceAsStream(path); } }
(2) 创建Configuration对象存MapperConfig.xml解析到的内容; package com.customer.config; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; public class Configuration { /** * 数据源信息 */ private DataSource dataSource; /** * mapper的相关信息 */ private Map map = new HashMap<>(); public DataSource getDataSource() { return dataSource; } public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } public Map getMap() { return map; } public void setMap(Map map) { this.map = map; } }
(3) 创建MappedStatement对象存mapper.xml解析出来的sql信息、statement类型、输入参数类型、输出参数类型。 package com.customer.config; public class MappedStatement { /** * id */ private String id; /** * SQL语句 */ private String sql; /** * 输入参数 */ private Class paramterType; /** * 输出参数 */ private Class resultType; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getSql() { return sql; } public void setSql(String sql) { this.sql = sql; } public Class getParamterType() { return paramterType; } public void setParamterType(Class paramterType) { this.paramterType = paramterType; } public Class getResultType() { return resultType; } public void setResultType(Class resultType) { this.resultType = resultType; } } 解析配置文件(dom4j的方式)
(1)创建SqlSessionFactoryBuilder类:将解析出来的配置文件信息存在上边的容器对象中;创建SqlSessionFactory类,用来生产SqlSession对象(会话对象) 创建XMLConfigerBuilder类解析config配置文件,并将解析结果注入Configuration中: package com.customer.io; import com.customer.config.Configuration; import com.customer.config.Resources; import com.mchange.v2.c3p0.ComboPooledDataSource; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import javax.naming.NamingException; import java.beans.PropertyVetoException; import java.io.InputStream; import java.util.List; import java.util.Properties; public class XMLConfigerBuilder { private Configuration configuration; public XMLConfigerBuilder(Configuration configuration) { this.configuration = configuration; } public Configuration parseConfiguration(InputStream inputStream) throws DocumentException, PropertyVetoException, NamingException, ClassNotFoundException { Document document = new SAXReader().read(inputStream); //取得标签 Element rootElement = document.getRootElement(); Properties properties = new Properties(); List propertyNodes = rootElement.selectNodes("//property"); for (Element propertyNode : propertyNodes) { String name = propertyNode.attributeValue("name"); String value = propertyNode.attributeValue("value"); properties.setProperty(name,value); } //连接池 ComboPooledDataSource dataSource = new ComboPooledDataSource(); dataSource.setDriverClass(properties.getProperty("driverClass")); dataSource.setJdbcUrl(properties.getProperty("jdbcUrl")); dataSource.setUser(properties.getProperty("user")); dataSource.setPassword(properties.getProperty("password")); //将连接池放入configuration对象中 configuration.setDataSource(dataSource); //利用XMLMapperBuilder解析mapper List mapperNodes = rootElement.selectNodes("//mapper"); XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration); for (Element mapperElement : mapperNodes) { String path = mapperElement.attributeValue("resource"); xmlMapperBuilder.parseMapper(Resources.getResourcesAsStream(path)); } return configuration; } } 创建XMLMapperBuilder类解析mapper.xml文件,将解析到的信息封装到MappedStatement对象并注入Configuration中; 在这里插入代码片 创建SqlSessionFactory接口类及默认实现DefaultSqlSessionFactory,生产SqlSession对象。 package com.customer.io; import com.customer.config.Configuration; import com.customer.config.MappedStatement; import org.dom4j.Document; import org.dom4j.DocumentException; import org.dom4j.Element; import org.dom4j.io.SAXReader; import java.io.InputStream; import java.util.List; public class XMLMapperBuilder { private Configuration configuration; public XMLMapperBuilder(Configuration configuration) { this.configuration = configuration; } public Configuration parseMapper(InputStream inputStream) throws DocumentException, ClassNotFoundException { Document document = new SAXReader().read(inputStream); Element rootElement = document.getRootElement(); String namespace = rootElement.attributeValue("namespace"); List selectElements = rootElement.selectNodes("select"); for (Element selectElement : selectElements) { String id = selectElement.attributeValue("id"); //入参类型 String paramterType = selectElement.attributeValue("paramterType"); //返回类型 String resultType = selectElement.attributeValue("resultType"); //sql语句 String sql = selectElement.getTextTrim(); //唯一标识 namespace+id String key = namespace + id; //将解析到的SQL信息封装到MappedStatement对象中 MappedStatement mappedStatement = new MappedStatement(); mappedStatement.setId(id); mappedStatement.setSql(sql); mappedStatement.setParamterType(getClassTypeByStr(paramterType)); mappedStatement.setResultType(getClassTypeByStr(resultType)); //将封装好的MappedStatement对象注入Configuration对象中 configuration.getMap().put(key,mappedStatement); } return configuration; } private Class getClassTypeByStr(String typeStr) throws ClassNotFoundException { return Class.forName(typeStr); } } 创建SqlSessionFactoryBuilder类生成SqlSessionFactory package com.customer.sqlsession; import com.customer.config.Configuration; import com.customer.io.XMLConfigerBuilder; import org.dom4j.DocumentException; import javax.naming.NamingException; import java.beans.PropertyVetoException; import java.io.InputStream; public class SqlSessionFactoryBuilder { private Configuration configuration; public SqlSessionFactoryBuilder() { configuration = new Configuration(); } public SqlSessionFactory build(InputStream inputStream) throws ClassNotFoundException, PropertyVetoException, DocumentException, NamingException { //1.解析配置文件得到Configuration对象 XMLConfigerBuilder xmlConfigerBuilder = new XMLConfigerBuilder(configuration); xmlConfigerBuilder.parseConfiguration(inputStream); //2.创建SqlSessionFactory return new DefaultSqlSessionFactory(configuration); } } 创建SqlSessionFactory接口和其默认实现类DefaultSqlSessionFactory,主要负责生产SqlSession package com.customer.sqlsession; public interface SqlSessionFactory { public SqlSession openSession(); } package com.customer.sqlsession; import com.customer.config.Configuration; public class DefaultSqlSessionFactory implements SqlSessionFactory{ private Configuration configuration; public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; } @Override public SqlSession openSession() { return new DefaultSqlSession(configuration); } } 创建SqlSession接口及其默认实现类DefaultSqlSession,定义对数据库的操作:查询、新增、修改、删除 创建SqlSession接口,定义对数据库的操作:查询、新增、修改、删除 package com.customer.sqlsession; import java.util.List; public interface SqlSession { List selectList(String statementId, Object... param); T selectOne(String statementId, Object... param); } 创建SqlSession接口的默认实现类DefaultSqlSession package com.customer.sqlsession; import com.customer.config.Configuration; import com.customer.config.MappedStatement; import com.customer.executor.Executor; import com.customer.executor.SimpleExecutor; import java.util.List; public class DefaultSqlSession implements SqlSession { private Configuration configuration; public DefaultSqlSession(Configuration configuration) { this.configuration = configuration; } private Executor simpleExecutor = new SimpleExecutor(); @Override public List selectList(String statementId, Object... param) { MappedStatement mappedStatement = configuration.getMap().get(statementId); return simpleExecutor.query(configuration, mappedStatement, param); } @Override public T selectOne(String statementId, Object... param) { List objects = selectList(statementId, param); if (objects.size() > 1) { throw new RuntimeException("Too many returns !"); } return (T) objects.get(0); } } 创建Executor接口及其实现类SimpleExecutor,定义query()方法执行jdbc。 创建Executor接口,定义query()方法 package com.customer.executor; import com.customer.config.Configuration; import com.customer.config.MappedStatement; import java.util.List; public interface Executor { List query(Configuration configuration, MappedStatement mappedStatement, Object[] param); } 创建Executor接口的实现类SimpleExecutor package com.customer.executor; import com.customer.config.Configuration; import com.customer.config.MappedStatement; import com.customer.util.GenericTokenParser; import com.customer.util.ParameterMapping; import com.customer.util.ParameterMappingTokenHandler; import java.beans.IntrospectionException; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class SimpleExecutor implements Executor { private Connection connection = null; @Override public List query(Configuration configuration, MappedStatement mappedStatement, Object[] param) throws SQLException, NoSuchFieldException, IllegalAccessException, InstantiationException, IntrospectionException, InvocationTargetException { //1.获取数据库连接 connection = configuration.getDataSource().getConnection(); //2.获取要执行的SQL语句 String sql = mappedStatement.getSql(); //3.对sql进行预编译处理 BoundSql boundSql = getBoundSql(sql); //4.获取预编译对象PreparedStatement PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getParseSql()); //5.设置参数对 Class paramterType = mappedStatement.getParamterType(); List parameterMappingList = boundSql.getParameterMappingList(); for (int i = 0; i < parameterMappingList.size(); i++) { ParameterMapping parameterMapping = parameterMappingList.get(i); String content = parameterMapping.getContent(); //利用反射 Field declaredField = paramterType.getDeclaredField(content); declaredField.setAccessible(true); Object o = declaredField.get(param[0]); preparedStatement.setObject(i + 1, o); } //6.执行sql语句 ResultSet resultSet = preparedStatement.executeQuery(); //7.封装返回结果集 Class resultType = mappedStatement.getResultType(); ArrayList list = new ArrayList(); while (resultSet.next()) { Object o = resultType.newInstance(); ResultSetMetaData metaData = resultSet.getMetaData(); for (int i = 0; i < metaData.getColumnCount(); i++) { //字段名 String columnName = metaData.getColumnName(i); Object object = resultSet.getObject(columnName); PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultType); Method writeMethod = propertyDescriptor.getWriteMethod(); writeMethod.invoke(o, object); } list.add(o); } return (List) list; } private BoundSql getBoundSql(String sql) { //标记处理类:配置标记解析器来完成对占位符的解析处理工作 ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler(); GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler); //解析得到的sql String parse = genericTokenParser.parse(sql); //#{}里面解析出来的参数名称 List parameterMappings = parameterMappingTokenHandler.getParameterMappings(); return new BoundSql(parse, parameterMappings); } } 引入预编译处理工具类 package com.customer.util; public class ParameterMapping { private String content; public ParameterMapping(String content) { this.content = content; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } } package com.customer.util; /** * @author Clinton Begin */ public interface TokenHandler { String handleToken(String content); } package com.customer.util; import java.util.ArrayList; import java.util.List; public class ParameterMappingTokenHandler implements TokenHandler { private List parameterMappings = new ArrayList(); // context是参数名称 #{id} #{username} @Override public String handleToken(String content) { parameterMappings.add(buildParameterMapping(content)); return "?"; } private ParameterMapping buildParameterMapping(String content) { ParameterMapping parameterMapping = new ParameterMapping(content); return parameterMapping; } public List getParameterMappings() { return parameterMappings; } public void setParameterMappings(List parameterMappings) { this.parameterMappings = parameterMappings; } } /** * Copyright 2009-2017 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.customer.util; /** * @author Clinton Begin */ public class GenericTokenParser { private final String openToken; //开始标记 private final String closeToken; //结束标记 private final TokenHandler handler; //标记处理器 public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) { this.openToken = openToken; this.closeToken = closeToken; this.handler = handler; } /** * 解析${}和#{} * @param text * @return * 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。 * 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现 */ public String parse(String text) { // 验证参数问题,如果是null,就返回空字符串。 if (text == null || text.isEmpty()) { return ""; } // 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。 int start = text.indexOf(openToken, 0); if (start == -1) { return text; } // 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder, // text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码 char[] src = text.toCharArray(); int offset = 0; final StringBuilder builder = new StringBuilder(); StringBuilder expression = null; while (start > -1) { // 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理 if (start > 0 && src[start - 1] == '\\') { builder.append(src, offset, start - offset - 1).append(openToken); offset = start + openToken.length(); } else { //重置expression变量,避免空指针或者老数据干扰。 if (expression == null) { expression = new StringBuilder(); } else { expression.setLength(0); } builder.append(src, offset, start - offset); offset = start + openToken.length(); int end = text.indexOf(closeToken, offset); while (end > -1) {////存在结束标记时 if (end > offset && src[end - 1] == '\\') {//如果结束标记前面有转义字符时 // this close token is escaped. remove the backslash and continue. expression.append(src, offset, end - offset - 1).append(closeToken); offset = end + closeToken.length(); end = text.indexOf(closeToken, offset); } else {//不存在转义字符,即需要作为参数进行处理 expression.append(src, offset, end - offset); offset = end + closeToken.length(); break; } } if (end == -1) { // close token was not found. builder.append(src, start, src.length - start); offset = src.length; } else { //首先根据参数的key(即expression)进行参数处理,返回?作为占位符 builder.append(handler.handleToken(expression.toString())); offset = end + closeToken.length(); } } start = text.indexOf(openToken, offset); } if (offset < src.length) { builder.append(src, offset, src.length - offset); } return builder.toString(); } } 创建BoundSql类存储处理后的sql和参数对 package com.customer.executor; import com.customer.util.ParameterMapping; import java.util.List; public class BoundSql { private String parseSql; private List parameterMappingList; public BoundSql(String parseSql, List parameterMappingList) { this.parseSql = parseSql; this.parameterMappingList = parameterMappingList; } public String getParseSql() { return parseSql; } public void setParseSql(String parseSql) { this.parseSql = parseSql; } public List getParameterMappingList() { return parameterMappingList; } public void setParameterMappingList(List parameterMappingList) { this.parameterMappingList = parameterMappingList; } }
1.4 简单测试 package com.testcus.test; import com.customer.config.Resources; import com.customer.sqlsession.SqlSession; import com.customer.sqlsession.SqlSessionFactory; import com.customer.sqlsession.SqlSessionFactoryBuilder; import com.testcus.pojo.User; import org.junit.Test; import java.io.InputStream; import java.util.List; public class MyTest { @Test public void Test() throws Exception { InputStream resourceAsSteam = Resources.getResourcesAsStream("mapperConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam); SqlSession sqlSession = sqlSessionFactory.openSession(); List userList = sqlSession.selectList("user.selectList"); for (User user : userList) { System.out.println(user); } } }
软件开发
2020-07-24 10:43:03
[TOC]
0. 前言
不管是在局域网、广域网还是数据中心IDC,一个健康的网络环境,都离不开一套网管系统。
网管系统不限于如下: 监控平台:用于监控网络设备、服务器、中间件等各项指标,超过阈值触发告警通知; 日志平台:用于收集所有设备日志数据、分析、可视化,报警、追溯等功能; 自动化运维平台:用于减少重复性工作,自动化执行脚本,大大提高工作效率;
以上这几个平台,相信大家都不陌生的了,那本次先给大家介绍一款开源的监控平台。
Zabbix在2001年首次发布,并于2005年成立了一家公司,以提供专业的技术支持服务。
Zabbix 是基于B/S架构的企业级分布式开源监控软件。适用于任何IT基础架构、服务、应用程序和资源的监控如网络监控、服务器监控、云监控、应用监控以及服务监控。
Zabbix拥有可拓展性、强大的性能、易用性和极低的成本。
接下来...'It's show time'
1. 安装
1.1 准备安装环境
1.1.1 下载安装包
下载链接: #链接: https://pan.baidu.com/s/1qmMhYfpta6r4cCpyvxXKhw&shfl=sharepset 提取码:et5u #安装包 zabbix_appliance_4.2.5_x86_64.ovf.tar.gz #解压得到如下两个文件: 1)zabbix_appliance_4.2.5.ovf 2)zabbix_appliance_4.2.5-disk1.vmdk
1.1.2 修改文件配置 #打开如下文件,进行编辑 zabbix_appliance_4.2.5.ovf #删除这一行,保存,避免'virtualbox'报错 virtualbox-2.2
1.2 开始安装
1)默认配置:
2)启动,输入用户名和密码:
备注:缺省用户名及密码:appliance / zabbix 3)修改密码
备注:输入'passwd'命令,修改密码。
4)切换到root账户 appliance@zabbix:~$ sudo su root appliance@zabbix:~$ passwd //修改密码为:zabbix@123
5)临时配置IP地址 root@zabbix:~# ip addr add 192.168.0.100/24 dev ens32 root@zabbix:~# ifconfig ens32 up
6) 修改允许通过root账户SSH登陆
通过secureCRT SSH或putty远程登陆到zabbix,首先通过用户appliance登陆,再切换到root
修改允许root远程SSH登陆: #修改配置,允许root可以SSH root@zabbix:~# vi /etc/ssh/sshd_config //进入配置文件 //键盘按下'i',进入编辑模式,按如下修改完成后按下‘Esc’,同时按住'Shit'和':',输入wq保存配置 ...省略代码... #PermitRootLogin prohibit-password PermitRootLogin yes ...省略代码... :wq //保存配置 #重启SSH服务 root@zabbix:~# /etc/init.d/ssh restart //重启SSH服务 #或者采用如下重启服务 root@zabbix:~# service sshd restart
7)修改永久IP地址 #手工配置永久固定IP地址(建议增加一块网卡,NAT模式,用于上网) root@zabbix:~# vi /etc/network/interfaces ...省略代码... auto ens32 iface ens32 inet static address 192.168.0.100 netmask 255.255.255.0 gateway 192.168.0.1 #修改完,保存配置 #重启network服务 root@zabbix:~# /etc/init.d/networking restart
8) 通过浏览器登陆Zabbix Web界面 web地址:http://192.168.0.100/zabbix/ #默认用户名密码 用户名:Admin 密码:zabbix
9)修改缺省密码
10)优化中文乱码问题 # 在zabbix目录下,备份文件DejaVuSans.ttf /usr/share/fonts/truetype/dejavu/DejaVuSans.ttf,备份DejaVuSans.ttf.bak # 把windows下的DejaVuSans.ttf拷贝到该目录中
2. 实验环境
2.1 简易拓扑图
实验环境简介: 四台思科路由器与Zabbix4.2全部桥接在一起; 使用到的OIDview工具,用于查找oid参数; Zabbix本次使用版本为4.2,大家可以使用最新的4.4版本; 设备配置接口IP和SNMP读权限; 浏览器推荐用:Google和Firefox ; 本次实验使用'cisco_ios' 的oid如下: #CPU_Total_5sec 键值:cpmCPUTotal5sec.1 oid:1.3.6.1.4.1.9.9.109.1.1.1.1.3.1 #Memory_Pool_Free 键值:ciscoMemoryPoolFree.1 oid:1.3.6.1.4.1.9.9.48.1.1.1.6.1 #Memory_Pool_Used 键值:ciscoMemoryPoolUsed.1 oid:1.3.6.1.4.1.9.9.48.1.1.1.5.1 #Interface_Use 接口入方向键值/OID:ifInOctets.1, 1.3.6.1.2.1.2.2.1.10.1 接口出方向键值/OID:ifOutOctets.1 1.3.6.1.2.1.2.2.1.16.1 #备注:如键值后面的 '.1' 表示是索引,不同接口索引不一样,其他也类似。
2.2 基本配置 #路由器基本snmp配置(只读权限就行) snmp-server community test@123 RO
3. 配置
1)修改全局宏(snmp团体变量):
操作步骤:点击“管理->一般->宏”
备注:这个变量统一为所有网络设备使用,一旦要修改,改它就行了,非常方便。
2)创建主机群组
操作步骤:点击“配置->主机群组->创建主机群组”
备注:主机群组可以存放主机和模板。
3)添加模板
操作步骤:点击“配置->模板->创建模板”
备注:模板需要关联到群组中。
4)添加监控项
操作步骤:点击"配置->模板->已创建模板->监控项->创建监控项"
备注:大家可以通过 'OidView' 工具来获取OID值(已放网盘上)。
5)添加图形
操作步骤:点击"配置->模板->已创建模板->图形->创建图形"
6)添加仪表盘
操作步骤:点击"监测->仪表板->创建新的(靠近右上角)"

7)添加触发器
操作步骤:点击"配置->模板->已创建模板->触发器->创建触发器"
说明:我这里的条件-结果填写很小,只为演示触发报警使用。
8)添加邮件告警
①操作步骤:点击"管理->报警媒介类型->Email" 先获取QQ邮箱的授权码:

备注:授权码是在QQ邮箱,“设置->账户->POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务”,生成授权码。
②触发器报警
备注:通过手段制造CPU阈值上升,满足报警的条件(我使用show tech就可以触发告警)
③查看发送告警日志:
操作步骤:"报表->动作日志"
备注:状态已显示'已送达',如失败请仔细检查各项指标或邮箱问题(前提zabbix能连上互联网)。
④已收到告警邮件通知
备注:zabbix发送的日期与QQ邮箱日期不一致,这个请忽略,zabbix上的时间我没去调,哈哈。
如果喜欢的我的文章,欢迎关注我的公众号:点滴技术,扫码关注,不定期分享
软件开发
2020-07-23 20:15:00
[TOC]
1. 概念
1.1 基本概念
时间,对于我们来说很重要,什么时候做什么?什么时候发生什么?没有时间的概念,生活就乱了。
在日常的运维当中,我们更关注告警的时间:什么时候发生、什么事故、影响范围、什么时候解决,都是有关联性的,所以时间的准确性是非常非常的重要。
你可能会好奇,时间到底是怎么来的呢?作为网工的你,可能你也不是很清楚吧?
你是不是只知道NTP? 标准参考时钟是什么? 时间戳'1573401600' , 能看出这个是什么时间点?
原子钟和GPS是作为标准参考时钟,全世界都以它为时钟源。
从你手表或电脑的时间同步到时钟源,中间可能经过多层级,每一层叫做 'stratum' ,只是精确度不一样而已。当然,我们肯定不是直接和原子钟同步,我们可以选择最近的主时钟源,比如中国/香港等等。
或者,你们公司内网可能是同步到内网的主时钟服务器,然后同步到互联网去。
说完了时钟源,那么得有一个时间标准吧,全世界每个国家时区都不一样,不能用一个时间给全地球用吧。
GMT(Greenwich Mean Time): 格林尼治标准时间,以太阳横穿格林尼治子午线(本初子午线)来计算,现不推荐;
UTC( Universal Time Coordinated ):协调世界时间 , 以原子时秒长为基础 ,接近GMT,作为世界标准时间。
为了统一世界的时间,1884年的国际经度会议规规定将全球划分为24个时区(东、西各12个时区)。规定英国(格林尼治天文台旧址)为零时区(GMT+00),东1-12区,西1-12区,中国北京处于东8区(GMT+08)
举个栗子: UTC 时间:2019-11-10 16:00:00 北京时间:2019-11-11 00:00:00 (UTC + 8个小时)
时钟源和标准时间说完了,那么接下来讲下时间同步通信机制。 网络时间协议NTP(Network Time Protocol)是TCP/IP协议族里面的一个应用层协议。 NTP用于在一系列分布式时间服务器与客户端之间同步时钟。 NTP的实现基于IP和UDP,NTP报文通过UDP传输,端口号是123。
1.2 版本演进
版本 时间 协议号 描述 NTPv1 1988年6月 RFC 1059 首次提出了完整的NTP规则以及算法,但是NTPv1不支持认证和控制消息
NTPv2 NTPv3 NTPv4
1989年9月 1992年3月 2010年6月
RFC 1119 RFC 1305 RFC 5905
支持认证和控制消息 正式引入了校正原则,并改进了时钟选择和时钟过滤算法。NTPv3目前应用较为广泛,仅支持IPv4 支持IPv4和IPv6,安全性更高。
2. 示例演示
2.1 前提条件
准备好三方库: ntplib
安装方法: pip3 install -i [https://pypi.douban.com/simple/](https://pypi.douban.com/simple/) ntplib
环境:Windows + Python3
2.2 完整代码 #!/usr/bin/env python3 #-*- coding:UTF-8 -*- #欢迎关注微信公众号:点滴技术 #这里有靠谱、有价值、免费分享 ​ import ntplib import os,datetime ​ hosts = ['0.cn.pool.ntp.org','1.cn.pool.ntp.org','2.cn.pool.ntp.org','3.cn.pool.ntp.org'] ​ def ntp_client(): #创建实例,NTPClient()是一个类 t = ntplib.NTPClient() for host in hosts: try: #ntp server可以填写主机和域名,建议用域名 #缺省端口为ntp, 版本为2, 超时为5s #作用:查询 NTP 服务器,并返回对象 r = t.request(host , port='ntp', version=4, timeout=5) if r: break except Exception as e: pass #显示的是时间戳 t = r.tx_time #使用datetime模块,格式化:x年x月x日 时:分:秒.毫秒 _date,_time = str(datetime.datetime.fromtimestamp(t))[:22].split(' ') ​ print("调整前时间是:", datetime.datetime.now()) os.system('date {} && time {}'.format(_date, _time)) print("调整后时间是:", datetime.datetime.now()) ​ if __name__ == '__main__': #适用于Windows ntp_client()
在windowns环境下,通过'管理员'运行'cmd',进入到对应的文件路径,运行结果如下所示:
备注:截至目前,尚未找到能够在pycharm直接运行代码,报错 “客户端没有所需的特权”,后续研究 'ctypes' 看能否实现。
2.3 其他问题
关于pycharm运行'os.system()' 出现中文乱码问题,需要改下编码问题,设置如下:
3. 参考信息:
亚洲NTP服务器池:
时间戳转换
UTC
时区
如果喜欢的我的文章,欢迎关注我的公众号:点滴技术,扫码关注,不定期分享
软件开发
2020-07-23 20:14:00
两栈共享空间
可以用一个数组来存储两个相同类型的栈。
数组有两个端点,两个栈有两个栈底,
让一个栈的栈底作为数组的开始端,下标为0处,
让另外一个栈的栈底作为数组的结束端,下标为n-1出。
两个栈如果增加元素,就是两个端点向中间延伸。
两个栈的栈底在数组两端, 新增元素,向中间考虑,即两个栈底的栈顶top1和 top2向中间靠拢, 只要top1和top2 不见面, 两个栈就可以一直使用。
当栈1为空时 top1 = -1
当栈2为空时 top2 = n
若栈2 是空栈, 栈1的top1 等于 n-1时, 就是栈1满了。
若栈1 是空栈, 栈2的的top2等于0时, 就是栈2满了。
两个栈见面时,也就是top1 和 top2之间差1时 , 即top1+1 = top2 是栈满。
//两栈共享空间结构 typedef struct{ SElemType data[MAXSIZE]; //栈1栈顶指针 int top1; //栈2栈顶指针 int top2; } //两栈push方法 Status Push(SqDoubleStack *S, SElemType *e, int stackNumber){ //栈已满, 不能再push新元素 if(S->top1+1 == S->top2){ return ERROR; } //栈1有新元素进栈, 则先top1+1给数组元素赋值 if(stackNumber == 1){ S->data[++s->top1] = e; }else if(stackNumber == 2){ //栈2有新元素进栈, 则先top2-1给数组元素赋值 S->data[--S->top2] = e; } return ok; } //两栈pop方法 若栈不为空,则删除S的栈顶元素,用e返回其值, 并返回OK; 否则返回ERROR Status Pop(SqDoubleStack *S, SElemType *e, int stackNumber){ if(stackNumber == 1){ //栈1已经是空栈 if(S->top1 == -1){ return ERROR; } //栈1的栈顶元素出栈 *e = S->data[S->top1--]; }else if(stackNumber == 2){ //栈2已经是空栈 if(S->top2 == MAXSIZE){ return ERROR; } //栈2的栈顶元素出栈 *e = S->data[S->top2++]; } return OK; }
已经在push入口侧判断过栈满情况,后面的top+1 或top2-1不用担心溢出的问题。
使用这样的数据结构, 通常都是当两个栈的空间需求有相反的关系时,也就是一个栈增长时另外一个栈在缩短的情况。
这样使用两栈共享空间存储才有比较大的意义。否则两个栈都在不停地增长,很快会引栈满而溢出。
两栈共享空间针对的是相同数据类型的栈。
软件开发
2020-07-23 19:37:00
[toc]
码好python的每一篇文章.
大家好!又有一段时间没更新文章了,没错,是真的忙了。想要写一篇文章,确实要花几天时间的功夫,因为要保障文章的质量。
后面的文章我会通过写些基础理论和实战文章,毕竟干技术的,光看理论确实会没那么多耐心,总之,我会穿插一些,保证各位看官满意为止。
1. 环境准备
序号 IP地址 操作系统 安装软件 备注
1 2 3
192.168.8.130 192.168.8.131 192.168.8.1
CentOS 7.6.1810 CentOS 7.6.1810 windows 10
python3.6+django2.2 mysql PyCharm Pro
都运行在虚拟环境 mysql数据 专业版能够使用与centos远程同步配置
CentOS7安装环境,请谷歌 或百度查找安装文档,该步骤略。 python环境安装
pyenv虚拟环境管理python多版本和软件库 请参考此文。
如何更新 pip 源步骤: [root@localhost ~]# mkdir .pip [root@localhost ~]# cd .pip/ [root@localhost .pip]# touch pip.conf [root@localhost .pip]# vim pip.conf [global] index-url = https://mirrors.aliyun.com/pypi/simple/ [install] trusted-host=mirrors.aliyun.com # 添加或修改后,记得保存。
如何更新 YUM 源步骤: # 安装wget,有则忽略,跳过 [root@localhost ~]# yum install wget -y # 备份yum源 [root@localhost python]# cd /etc/yum.repos.d/ [root@localhost yum.repos.d]# mkdir repo_bak [root@localhost yum.repos.d]# mv *.repo repo_bak/ # 下载新的repo [root@localhost yum.repos.d]# wget http://mirrors.aliyun.com/repo/Centos-7.repo # 清除系统yum缓存并生成新的yum缓存 [root@localhost yum.repos.d]# yum clean all [root@localhost yum.repos.d]# yum makecache # 安装EPEL(Extra Packages for Enterprise Linux )源 [root@localhost yum.repos.d]# yum install -y epel-release # 再次清除系统yum缓存并生成新的yum缓存 [root@localhost yum.repos.d]# yum clean all [root@localhost yum.repos.d]# yum makecache # 查看系统可用的yum源和所有的yum源 [root@localhost yum.repos.d]# yum repolist enabled
PIP源更新参考链接
YUM源更新参考链接
2. 开始安装
2.1 安装Django
说明: 此台CentOS IP地址是 192.168.8.130(通过NAT映射,可以上互联网) # 安装django2.2版本 (py369) [python@localhost Python]$ pip install django==2.2 # 查看已安装的版本信息 (py369) [python@localhost Python]$ python -m django --version 2.2 # 安装mysql拓展 (py369) [python@localhost Python]$ pip install mysqlclient
2.2 安装 Mysql数据库
说明: 此台CentOS IP地址是 192.168.8.131(通过NAT映射,可以上互联网) 安装与启动
提前下载如下安装包,通过winscp上传到根目录下:
http://repo.mysql.com/yum/mysql-5.7-community/el/7/x86_64/mysql-community-server-5.7.31-1.el7.x86_64.rpm
复制上面链接通过浏览器下载,速度更快。 # 安装mysql及拓展 [root@localhost ~]# wget -i -c http://dev.mysql.com/get/mysql57-community-release-el7-10.noarch.rpm [root@localhost ~]# yum -y install mysql57-community-release-el7-10.noarch.rpm [root@localhost ~]# yum -y install mysql-community-server-5.7.31-1.el7.x86_64.rpm # 启动mysql服务 [root@localhost ~]# systemctl start mysqld.service # 查看mysql服务状态 [root@localhost ~]# systemctl status mysqld.service ...此处省略字符 # active 就表示正常在运行了 Active: active (running) since Tue 2020-07-21 10:14:23 CST; 2s ago ...此处省略字符 修改密码及创建数据库 # 查找密码,uQjeSi?N(2zH,这个就是缺省密码 [root@localhost ~]# grep "password" /var/log/mysqld.log 2020-07-21T20:48:28.965616Z 1 [Note] A temporary password is generated for root@localhost: uQjeSi?N(2zH # 用缺省密码登陆数据库 [root@localhost ~]# mysql -uroot -p Enter password:uQjeSi?N(2zH # 输入密码 # 修改密码 mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'DevOps@2020'; Query OK, 0 rows affected (0.01 sec) # 授权访问数据库 mysql> grant all on *.* to 'root'@'%' identified by 'DevOps@2020'; Query OK, 0 rows affected, 1 warning (0.00 sec) # 刷新权限 mysql> flush privileges; Query OK, 0 rows affected (0.00 sec) # 创建新的数据库 mysql> CREATE DATABASE IF NOT EXISTS devops default charset utf8 COLLATE utf8_general_ci; Query OK, 1 row affected (0.00 sec) # 查看已创建的数据库 mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | devops | | mysql | | performance_schema | | sys | +--------------------+ 5 rows in set (0.00 sec) # 退出数据库,修改配置文件,结果如下所示: [root@localhost ~]# cat /etc/my.cnf |grep -v ^#|grep -v ^$ [client] default-character-set = utf8 [mysqld] datadir=/var/lib/mysql socket=/var/lib/mysql/mysql.sock symbolic-links=0 log-error=/var/log/mysqld.log pid-file=/var/run/mysqld/mysqld.pid character-set-server = utf8 collation-server = utf8_general_ci # 重启数据库 [root@localhost ~]# service mysqld restart 数据库连接验证正常:
windows 10下安装mysql拓展并验证连接数据库: C:\>pip install mysqlclient
备注: 我是通过pycharm导入模块pymysql进行测试。
3. 开始配置
各位先简单意淫下这种图,应该有个初步的认识。 当一位屌丝打开浏览器访问某页面的时候,输入 http://www.baidu.com,然后回车; django后台urls就会匹配到链接,可以把urls看作是http链接,专业叫做路由;
说明 :不是我们攻城狮理解的路由协议哈。 urls匹配到了就会在View视图查找代码,然后做出响应;
说明 :view就是我们要写的python代码 template简单理解就是html页面模板; Model简单理解数据库的一张表;
3.1 Django简单配置
3.1.1 创建一个工程(project)为devops: # 先安装个tree软件包 [python@localhost ~]$ sudo yum install tree # 创建一个新的工程project (py369) [python@localhost Python]$ django-admin startproject devops (py369) [python@localhost Python]$ tree devops devops ├── devops │   ├── __init__.py │   ├── __pycache__ │   │   ├── __init__.cpython-36.pyc │   │   ├── settings.cpython-36.pyc │   │   ├── urls.cpython-36.pyc │   │   └── wsgi.cpython-36.pyc │   ├── settings.py │   ├── urls.py │   └── wsgi.py └── manage.py
3.1.2 通过PyCharm远程同步服务器代码
说明: pycharm将django项目下载到window电脑上进行编辑,然后再实时同步到CentOS上,pycharm编写代码方便。 打开pycharm,点击Tools->Deployment->Configuration
创建新的服务名,参考如下配置:
3)下载远端django的project:
4)修改自动同步配置,Tools->Deployment->Options
3.1.3 在pycharm上直接修改django的配置文件:
1)devops->devops->settings.py: # 允许所有主机访问django,星号表示所有主机 ALLOWED_HOSTS = ['*'] # 增加数据库信息 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME':'devops', 'HOST':'192.168.8.131', 'USER':'root', 'PASSWORD':'root@123', 'PORT':3306, } } #修改语言编码和时区 LANGUAGE_CODE = 'zh-hans' TIME_ZONE = 'Asia/Shanghai'
2) 改完配置后,会自动同步到远程django那里。 摘取log部分:
2020/3/27 23:07] Automatic upload completed in 33 ms: 1 file transferred (103.6 kbit/s)
3.1.4 启动django服务
1)通过命令启动: python manage.py runserver 0.0.0.0:8888 (py369) [python@localhost devops]$ python manage.py runserver 0.0.0.0:8888 Watching for file changes with StatReloader Performing system checks... System check identified no issues (0 silenced). March 27, 2020 - 23:45:14 Django version 2.2, using settings 'devops.settings' Starting development server at http://0.0.0.0:8888/ Quit the server with CONTROL-C.
2)打开浏览器,输入 http://192.168.8.130:8888 :
3)还可以进入后台权限管理系统,输入 http://192.168.8.130:8888/admin
此时,还没有创建超级管理员用户和密码,不能够登陆。
4)接下来启动自带的APP admin (py369) [python@localhost devops]$ python manage.py migrate
*备注:*此命令会把系统自带的admin app数据库写入到mysql中。
5)登陆数据库mariadb,看是否正常写入 [root@localhost ~]# mysql -u root -pDevOps@2020 # 查看数据库 mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | ddjsdb | | devops | | devops01 | | mysql | | performance_schema | | sys | +--------------------+ 7 rows in set (0.73 sec) # 切换到数据库devops中 mysql> use devops # 查看数据库devops中的表,已经成功写入了 mysql> show tables; +----------------------------+ | Tables_in_devops | +----------------------------+ | auth_group | | auth_group_permissions | | auth_permission | | auth_user | | auth_user_groups | | auth_user_user_permissions | | django_admin_log | | django_content_type | | django_migrations | | django_session | +----------------------------+ 10 rows in set (0.00 sec)
6)创建一个管理员用户 (py369) [python@localhost devops]$ python manage.py createcachetable # 依次输入用户名、邮箱地址、密码
7)成功登陆后台管理系统 # 再次启动django (py369) [python@localhost devops]$ python manage.py runserver 0.0.0.0:8888
3.2 创建新的APP
3.2.1 创建新的app hello (py369) [python@localhost devops]$ python manage.py startapp hello # 手工创建urls.py 文件 (py369) [python@localhost devops]$ touch hello/urls.py # 查看app hello 树结构 (py369) [python@localhost devops]$ tree hello hello ├── admin.py ├── apps.py ├── __init__.py ├── migrations │   └── __init__.py ├── models.py ├── tests.py ├── urls.py └── views.py
3.2.2 pycharm同步下载远程hello目录
说明 :方法同章节3.1.2一样。
3.2.3 修改django配置脚本
1) 修改devops->devops->setting.py配置 INSTALLED_APPS = [ ...此处已省略 'hello.apps.HelloConfig', # 方式一:新增hello app信息 'hello', # 方式二:新增hello app信息 ]
2) 修改devops->hello->views.py配置 from django.shortcuts import render from django.http import HttpResponse def index(request): return HttpResponse('

hello django

')
3) 修改devops->hello->urls.py配置 from django.urls import path from . import views urlpatterns = [ path('hello/', views.index, name='index'), ]
4)最后修改devops->devops->urls.py配置
说明: 此urls.py为根路由入口(即总入口)。
第一种静态路由urls演示方法: from hello import views urlpatterns = [ path('admin/', admin.site.urls), # 缺省 path('hello/', views.index), # 新增 ]
登陆浏览器,输入 http://192.168.8.130:8888/hello/, 效果如下:
第二种静态路由urls演示方法: from django.urls import path,include # 新增,导入include模块 urlpatterns = [ path('admin/', admin.site.urls), # 缺省 path('hello/', include('hello.urls')), # 新增 ]
登陆浏览器,输入 http://192.168.8.130:8888/hello/hello, 效果如下:
如果喜欢的我的文章,欢迎关注我的公众号:点滴技术,扫码关注,不定期分享
软件开发
2020-07-23 09:19:00
线性表的链式存储结构
线性表的链式存储结构的特点是 用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的。
意味着数据元素可以存在内存未被占用的任意位置。
链式结构中,除了要存数据元素信息外,还要存储它的后继元素的存储地址。
为了表示每个数据元素ai与其直接后继数据元素ai+1之间的逻辑关系,对数据元素ai来说,除了存储其本身的信息之外,还需要存储一个指示其直接后继的信息(即直接后继的存储位置)。
我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。
指针域中存储的信息称做指针或链。
这两部分信息组成数据元素ai的存储映像,称为 结点 Node。
n个节点(ai的存储映像)链结成一个链表,即为线性表(a1,a2,...,an)的链式存储结构。
此链表的每个结点中只包含一个指针域,所以叫做 单链表。
单链表正是通过每个结点的指针域将线性表的数据元素按其逻辑次序链接在一起。
我们把链表中的第一个结点的存储位置叫做头指针,整个链表的存取必须是从头指针开始进行,之后的每一个结点,就是上一个的后继指针指向的位置。
最后一个节点的指针没有直接后继,因此指针是 空 (NULL 或者 ^ 表示)
一般会在单链表的第一个结点前附设一个节点,称为头结点。可以不存储任何信息,也可以存储链表长度等附加信息。
头结点的指针域存储指向第一个结点的指针。
头指针与头结点的异同
头指针:
头指针是指链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针
头指针具有标识作用,所有常用头指针冠以链表的名字
无论链表是否为空,头指针均不为空。头指针是链表的必要元素
头结点:
头结点是为了操作的统一和方便而设立的,放在第一元素的节点之前,其数据域一般无意义(可以存放链表的长度)
有了头结点,对在第一元素节点前插入节点和删除第一节点,其操作与其它节点的操作就统一了
头结点不一定是链表必须要素
线性表链式存储结构代码描述
若线性表是空表,头结点的指针域为空。
结点由存放数据元素的数据域,存放后继结点地址的指针域组成。
假设p是指向线性表第i个元素的指针,
节点ai的数据域,可以用p->data表示,p->data的值是一个数据元素,
节点ai的指针域,可以用p->next表示,p->next的值是一个指针。
p->next指向第i+1个元素,即指向ai+1的指针。
如果p->data = ai 那么 p->next->data = ai+1
单链表的读取
单链表获取第i个元素的数据操作GetElem的算法思路:
1. 声明一个节点p指向链表的第一个结点,初始化j从1开始;
2. 当j3. 若到链表末尾p为空,则说明第i个元素不存在;
4. 否则查找成功,返回节点p的数据。
//初始条件: 顺序线性表L已经存在, 1<=i<=ListLength(L) //操作结果: 用e返回L中第i个数据元素的值 Status GetElem(LinkList L, int i, ElemType *e){ int j; //声明一个节点p LinkList p; //让p指向链表L的第一个结点 p = L->next; //j为计数器 j = 1; //p不为空且j不等于 while(p && j < i){ //让p指向下一个节点 p = p->next ++j; } if(!p || j > i){ return ERROR; } //赋值第i个元素给e *e = p->data; return OK; }
查找算法的时间复杂度取决于i的位置,当i=n的时候遍历n-1次才行,最坏的时间复杂度是O(n)。
主要核心思想是 “工作指针后移”。
单链表的插入与删除
单链表的插入
将结点s插入到结点p和p->next之间,
s->next = p>next; p->next = s;
先把p的后继结点改成s的后继结点,再把结点s变成p的后继结点。
如果更换顺序,就会导致p->next节点没有上级。
单链表第i个数据插入结点的算法思路:
1. 声明一结点p指向链表第一个结点,初始化j从1开始; 当jdata; 单链表的插入标准语句s->next=p->next; p->next = s; 返回成功。
//初始条件:顺序线性表L 已经存在, 1 <=i<=ListLength(L) //操作结果: 在L中第i个位置之前插入新的数据元素e,L的长度加1 Status ListInsert(LinkList *L, int i, ElemType e){ int j; LinkList p,s; p = *L; j = 1; //寻找第i个节点 while(p && j < i){ p = p>nextl ++j; } //第i个元素不存在 if(!p || j > i){ return ERROR; } //生成新结点 s = (LinkList)malloc(Sizeof(Node)) s->data = e; s->next = p->next; p->next = s; return OK; }
malloc是C的标准函数用来生成一个新的结点,其类型与Node是一样的,其实质就是在内存中找一块空地,用来存放e数据s结点。

单链表的删除
将节点q删除单链表的操作,其实就是讲它的前继结点的指针绕过,指向它的后继结点即可。
p->next=p->next->next。
用q替换p->next 即是
q = p->next; p->next=q->next;
就是让p的后继的后继结点改成p的后继结点。
单链表第i个数据删除结点的算法思路: 声明一结点p指向链表第一个结点,初始化j从1开始; 当jnext赋值给q; 单链表的删除标准语句p->next = q->next; 将q结点中的数据赋值给e,作为返回; 释放q节点; 返回成功。

//初始条件:顺序线性表L 已经存在, 1 <=i<=ListLength(L) //操作结果: 删除L的第i个数据元素,并用e返回其值, L的长度减1 Status ListDelete(LinkList L, int i, ElemType e){ int j; LinkList p,q; p = *L; j = 1; //寻找第i个节点 while(p->next && j < i){ p = p>nextl ++j; } //第i个元素不存在 if(!(p->next) || j > i){ return ERROR; } //将q的后继赋值给p的后继 q = p->next; p-next = q->next; //将q结点中的数据给e *e = q->data; //让系统回收此结点,释放内存 free(q); return OK; }
free是c的标准函数,主要作用是让系统回收一个Node结点,释放内存。
单链表插入和删除算法,都是由两部分组成: 第一部分就是遍历查找第i个元素;第二部分就是插入和删除元素。时间复杂度都是O(n)。
如果在第i个位置,插入10个元素,对于顺序存储结构来说, 每一次插入都需要移动n-i个元素,每次都是O(n)。
而单链表,只需要在第一次时,找到第i个位置的指针,此时为O(n),接下来只是简单地通过赋值移动指针,时间复杂度都是O(1)。
对于插入或删除数据越频繁的操作,单链表的效率优势就越是明显。
软件开发
2020-07-22 22:45:00
栈(stack)是 限定仅在表尾插入和删除操作的线性表。
把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。
栈又称为后进先出(Last In First Out) LIFO 或者 先进后出 (First In Last Out) FILO。
栈是一个线性表,栈的元素具有线性关系,即前驱后继关系。只不过栈是一种特殊的线性表。只支持栈顶进行插入和删除操作。

栈的插入操作,叫作进栈,也称压栈,入栈。
栈的删除操作,叫作出栈,也称弹栈。

进栈出栈变化形式
栈对线性表的插入和删除的位置进行了限制, 并没有对元素进出的时间进行限制。
在不是所有元素都进栈的情况下,事先进去的元素也可以出栈,只要保证是栈顶元素出栈就可以。

示例: 1、2、3进, 再3、2、1出。 出栈次序为321 1进、1出、2进、2出、3进、3出。 出栈次序为123 1进、2进、2出、1出、3进、3出。 出栈次序为213 1进、1出、2进、3进、3出、2出。 出栈次序为132 1进、2进、2出、3进、3出、1出。 出栈次序为231

栈的抽象数据类型
栈的插入和删除操作,可以改名为push(压)和pop(弹)。
ADT Abstract Data Type 栈 (stack)
Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继元素。
Operation
InitStack(*S): 初始化操作,建立一个空栈S。
DestroyStack(*S): 若栈存在, 则销毁它。
ClearStack(*S): 将栈清空。
StackEmpty(S): 若栈为空,返回true,否则返回false。
GetTop(S,*e): 若栈存在且非空,用e返回S的栈顶元素。
Push(*S,e): 若栈S存在,插入新元素e到栈S中并成为栈顶元素。
Pop(*S,*e): 删除栈S中栈顶元素,并用e返回其值。
StackLength(S): 返回栈S的元素个数。
endData
软件开发
2020-07-22 21:36:00
Lambda表达式
一、Lambda表达式简介
什么是Lambda?
Lambda是JAVA 8添加的新特性,说白了,Lambda是一个匿名函数
为什么使用Lambda
使用Lambda表达式可以对一个接口的方法进行非常简洁的实现
Lambda对接口的要求
虽然可以使用Lambda表达式对某些接口进行简单的实现,但是并不是所有的接口都可以用Lambda表达式来实现,要求接口中定义的 必须要实现的抽象方法只能是一个 在JAVA8中 ,对接口加了一个新特性:default 可以使用default对接口方法进行修饰,被修饰的方法在接口中可以默认实现
@FunctionalInterface
修饰函数式接口的,接口中的抽象方法只有一个
二、Lambda的基础语法
1.语法 // 1.Lambda表达式的基础语法 // Lambda是一个匿名函数 一般关注的是以下两个重点 // 参数列表 方法体 /** * ():用来描述参数列表 * {}:用来描述方法体 有时可以省略 * ->: Lambda运算符 读作goes to * 例 Test t=()->{System.out.println("hello word")}; 大括号可省略 */
2.创建多个接口 /** * 无参数无返回值接口 * @author Alan * @version 1.0 * @date 2020-05-27 10:24 */ @FunctionalInterface public interface LambdaNoneReturnNoneParmeter { void test(); } /** * 无返回值有单个参数 * @author Alan * @version 1.0 * @date 2020-05-27 10:26 */ @FunctionalInterface public interface LambdaNoneReturnSingleParmeter { void test(int n); } /** * 无返回值 多个参数的接口 * @author Alan * @version 1.0 * @date 2020-05-27 10:27 */ @FunctionalInterface public interface LambdaNoneReturnMutipleParmeter { void test(int a,int b); } /** * 有返回值 无参数接口 * @author Alan * @version 1.0 * @date 2020-05-27 10:28 */ @FunctionalInterface public interface LambdaSingleReturnNoneParmeter { int test(); } /** * 有返回值 有单个参数的接口 * @author Alan * @version 1.0 * @date 2020-05-27 10:29 */ @FunctionalInterface public interface LambdaSingleReturnSingleParmeter { int test(int n); } /** * 有返回值 有多个参数的接口 * @author Alan * @version 1.0 * @date 2020-05-27 10:30 */ @FunctionalInterface public interface LambdaSingleReturnMutipleParmeter { int test(int a,int b); }
3.创建测试类 package com.alan.learn.syntax; import com.alan.learn.interfaces.*; /** * @author Alan * @version 1.0 * @date 2020-05-27 10:33 */ public class Syntax1 { public static void main(String[] args) { // 1.Lambda表达式的基础语法 // Lambda是一个匿名函数 一般关注的是以下两个重点 // 参数列表 方法体 /** * ():用来描述参数列表 * {}:用来描述方法体 * ->: Lambda运算符 读作goes to */ // 无参无返回 LambdaNoneReturnNoneParmeter lambda1=()->{ System.out.println("hello word"); }; lambda1.test(); // 无返回值 单个参数 LambdaNoneReturnSingleParmeter lambda2=(int n)->{ System.out.println("参数是:"+n); }; lambda2.test(10); // 无返回值 多个参数 LambdaNoneReturnMutipleParmeter lambda3=(int a,int b)->{ System.out.println("参数和是:"+(a+b)); }; lambda3.test(10,12); // 有返回值 无参数 LambdaSingleReturnNoneParmeter lambda4=()->{ System.out.println("lambda4:"); return 100; }; int ret=lambda4.test(); System.out.println("返回值是:"+ret); // 有返回值 单个参数 LambdaSingleReturnSingleParmeter lambda5=(int a)->{ return a*2; }; int ret2= lambda5.test(3); System.out.println("单个参数,lambda5返回值是:"+ret2); //有返回值 多个参数 LambdaSingleReturnMutipleParmeter lambda6=(int a,int b)->{ return a+b; }; int ret3=lambda6.test(12,14); System.out.println("多个参数,lambda6返回值是:"+ret3); } } 输出结果: hello word 参数是:10 参数和是:22 lambda4: 返回值是:100 单个参数,lambda5返回值是:6 多个参数,lambda6返回值是:26
三、语法精简
针对上述基础语法的精简
1.参数类型精简 /** * 语法精简 * 1.参数类型 * 由于在接口的抽象方法中,已经定义了参数的数量类型 所以在Lambda表达式中参数的类型可以省略 * 备注:如果需要省略类型,则每一个参数的类型都要省略,千万不要一个省略一个不省略 */ LambdaNoneReturnMutipleParmeter lambda1=(int a,int b)-> { System.out.println("hello world"); }; 可以精简为: LambdaNoneReturnMutipleParmeter lambda1=(a,b)-> { System.out.println("hello world"); };
2.参数小括号精简 /** * 2.参数小括号 * 如果参数列表中,参数的数量只有一个 此时小括号可以省略 */ LambdaNoneReturnSingleParmeter lambda2=(a)->{ System.out.println("hello world"); }; 可以精简为: LambdaNoneReturnSingleParmeter lambda2= a->{ System.out.println("hello world"); };
3.方法大括号精简 /** * 3.方法大括号 * 如果方法体中只有一条语句,此时大括号可以省略 */ LambdaNoneReturnSingleParmeter lambda3=a->{ System.out.println("hello world"); }; 可以精简为: LambdaNoneReturnSingleParmeter lambda3=a->System.out.println("hello world");
4.大括号精简补充 /** * 4.如果方法体中唯一的一条语句是一个返回语句 * 贼省略大括号的同时 也必须省略return */ LambdaSingleReturnNoneParmeter lambda4=()->{ return 10; }; 可以精简为: LambdaSingleReturnNoneParmeter lambda4=()->10;
5.多参数,有返回值 精简 LambdaSingleReturnNoneParmeter lambda4=(a,b)->{ return a+b; }; 可以精简为: LambdaSingleReturnMutipleParmeter lambda5=(a,b)->a+b;
四、Lambda语法进阶
1.方法引用(普通方法与静态方法)
在实际应用过程中,一个接口在很多地方都会调用同一个实现,例如: LambdaSingleReturnMutipleParmeter lambda1=(a,b)->a+b; LambdaSingleReturnMutipleParmeter lambda2=(a,b)->a+b;
这样一来每次都要写上具体的实现方法 a+b,如果需求变更,则每一处实现都需要更改,基于这种情况,可以将后续的是实现更改为已定义的 方法,需要时直接调用就行
语法: /** *方法引用: * 可以快速的将一个Lambda表达式的实现指向一个已经实现的方法 * 方法的隶属者 如果是静态方法 隶属的就是一个类 其他的话就是隶属对象 * 语法:方法的隶属者::方法名 * 注意: * 1.引用的方法中,参数数量和类型一定要和接口中定义的方法一致 * 2.返回值的类型也一定要和接口中的方法一致 */
例: package com.alan.learn.syntax; import com.alan.learn.interfaces.LambdaSingleReturnSingleParmeter; /** * @author Alan * @version 1.0 * @date 2020-05-27 11:48 */ public class Syntax3 { public static void main(String[] args) { LambdaSingleReturnSingleParmeter lambda1=a->a*2; LambdaSingleReturnSingleParmeter lambda2=a->a*2; LambdaSingleReturnSingleParmeter lambda3=a->a*2; //简化 LambdaSingleReturnSingleParmeter lambda4=a->change(a); //方法引用 LambdaSingleReturnSingleParmeter lambda5=Syntax3::change; } /** * 自定义的实现方法 */ private static int change(int a){ return a*2; } }
2.方法引用(构造方法)
目前有一个实体类 public class Person { public String name; public int age; public Person() { System.out.println("Person的无参构造方法执行"); } public Person(String name, int age) { this.name = name; this.age = age; System.out.println("Person的有参构造方法执行"); } }
需求
两个接口,各有一个方法,一个接口的方法需要引用Person的无参构造,一个接口的方法需要引用Person的有参构造 用于返回两个Person对象,例: interface PersonCreater{ //通过Person的无参构造实现 Person getPerson(); } interface PersonCreater2{ //通过Person的有参构造实现 Person getPerson(String name,int age); }
那么可以写作: public class Syntax4 { public static void main(String[] args) { PersonCreater creater=()->new Person(); //引用的是Person的无参构造 //PersonCreater接口的方法指向的是Person的方法 PersonCreater creater1=Person::new; //等价于上面的()->new Person() //实际调用的是Person的无参构造 相当于把接口里的getPerson()重写成new Person()。 Person a=creater1.getPerson(); //引用的是Person的有参构造 PersonCreater2 creater2=Person::new; Person b=creater2.getPerson("张三",18); } }
注意:是引用无参构造还是引用有参构造 在于接口定义的方法参数
五、综合练习
1.集合排序案例 package com.alan.exercise; import com.alan.learn.data.Person; import java.util.ArrayList; /** * 集合排序案例 * @author Alan * @version 1.0 * @date 2020-05-27 15:08 */ public class Exercise1 { public static void main(String[] args) { //需求:已知在一个ArrayList中有若干各Person对象,将这些Person对象按照年龄进行降序排列 ArrayList list=new ArrayList<>(); list.add(new Person("张三",10)); list.add(new Person("李四",12)); list.add(new Person("王五",13)); list.add(new Person("赵六",14)); list.add(new Person("李雷",11)); list.add(new Person("韩梅梅",8)); list.add(new Person("jack",10)); System.out.println("排序前:"+list); //将排列的依据传入 具体的方法指向的是 内部元素的age相减 sort会依据结果的正负进行降序排列 //sort 使用提供的 Comparator对此列表进行排序以比较元素。 list.sort((o1, o2) -> o2.age-o1.age); System.out.println("排序后:"+list); } }
2.Treeset排序案例 package com.alan.exercise; import com.alan.learn.data.Person; import java.util.TreeSet; /** * @author Alan * @version 1.0 * @date 2020-05-27 15:37 */ public class Exercise2 { public static void main(String[] args) { /**Treeset 自带排序 * 但是现在不知道Person谁大谁小无法排序 * 解决方法: * 使用Lambda表达式实现Comparator接口,并实例化一个TreeSet对象 * 注意:在TreeSet中如果Comparator返回值是 0 会判断这是两个元素是相同的 会进行去重 * TreeSet set=new TreeSet<>((o1, o2) -> o2.age-o1.age); * 这个获取的对象打印会少一个Person * 此时我们将方法修改 */ TreeSet set=new TreeSet<>((o1, o2) ->{ if(o1.age>=o2.age){ return -1; }else { return 1; } }); set.add(new Person("张三",10)); set.add(new Person("李四",12)); set.add(new Person("王五",13)); set.add(new Person("赵六",14)); set.add(new Person("李雷",11)); set.add(new Person("韩梅梅",8)); set.add(new Person("jack",10)); System.out.println(set); } }
3.集合的遍历 package com.alan.exercise; import java.util.ArrayList; import java.util.Collections; /** * 集合的遍历 * @author Alan * @version 1.0 * @date 2020-05-27 15:52 */ public class Exercise3 { public static void main(String[] args) { ArrayList list=new ArrayList<>(); Collections.addAll(list,1,2,3,4,5,6,7,8,9); /** * list.forEach(Consumer action) * api文档解释: 对 集合中的每个元素执行给定的操作,直到所有元素都被处理或动作引发异常。 * 将集合中的每一个元素都带入到接口Consumer的方法accept中 然后方法accept指向我们的引用 * 输出集合中的所有元素 * list.forEach(System.out::println); */ //输出集合中所有的偶数 list.forEach(ele->{ if(ele%2==0){ System.out.println(ele); } }); } }
4.删除集合中满足条件的元素 package com.alan.exercise; import com.alan.learn.data.Person; import java.util.ArrayList; /** * 删除集合中满足条件的元素 * @author Alan * @version 1.0 * @date 2020-05-27 16:05 */ public class Exercise4 { public static void main(String[] args) { ArrayList list=new ArrayList<>(); list.add(new Person("张三",10)); list.add(new Person("李四",12)); list.add(new Person("王五",13)); list.add(new Person("赵六",14)); list.add(new Person("李雷",11)); list.add(new Person("韩梅梅",8)); list.add(new Person("jack",10)); //删除集合中年龄大于12的元素 /** * 之前迭代器的做法 * ListIterator it = list.listIterator(); * while (it.hasNext()){ * Person ele=it.next(); * if(ele.age>12){ * it.remove(); * } * } */ /** * lambda实现 * 逻辑 * 将集合中的每一个元素都带入到接口Predicate的test方法中, * 如果返回值是true,则删除这个元素 */ list.removeIf(ele->ele.age>10); System.out.println(list); } }
5.开辟一条线程 做一个数字的输出 package com.alan.exercise; /** * 需求: * 开辟一条线程 做一个数字的输出 * @author Alan * @version 1.0 * @date 2020-05-27 16:17 */ public class Exercise5 { public static void main(String[] args) { /** * 通过Runnable 来实例化线程 */ Thread t=new Thread(()->{ for(int i=0;i<100;i++){ System.out.println(i); } }); t.start(); } }
六、系统内置的函数式接口 package com.alan.functional; import java.util.function.*; /** * 系统内置的一些函数式接口 * @author Alan * @version 1.0 * @date 2020-05-27 16:23 */ public class FunctionalInterface { public static void main(String[] args) { // Predicate : 参数是T 返回值boolean // 在后续如果一个接口需要指定类型的参数,返回boolean时可以指向 Predicate // IntPredicate int -> boolean // LongPredicate long -> boolean // DoublePredicate double -> boolean // Consumer : 参数是T 无返回值(void) // IntConsumer int ->void // LongConsumer long ->void // DoubleConsumer double ->void // Function : 参数类型T 返回值R // IntFunction int -> R // LongFunction long -> R // DoubleFunction double -> R // IntToLongFunction int -> long // IntToDoubleFunction int -> double // LongToIntFunction long -> int // LongToDoubleFunction long -> double // DoubleToLongFunction double -> long // DoubleToIntFunction double -> int // Supplier : 参数 无 返回值T // UnaryOperator :参数T 返回值 T // BiFunction : 参数 T、U 返回值 R // BinaryOperator :参数 T、T 返回值 T // BiPredicate : 参数T、U 返回值 boolean // BiConsumer : 参数T、U 无返回值 /** * 常用的 函数式接口 * Predicate、Consumer、Function、Supplier */ } }
七、Lambda闭包 package com.alan.closure; import java.util.function.Supplier; /** * @author Alan * @version 1.0 * @date 2020-05-27 16:59 */ public class ClosureDemo { public static void main(String[] args) { /** * lambda的闭包会提升包围变量的生命周期 * 所以局部变量 num在getNumber()方法内被 get()引用 不会在getNumber()方法执行后销毁 * 这种方法可以在外部获取到某一个方法的局部变量 */ int n=getNumber().get(); System.out.println(n); } private static Supplier getNumber(){ int num=10; /** * Supplier supplier=()->num; * return supplier; */ return ()->{ return num; }; } } ************************************************************************* package com.alan.closure; import java.util.function.Consumer; /** * @author Alan * @version 1.0 * @date 2020-05-27 17:20 */ public class ClosureDemo2 { public static void main(String[] args) { int a=10; Consumer c=ele->{ System.out.println(a+1); //System.out.println(ele); //System.out.println(a++); 会报错 //在lambda中引用局部变量 这个变量必须是一个常量 }; //a++; 这样也会导致内部报错 //如果在内部已经引用局部变量 参数传递后 打印的还是 10 c.accept(1); } }
软件开发
2020-07-22 12:00:00
前言:
请各位不要一直刷框架、刷源码大型项目了.......................
今年面试频繁失败的原因在哪儿?败就败在那些年被你遗忘的基础题上面了.
针对最常见、最常问的java面试题,我想这些你是必须要掌握的
不说精准,但是可以参考下,看那些是你掌握了,看一眼就知道原理及解决方案.可以查缺补漏看看.
Java集合22题(基础) ArrayList 和 Vector 的区别。 说说 ArrayList,Vector, LinkedList 的存储性能和特性。 快速失败 (fail-fast) 和安全失败 (fail-safe) 的区别是什么? hashmap 的数据结构。 HashMap 的工作原理是什么? Hashmap 什么时候进行扩容呢? List、Map、Set 三个接口,存取元素时,各有什么特点? Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用 == 还是 equals()? 它们有何区别? 两个对象值相同 (x.equals(y) == true),但却可有不同的 hash code,这句话对不对? heap 和 stack 有什么区别。 Java 集合类框架的基本接口有哪些? HashSet 和 TreeSet 有什么区别? HashSet 的底层实现是什么? LinkedHashMap 的实现原理? 为什么集合类没有实现 Cloneable 和 Serializable 接口? 什么是迭代器 (Iterator)? Iterator 和 ListIterator 的区别是什么? 数组 (Array) 和列表 (ArrayList) 有什么区别?什么时候应该使用 Array 而不是 ArrayList? Java 集合类框架的最佳实践有哪些? Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢?是用 == 还是 equals()?它们有何区别? Comparable 和 Comparator 接口是干什么的?列出它们的区别 Collection 和 Collections 的区别。
JVM与调优21题(算是大厂必问题) Java 类加载过程? 描述一下 JVM 加载 Class 文件的原理机制? Java 内存分配。 GC 是什么? 为什么要有 GC? 简述 Java 垃圾回收机制 如何判断一个对象是否存活?(或者 GC 对象的判定方法) 垃圾回收的优点和原理。并考虑 2 种回收机制 垃圾回收器的基本原理是什么?垃圾回收器可以马上回收内存吗?有什么办法主动通知虚拟机进行垃圾回收? Java 中会存在内存泄漏吗,请简单描述 深拷贝和浅拷贝。 System.gc() 和 Runtime.gc() 会做什么事情? finalize() 方法什么时候被调用?析构函数 (finalization) 的目的是什么? 如果对象的引用被置为 null,垃圾收集器是否会立即释放对象占用的内存? 串行(serial)收集器和吞吐量(throughput)收集器的区别是什么? 在 Java 中,对象什么时候可以被垃圾回收? 简述 Java 内存分配与回收策略以及 Minor GC 和 Major GC。 VM 的永久代中会发生垃圾回收么? Java 中垃圾收集的方法有哪些? 什么是类加载器,类加载器有哪些? 类加载器双亲委派模型机制?
并发编程28题(并发多重要,就不要我多说了) Synchronized 用过吗,其原理是什么? 你刚才提到获取对象的锁,这个“锁”到底是什么?如何确定对象的锁? 什么是可重入性,为什么说 Synchronized 是可重入锁? VM 对 Java 的原生锁做了哪些优化?48 为什么说 Synchronized 是非公平锁?49 什么是锁消除和锁粗化?49 为什么说 Synchronized 是一个悲观锁?乐观锁的实现原理又是什么?什么是 CAS,它有什么特性? 乐观锁一定就是好的吗? 跟 Synchronized 相比,可重入锁 ReentrantLock 其实现原理有什么不同? 那么请谈谈 AQS 框架是怎么回事儿? 请尽可能详尽地对比下 Synchronized 和 ReentrantLock 的异同。 ReentrantLock 是如何实现可重入性的? 除了 ReetrantLock,你还接触过 JUC 中的哪些并发工具? 请谈谈 ReadWriteLock 和 StampedLock。 如何让 Java 的线程彼此同步?你了解过哪些同步器?请分别介绍下。 CyclicBarrier 和 CountDownLatch 看起来很相似,请对比下呢? Java 线程池相关问题 Java 中的线程池是如何实现的? 创建线程池的几个核心构造参数? 线程池中的线程是怎么创建的?是一开始就随着线程池的启动创建好的吗? 既然提到可以通过配置不同参数创建出不同的线程池,那么 Java 中默认实现好的线程池又有哪些呢?请比较它们的异同 如何在 Java 线程池中提交线程? 什么是 Java 的内存模型,Java 中各个线程是怎么彼此看到对方的变量的? 请谈谈 volatile 有什么特点,为什么它能保证变量对所有线程的可见性? 既然 volatile 能够保证线程间的变量可见性,是不是就意味着基于 volatile 变量的运算就是并发安全的? 请对比下 volatile 对比 Synchronized 的异同。 请谈谈 ThreadLocal 是怎么解决并发安全的? 很多人都说要慎用 ThreadLocal,谈谈你的理解,使用 ThreadLocal 需要注意些什么?
spring 25题(开源框架中的核心知识点) 1、什么是 Spring 框架?Spring 框架有哪些主要模块? 2、使用 Spring 框架能带来哪些好处? 3、什么是控制反转(IOC)?什么是依赖注入? 4、请解释下 Spring 框架中的 IoC? 5、BeanFactory 和 ApplicationContext 有什么区别? 6、Spring 有几种配置方式? 7、如何用基于 XML 配置的方式配置 Spring? 8、如何用基于 Java 配置的方式配置 Spring? 9、怎样用注解的方式配置 Spring? 10、请解释 Spring Bean 的生命周期? 11、Spring Bean 的作用域之间有什么区别? 12、什么是 Spring inner beans? 13、Spring 框架中的单例 Beans 是线程安全的么? 14、请举例说明如何在 Spring 中注入一个 Java Collection? 15、如何向 Spring Bean 中注入一个 Java.util.Properties? 16、请解释 Spring Bean 的自动装配? 17、请解释自动装配模式的区别? 18、如何开启基于注解的自动装配? 19、请举例解释@Required 注解? 20、请举例解释@Autowired 注解? 21、请举例说明@Qualifier 注解? 22、构造方法注入和设值注入有什么区别? 23、Spring 框架中有哪些不同类型的事件? 24、FileSystemResource 和 ClassPathResource 有何区别? 25、Spring 框架中都用到了哪些设计模式?
设计模式 10题 1.请列举出在 JDK 中几个常用的设计模式? 2.什么是设计模式?你是否在你的代码里面使用过任何设计模式? 3.Java 中什么叫单例设计模式?请用 Java 写出线程安全的单例模式 4.在 Java 中,什么叫观察者设计模式(observer design pattern)? 5.使用工厂模式最主要的好处是什么?在哪里使用? 6.举一个用 Java 实现的装饰模式(decorator design pattern)?它是作用于对象层次还是类 层次? 7.在 Java 中,为什么不允许从静态方法中访问非静态变量? 8.设计一个 ATM 机,请说出你的设计思路? 9.在 Java 中,什么时候用重载,什么时候用重写? 10.举例说明什么情况下会更倾向于使用抽象类而不是接口
springboot 22题(中小型企业的占比使用率较高,还有spring cloud) 什么是 Spring Boot? Spring Boot 有哪些优点? 什么是 JavaConfig? 如何重新加载 Spring Boot 上的更改,而无需重新启动服务器? Spring Boot 中的监视器是什么? 如何在 Spring Boot 中禁用 Actuator 端点安全性? 如何在自定义端口上运行 Spring Boot 应用程序? 什么是 YAML? 如何实现 Spring Boot 应用程序的安全性? 如何集成 Spring Boot 和 ActiveMQ? 如何使用 Spring Boot 实现分页和排序? 什么是 Swagger?你用 Spring Boot 实现了它吗? 什么是 Spring Profiles? 什么是 Spring Batch? 什么是 FreeMarker 模板? 如何使用 Spring Boot 实现异常处理? 您使用了哪些 starter maven 依赖项? 什么是 CSRF 攻击? 什么是 WebSockets? 什么是 AOP? 什么是 Apache Kafka? 我们如何监视所有 Spring Boot 微服务?
Netty10题 BIO、NIO和AIO的区别? NIO的组成? Netty的特点? Netty的线程模型? TCP 粘包/拆包的原因及解决方法? 了解哪几种序列化协议? 如何选择序列化协议? Netty的零拷贝实现? Netty的高性能表现在哪些方面? NIOEventLoopGroup源码?
Redis 16题(优势以及使用场景都需要了解一下) 什么是redis? Reids的特点 Redis支持的数据类型 Redis是单进程单线程的 虚拟内存 Redis锁 读写分离模型 数据分片模型 Redis的回收策略 使用Redis有哪些好处? redis相比memcached有哪些优势?4 redis常见性能问题和解决方案 MySQL里有2000w数据,redis中只存20w的数据,如何保证redis中的数据都是热点数据245 Memcache与Redis的区别都有哪些? Redis 常见的性能问题都有哪些?如何解决? Redis 最适合的场景 篇幅有限,这里收集了各方面的,当前公司的,还有自己收集总结的,下面的图片截取的有pdf,有如果有需要的自取.
各大公司面试题集合:
简历模板:
链接:  https://pan.baidu.com/s/1DO6XGkbmak7KIt6Y7JQqyw
提取码:fgj6
不知道会不会失效,如果失效 点击(778490892) 或者扫描下面二维码,进群获取,链接补发不过来,谢谢。
最后
欢迎大家评论区一起交流,相互提升;整理资料不易,如果喜欢文章记得点个赞哈,感谢大家支持!!!
软件开发
2020-07-22 11:38:00
第二十章 多核编程
Table of Contents 第二十章 多核编程 20.1 如何在多核的CPU上更有效率的运行 20.1.1 使用大量进程 20.1.2 避免副作用 20.1.3 顺序瓶颈 20.2 并行化顺序代码 20.3 小消息, 大计算 20.4 map-reduce算法和磁盘索引程序 20.4.1 map-reduce算法 20.4.2 全文检索 20.4.3 索引器的操作 20.4.4 运行索引器 20.4.5 评论 20.4.6 索引器的代码
第二十章 多核编程 20.1 如何在多核的CPU上更有效率的运行 20.1.1 使用大量进程 这个标准…显而易见。 20.1.2 避免副作用 因为存在副作用, 导致使用共享内存方式时必须使用锁机制, 虽然Erlang没有共享内存, 但对于可以被多个进程共享的ETS表和DETS表还是应该特别注意。 20.1.3 顺序瓶颈 对于本质就是顺序性的问题, 显然无法做到并发化。
而磁盘IO, 也是一个无法避免的自然瓶颈。
注册进程, 人为的创建了一个潜在的顺序瓶颈。 20.2 并行化顺序代码 并行化的map pmap ( F , L ) -> S = self (), Ref = erlang : make_ref (), %% 对于列表中的每个参数都启动一个进程去处理 Pids = map ( fun ( I ) -> spawn ( fun () -> do_f ( S , Ref , F , I ) end ) end , L ), gather ( Pids , Ref ). %% 处理完成后向父进程发送结果 do_f ( Parent , Ref , F , I ) -> Parent ! { self (), Ref , ( catch F ( I ))}. %% 以正确的顺序拼接每个进程的运行结果 gather ([ Pid | T ], Ref ) -> receive { Pid , Ref , Ret } ->[ Ret | gather ( T , Ref )] end ; gather ([], _ ) ->[].
什么时候可以用pmap:1. 计算量很小的函数; 2. 不创建太多的进程; 3. 在恰当的抽象层次上思考 20.3 小消息, 大计算 启动SMP Erlang # -smp 启动SMP Erlang # +S N 使用N个Erlang虚拟机 $ erl -smp +S N
测试不同的虚拟机数量对性能的影响 # !/bin/ sh echo "" >results for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 do echo $ i erl -boot start_clean -noshell -smp +S $ i -s ptests tests $ i >> results done 20.4 map-reduce算法和磁盘索引程序 20.4.1 map-reduce算法 %% map函数 MapReduce每次给列表中的每个X创建一个新的进程 F1 = fun ( Pid , X ) ->void, %% reduce函数 针对每个键值, 将它所对应的所有值合并到一起 %% Acc0 累加器 F2 = fun (key, [ Value ], Acc0 ) -> Acc L = [ X ] Acc = X = term () %% 调用形式 mapreduce ( F1 , F2 , Acc0 , L ) -> Acc
具体的实现 mapreduce ( F1 , F2 , Acc0 , L ) -> S = self (), %% 启动新的进程运行reduce函数 Pid = spawn ( fun () -> reduce ( S , F1 , F2 , Acc0 , L ) end ), receive { Pid , Result } -> Result end . reduce ( Parent , F1 , F2 , Acc0 , L ) -> process_flag (trap_exit, true), ReducePid = self (), %% map过程的实现 %% 对于列表中的每个值都启动一个进程在do_job中调用F1进行处理 foreach ( fun ( X ) -> spawn_link ( fun () -> do_job ( ReducePid , F1 , X ) end ) end , L ), N = length ( L ), %% 用字典存储键值 Dict0 = dict : new (), %% 等待map过程完成 Dict1 = collect_replies ( N , Dict0 ), %% 调用F2按相同键值进行合并 Acc = dict : fold ( F2 , Acc0 , Dict1 ), %% 向MapReduce进程通知运行结果 Parent ! { self (), Acc }. %% 按键值进行合并的过程 collect_replies (0, Dict ) -> Dict ; collect_replies ( N , Dict ) -> receive %% 对键-值的处理 %% 存在Key则将Val相加, 否则插入到字典 { Key , Val } -> case dict : is_key ( Key , Dict ) of true -> Dict1 = dict : append ( Key , Val , Dict ), collect_replies ( N , Dict1 ); false -> Dict1 = dict : store ( Key ,[ Val ], Dict ), collect_replies ( N , Dict1 ) end ; { 'EXIT' , _ , _Why } -> collect_replies ( N -1, Dict ) end . %% 执行指定的map函数 do_job ( ReducePid , F , X ) -> F ( ReducePid , X ).
测试代码: -module (test_mapreduce). -compile (export_all). -import (lists, [ reverse/1 , sort/1 ]). test () -> wc_dir ( "." ). wc_dir ( Dir ) -> %% map函数 F1 = fun generate_words/2 , %% reduce函数 F2 = fun count_words/3 , %% 参数列表 Files = lib_find : files ( Dir , ".*[.](erl)" , false), %% 调用mapreduce处理 L1 = phofs : mapreduce ( F1 , F2 , [], Files ), reverse ( sort ( L1 )). %% 查找文件中的每个单词 generate_words ( Pid , File ) -> F = fun ( Word ) -> Pid ! { Word , 1} end , lib_misc : foreachWordInFile ( File , F ). %% 统计有多少个不同的单词 count_words ( Key , Vals , A ) -> [{ length ( Vals ), Key }| A ].
运行结果: 1> test_mapreduce:test(). [{115,"L"}, {84,"T"}, {80,"1"}, {77,"end"}, {72,"X"}, {52,"H"}, {47,"file"}, {46,"S"}, {44,"of"}, {43,"F"}, {40,"2"}, {39,"Key"}, {39,"Fun"}, {37,"is"}, {35,"case"}, {34,"fun"}, {34,"Pid"}, {34,"N"}, {33,"File"}, {32,"true"}, {31,"Str"}, {28,"ok"}, {27,"prefix"}, {27,"Val"}, {27,"I"}, {26,"to"}, {26,[...]}, {24,...}, {...}|...] 20.4.2 全文检索 1. 反向索引
文件-内容对照表
文件名 内容 /home/dogs rover jack buster winston
/home/animals/cats /home/cars
zorro daisy jaguar rover jaguar ford
索引-文件对照表
索引 文件名 1 /home/dogs
2 3
/home/animals/cats /home/cars
单词-索引对照表
单词 索引 rover 1,3
jack 1
buster 1
winston 1
zorro 2
daisy 2
jaguar
ford
2,3
3
2. 反向索引的查询
通过单词-索引, 索引-文件的对照表查找单词与文件的对应关系 3. 反向索引的数据结构
因为一个常见的词可能在成千上万的文件中出现, 因此使用数字索引代替文件名可大大节省存储空间, 因此需要文件与索引的对照表。
对于每个在文件中出现的单词, 都需要记录此文件的索引号, 因此建立单词与索引的对照表。 20.4.3 索引器的操作 %% 启动一个名为indexer_server的服务器进程 %% 启动一个worker进程来执行索引动作 start () -> indexer_server : start ( output_dir ()), spawn_link ( fun () -> worker () end ). worker () -> possibly_stop (), %% 返回下一个需要索引的目录 case indexer_server : next_dir () of {ok, Dir } -> %% 查找目录下需要进行索引的文件 Files = indexer_misc : files_in_dir ( Dir ), %% 为其建立索引 index_these_files ( Files ), %% 检测是否正常完成 indexer_server : checkpoint (), possibly_stop (), sleep (10000), worker (); done -> true end . %% 使用MapReduce算法实现建立索引的并行处理 index_these_files ( Files ) -> Ets = indexer_server : ets_table (), OutDir = filename : join ( indexer_server : outdir (), "index" ), %% map函数 F1 = fun ( Pid , File ) -> indexer_words : words_in_file ( Pid , File , Ets ) end , %% reduce函数 F2 = fun ( Key , Val , Acc ) -> handle_result ( Key , Val , OutDir , Acc ) end , indexer_misc : mapreduce ( F1 , F2 , 0, Files ). %% 按照Key值进行合并 handle_result ( Key , Vals , OutDir , Acc ) -> add_to_file ( OutDir , Key , Vals ), Acc + 1. %% 将索引数组添加到Word中 add_to_file ( OutDir , Word , Is ) -> L1 = map ( fun ( I ) -><< I :32>> end , Is ), OutFile = filename : join ( OutDir , Word ), case file : open ( OutFile , [write,binary,raw,append]) of {ok, S } -> file : pwrite ( S , 0, L1 ), file : close ( S ); {error, E } -> exit ({ebadFileOp, OutFile , E }) end . 20.4.4 运行索引器 1> indexer:cold_start(). 2> indexer:start(). 3> indexer:stop(). 20.4.5 评论 可以改进的三个方面 1. 改进单词抽取 2. 改进map-reduce算法, 以便处理海量数据 3. 方向索引的数据结构只使用了文件系统来存储 20.4.6 索引器的代码
软件开发
2020-07-22 08:20:00
前言
工作三年有余, 第一份工作是在一家外包公司 ,第二份工作是在一家做SAAS平台的公司
第一家公司让我入门,进入了软件开发的行业,了解了一些基础的东西 ;
第二家公司由于规模不大,很多活儿都是一个人来做 ,从产品到开发、测试、运维我都了解了,对于整体的软件开发流程有了很大的了解。
但是,为了自己的更好发展,决定去一家大公司,于5月初提出了离职,虽然领导挽留,但是
我还是离职了,然后我自己面试了很多家公司,大公司小公司都有,积累了足够的经验,开始
向大公司投递简历,面了几家之后,终于拿到了自己满意的offer(已入职阿里),我是在积
累了足够的经验之后才提出离职的,离职时一件很重大的事情,自己要慎重考虑,要从职业发
展和个人规划上考虑清楚再去做出选择。
大公司面试考验基础和项目经验,小公司比较偏向于项目经验。
这里我都总结下,给大家一个参考,希望可以帮助到正在面临跳槽的同志 。
面试整体事项 1.简历要准备好,联系方式一定要正确清晰醒目,项目经历按照时间倒序阐述,注意描述自己在项目中承担的职责,简历的模板尽量选择简洁的,毕竟程序员大部分还是喜欢简单明了的。 2.推荐boss直聘,我觉得很好用(不是广告)。 3.一般的整体面试流程都是电面->现场面->HR面->等着。 4.不要觉得HR说让你回去等消息就是GG了,他们也要跟你之前的面试官讨论,再向领导汇报,如果说不急可能还要和其他候选人比较,所以HR让你回去等消息绝对不是说明你完蛋了。 5.面试前准备好自我介绍,1分钟左右就可以,可以写在纸上,电面可以照着念,等你到了现场面了基本也都快背下来你的自我介绍了。 6.准备好扎实的基础,这是一切的根源,没实力怎么都没用的。 7.面试中你可以把你的面试官往你会的知识上引导(我遇到过你会什么他不问什么的)。 8.遇到了设计类题目不要着急,面试官不是为了让你几分钟设计一个高并发高可用设计模式完美的架构,只是想看看你的思路,看看你应变的能力,然后给你些提示看看你能否迅速的调整。 9.offer都会有的,不要着急,把面试当成一个交流的过程。
需要准备的知识
以下为在近期面试中比较有印象的问题,也就不分公司了,因为没什么意义,大致分类记录一下。
目前只想起这么多,不过一定要知道这些问题只是冰山一角,就算都会了也不能怎么样,最最
重要的,还是坚实的基础,清醒的头脑。
Java基础 1.HashMap的源码,实现原理,JDK8中对HashMap做了怎样的优化。 2.HaspMap扩容是怎样扩容的,为什么都是2的N次幂的大小。 3.HashMap,HashTable,ConcurrentHashMap的区别。 4.极高并发下HashTable和ConcurrentHashMap哪个性能更好,为什么,如何实现的。 5.HashMap在高并发下如果没有处理线程安全会有怎样的安全隐患,具体表现是什么。 6.java中四种修饰符的限制范围。 7.Object类中的方法。 8.接口和抽象类的区别,注意JDK8的接口可以有实现。 9.动态代理的两种方式,以及区别。 10.Java序列化的方式。 11.传值和传引用的区别,Java是怎么样的,有没有传值引用。 12.一个ArrayList在循环过程中删除,会不会出问题,为什么。 13.@transactional注解在什么情况下会失效,为什么。
数据结构和算法 1.B+树 2.快速排序,堆排序,插入排序(其实八大排序算法都应该了解 3.一致性Hash算法,一致性Hash算法的应用
JVM 1.JVM的内存结构。 2.JVM方法栈的工作过程,方法栈和本地方法栈有什么区别。 3.JVM的栈中引用如何和堆中的对象产生关联。 4.可以了解一下逃逸分析技术。 5.GC的常见算法,CMS以及G1的垃圾回收过程,CMS的各个阶段哪两个是Stop the world的,CMS会不会产生碎片,G1的优势。 6.标记清除和标记整理算法的理解以及优缺点。 7.eden survivor区的比例,为什么是这个比例,eden survivor的工作过程。 8.JVM如何判断一个对象是否该被GC,可以视为root的都有哪几种类型。 9.强软弱虚引用的区别以及GC对他们执行怎样的操作。 10.Java是否可以GC直接内存。 11.Java类加载的过程。 12.双亲委派模型的过程以及优势。 13.常用的JVM调优参数。 14.dump文件的分析。 15.Java有没有主动触发GC的方式(没有)。
多线程 1.Java实现多线程有哪几种方式。 2.Callable和Future的了解。 3.线程池的参数有哪些,在线程池创建一个线程的过程。 4.volitile关键字的作用,原理。 5.synchronized关键字的用法,优缺点。 6.Lock接口有哪些实现类,使用场景是什么。 7.可重入锁的用处及实现原理,写时复制的过程,读写锁,分段锁(ConcurrentHashMap中的segment)。 8.悲观锁,乐观锁,优缺点,CAS有什么缺陷,该如何解决。 9.ABC三个线程如何保证顺序执行。 10.线程的状态都有哪些。 11.sleep和wait的区别。 12.notify和notifyall的区别。 13.ThreadLocal的了解,实现原理。
数据库相关 1.常见的数据库优化手段 2.索引的优缺点,什么字段上建立索引 3.数据库连接池。 4.durid的常用配置。
计算机网络 1.TCP,UDP区别。 2.三次握手,四次挥手,为什么要四次挥手。 3.长连接和短连接。 4.连接池适合长连接还是短连接。
设计模式 1.观察者模式 2.代理模式 3.单例模式,有五种写法,可以参考文章单例模式的五种实现方式 4.可以考Spring中使用了哪些设计模式
分布式相关 1.分布式事务的控制。 2.分布式锁如何设计。 3.分布式session如何设计。 4.dubbo的组件有哪些,各有什么作用。 5.zookeeper的负载均衡算法有哪些。 6.dubbo是如何利用接口就可以通信的。
缓存相关 1.redis和memcached的区别。 2.redis支持哪些数据结构。 3.redis是单线程的么,所有的工作都是单线程么。 4.redis如何存储一个String的。 5.redis的部署方式,主从,集群。 6.redis的哨兵模式,一个key值如何在redis集群中找到存储在哪里。 7.redis持久化策略。
框架相关 1.SpringMVC的Controller是如何将参数和前端传来的数据一一对应的。 2.Mybatis如何找到指定的Mapper的,如何完成查询的。 3.Quartz是如何完成定时任务的。 4.自定义注解的实现。 5.Spring使用了哪些设计模式。 6.Spring的IOC有什么优势。 7.Spring如何维护它拥有的bean。
一些较新的东西 1.JDK8的新特性,流的概念及优势,为什么有这种优势。 2.如何设计双11交易总额面板,要做到高并发高可用。
一些小建议 1.可以去leetcode上刷题换换思路。 2.八大排序算法一定要手敲一遍(快排,堆排尤其重要)。 3.了解一些新兴的技术。 4.面试之后面试官都会问你有没有什么问题,千万不要没问题,也别傻乎乎的问一些敏感问题。 5.了解你要面试的公司的产品及竞争产品。
无论是哪家公司,都很重视高并发高可用的技术,重视基础,重视JVM 。
面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。
同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。其实
写了这么多,只是我自己的总结,并不一定适用于所有人,相信经过一些面试,大家都会有这些感触。 篇幅有限,这里收集了各方面的,当前公司的,还有自己收集总结的,下面的图片截取的有pdf,有如果有需要的自取.
各大公司面试题集合:
简历模板:
链接:  https://pan.baidu.com/s/1DO6XGkbmak7KIt6Y7JQqyw
提取码:fgj6
不知道会不会失效,如果失效 点击(778490892) 或者扫描下面二维码,进群获取,链接补发不过来,谢谢。
最后
欢迎大家评论区一起交流,相互提升;整理资料不易,如果喜欢文章记得点个赞哈,感谢大家支持!!!
软件开发
2020-07-21 17:40:00
invokeBeanFactoryPostProcessors protected void invokeBeanFactoryPostProcessors(ConfigurableListableBeanFactory beanFactory) { PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(beanFactory, this.getBeanFactoryPostProcessors()); if (beanFactory.getTempClassLoader() == null && beanFactory.containsBean("loadTimeWeaver")) { beanFactory.addBeanPostProcessor(new LoadTimeWeaverAwareProcessor(beanFactory)); beanFactory.setTempClassLoader(new ContextTypeMatchClassLoader(beanFactory.getBeanClassLoader())); } }
PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors
执行post方法的类
此时有三个明显的BeanFactoryPostProcessor Springboot准备过程已经添加好了这三个 0 = {SharedMetadataReaderFactoryContextInitializer$CachingMetadataReaderFactoryPostProcessor@4085} 1 = {ConfigurationWarningsApplicationContextInitializer$ConfigurationWarningsPostProcessor@4086} 2 = {ConfigFileApplicationListener$PropertySourceOrderingPostProcessor@4087}
这三个看着没啥用,不分析了 然而下边还有其他的更重要的BeanFactoryPostProcesser
invokeBeanDefinitionRegistryPostProcessors
关键代码就这两句 ..... //获取到了一个叫org.springframework.context.annotation.internalConfigurationAnnotationProcessor的bean postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); .... //实例化一下 currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); ..... //执行一下注册方法 invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
由于逻辑比较复杂,具体逻辑请转入《6.1 Spring启动流程-internalConfigurationAnnotationProcessor》
CachingMetadataReaderFactoryPostProcessor
简单看一下CachingMetadataReaderFactoryPostProcessor 干了啥吧 public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException { this.register(registry); this.configureConfigurationClassPostProcessor(registry); } private void register(BeanDefinitionRegistry registry) { BeanDefinition definition = BeanDefinitionBuilder.genericBeanDefinition(SharedMetadataReaderFactoryContextInitializer.SharedMetadataReaderFactoryBean.class, SharedMetadataReaderFactoryContextInitializer.SharedMetadataReaderFactoryBean::new).getBeanDefinition(); registry.registerBeanDefinition("org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory", definition); } private void configureConfigurationClassPostProcessor(BeanDefinitionRegistry registry) { try { //在这里给internalConfigurationAnnotationProcessor添加了一个metadataReaderFactory属性 BeanDefinition definition = registry.getBeanDefinition("org.springframework.context.annotation.internalConfigurationAnnotationProcessor"); definition.getPropertyValues().add("metadataReaderFactory", new RuntimeBeanReference("org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory")); } catch (NoSuchBeanDefinitionException var3) { ; } }
经过他之后加了一个 6 = "org.springframework.boot.autoconfigure.internalCachingMetadataReaderFactory"
软件开发
2020-07-21 15:56:00
项目地址:https://github.com/jinyousen/blog/tree/master/problem/master-slave-switch
该工程使用spring boot 和 Mybatis 实现多数据源,动态数据源切换。以及在过程遇到Spring事务执行顺序与数据源切换执行顺序设置
数据源动态切换由conf/dal 包下4个类实现;
DynamicDataSource.java
DataSourceConfig.java
TargetDataSource.java
DataSourceAspect.java
DynamicDataSource.java
利用ThreadLocal存取数据源名称
DynamicDataSource继承 AbstractRoutingDataSource.java 重写父类 determineCurrentLookupKey 获取当前线程链接的数据源名
public class DynamicDataSource extends AbstractRoutingDataSource {

/**
* 本地线程共享对象
* 动态数据源持有者,负责利用ThreadLocal存取数据源名称
*/
private static final ThreadLocal THREAD_LOCAL = new ThreadLocal<>();

public static void putDataSource(String name) {
THREAD_LOCAL.set(name);
}

public static String getDataSource() {
return THREAD_LOCAL.get();
}

public static void removeDataSource() {
THREAD_LOCAL.remove();
}

@Override
protected Object determineCurrentLookupKey() {
return getDataSource();
}
}
DataSourceConfig.java
配置数据源,从配置文件中获取数据源,放入DynamicDataSource Bean单例对象中
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}

@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
return druidDataSource;
}

/**
* @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
* @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
*/
@Bean
@Primary
public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource userDataSource
) {
//按照目标数据源名称和目标数据源对象的映射存放在Map中
Map targetDataSources = new HashMap<>();
targetDataSources.put(DalConstant.DATA_SOURCE_MASTER, masterDataSource);
targetDataSources.put(DalConstant.DATA_SOURCE_SLAVE, userDataSource);

//采用是想AbstractRoutingDataSource的对象包装多数据源
DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSources);
//设置默认的数据源,当拿不到数据源时,使用此配置
dataSource.setDefaultTargetDataSource(masterDataSource);
return dataSource;
}
TargetDataSource.java
标注方法调用的数据源名称
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface TargetDataSource {
String value();
}
DataSourceAspect.java
自定义实现Aspect切面,获取当前执行方法名 TargetDataSource 注解上调用的数据源名,修改ThreadLocal中数据源名
/*
* 定义一个切入点
*/
@Pointcut("execution(* org.yasser.service.*..*(..))")
public void dataSourcePointCut() {
}

/*
* 通过连接点切入
*/
@Before("dataSourcePointCut()")
public void doBefore(JoinPoint joinPoint) {
try {
String method = joinPoint.getSignature().getName();
Object target = joinPoint.getTarget();
Class[] classz = target.getClass().getInterfaces();
Class[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
Method m = classz[0].getMethod(method, parameterTypes);
if (m != null && m.isAnnotationPresent(TargetDataSource.class)) {
TargetDataSource data = m.getAnnotation(TargetDataSource.class);
String dataSourceName = data.value();
DynamicDataSource.putDataSource(dataSourceName);
}
} catch (Throwable e) {
e.printStackTrace();
DynamicDataSource.putDataSource(DalConstant.DATA_SOURCE_MASTER);
log.error("DataSourceAspect is error!", e);
}
}
工程中对于数据库的异常操作,我们将会创建事务进行回滚。在创建事务的过程中博主犯了一个错误:
spring中有BeanNameAutoProxyCreator和AnnotationAwareAspectJAutoProxyCreator两种AOP代理方式
1、匹配Bean的名称自动创建匹配到的Bean的代理,实现类BeanNameAutoProxyCreator
2、根据Bean中的AspectJ注解自动创建代理,实现类AnnotationAwareAspectJAutoProxyCreator
3、根据Advisor的匹配机制自动创建代理,会对容器中所有的Advisor进行扫描,自动将这些切面应用到匹配的Bean中,实现类DefaultAdvisorAutoProxyCreator
BeanNameAutoProxyCreator拦截优先级高于AnnotationAwareAspectJAutoProxyCreator
在我们数据源切换的DataSourceAspect中我们采用了 AspectJ注解开发,使用了AnnotationAwareAspectJAutoProxyCreator代理方式实现AOP
但是如果我们在创建事务时使用BeanNameAutoProxyCreator代理方式,则事务的代理优先级高于AnnotationAwareAspectJAutoProxyCreator。这也就导致我们事务切换无效,且Order注解设置无效。
对于数据源动态切换的事务代理选择方式,应选择AnnotationAwareAspectJAutoProxyCreator

@Bean
public AnnotationAwareAspectJAutoProxyCreator txProxy() {
/*
* 必须使用AspectJ方式的AutoProxy,这样才能和DataSourceSwitchAspect保持统一的aop拦截方式,否则不同的拦截方式会导致order失效
*/
AnnotationAwareAspectJAutoProxyCreator creator = new AnnotationAwareAspectJAutoProxyCreator();
creator.setInterceptorNames("txAdvice");
creator.setIncludePatterns(Arrays.asList("execution (public org.yasser..*Service(..))"));
creator.setProxyTargetClass(true);
creator.setOrder(2);
return creator;
}
总结
数据源动态切换主要由重写AbstractRoutingDataSource中determineTargetDataSource()方法,ThreadLocal 存储数据源名
使用AspectJ方式,获取方法上注解value得知当前方法所需数据源名,修改ThreadLocal中数据源名
启用事务时一定要注意代理方式的选择
开源插件MyBatis-Plus支持动态数据源切换 https://mp.baomidou.com/guide/dynamic-datasource.html


————————————————
版权声明:本文为CSDN博主「yasserJin」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yasserJin/article/details/103134399/
软件开发
2020-07-20 14:22:00
initHandlerMappings private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; if (this.detectAllHandlerMappings) { Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerMappings); } } else { try { HandlerMapping hm = (HandlerMapping)context.getBean("handlerMapping", HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } catch (NoSuchBeanDefinitionException var3) { ; } } if (this.handlerMappings == null) { this.handlerMappings = this.getDefaultStrategies(context, HandlerMapping.class); if (this.logger.isTraceEnabled()) { this.logger.trace("No HandlerMappings declared for servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties"); } } }
就是这么简单 从spring容器中获取HandlerMapping的bean 如果没有就创建一个默认的(默认文件就在mvc的jar包里 DispatcherServlet.properties) 完活 然而springboot中HandlerMapping这些是怎么注入的呢 默认的hanlerMapping实体有 0 = {PropertySourcedRequestMappingHandlerMapping@14317} 1 = {WebMvcEndpointHandlerMapping@14345} 2 = {ControllerEndpointHandlerMapping@14372} 3 = {RequestMappingHandlerMapping@14396} 4 = {BeanNameUrlHandlerMapping@15448}
initHandlerAdapters
与initHandlerMappings类似,从bean工程中获取HandlerAdapter的实现 private void initHandlerAdapters(ApplicationContext context) { this.handlerAdapters = null; if (this.detectAllHandlerAdapters) { Map matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerAdapters = new ArrayList(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerAdapters); } } else { try { HandlerAdapter ha = (HandlerAdapter)context.getBean("handlerAdapter", HandlerAdapter.class); this.handlerAdapters = Collections.singletonList(ha); } catch (NoSuchBeanDefinitionException var3) { ; } } if (this.handlerAdapters == null) { this.handlerAdapters = this.getDefaultStrategies(context, HandlerAdapter.class); if (this.logger.isTraceEnabled()) { this.logger.trace("No HandlerAdapters declared for servlet '" + this.getServletName() + "': using default strategies from DispatcherServlet.properties"); } } }
默认的hanlerMapping实体有 0 = {RequestMappingHandlerAdapter@6397} WebMvcAutoConfiguration 1 = {HttpRequestHandlerAdapter@6398} WebMvcConfigurationSupport 2 = {SimpleControllerHandlerAdapter@6399} WebMvcConfigurationSupport
软件开发
2020-07-20 12:37:00
看看 handlermapping 和 handleradapter都是怎么初始化的
HttpServletBean public final void init() throws ServletException { ....... //在这里 this.initServletBean(); }
servlet的初始化方法 在HttpServletBean 实现了
FrameworkServlet.initServletBean
初始化了springcontext环境 protected final void initServletBean() throws ServletException { ....... try { //初始化WebApplicationContext this.webApplicationContext = this.initWebApplicationContext(); this.initFrameworkServlet(); } catch (RuntimeException | ServletException var4) { this.logger.error("Context initialization failed", var4); throw var4; } . ....... } protected WebApplicationContext initWebApplicationContext() { WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext()); WebApplicationContext wac = null; if (this.webApplicationContext != null) { wac = this.webApplicationContext; if (wac instanceof ConfigurableWebApplicationContext) { ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)wac; if (!cwac.isActive()) { if (cwac.getParent() == null) { cwac.setParent(rootContext); } this.configureAndRefreshWebApplicationContext(cwac); } } } if (wac == null) { wac = this.findWebApplicationContext(); } if (wac == null) { wac = this.createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { Object var6 = this.onRefreshMonitor; synchronized(this.onRefreshMonitor) { //最终会调用这个方法 this.onRefresh(wac); } } if (this.publishContext) { String attrName = this.getServletContextAttributeName(); this.getServletContext().setAttribute(attrName, wac); } return wac; } @Nullable protected WebApplicationContext findWebApplicationContext() { String attrName = this.getContextAttribute(); if (attrName == null) { return null; } else { WebApplicationContext wac = WebApplicationContextUtils.getWebApplicationContext(this.getServletContext(), attrName); if (wac == null) { throw new IllegalStateException("No WebApplicationContext found: initializer not registered?"); } else { return wac; } } } protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) { Class contextClass = this.getContextClass(); if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext"); } else { ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass); wac.setEnvironment(this.getEnvironment()); wac.setParent(parent); String configLocation = this.getContextConfigLocation(); if (configLocation != null) { wac.setConfigLocation(configLocation); } this.configureAndRefreshWebApplicationContext(wac); return wac; } }
initWebApplicationContext 调用了onrefresh方法 真正初始化的时机到了
onRefresh
初始化时机 在这里 protected void onRefresh(ApplicationContext context) { this.initStrategies(context); } protected void initStrategies(ApplicationContext context) { //用于处理上传请求。处理方法是将普通的request包装成MultipartHttpServletRequest,后者可以直接调用getFile方法获取File. initMultipartResolver(context); //SpringMVC主要有两个地方用到了Locale:一是ViewResolver视图解析的时候;二是用到国际化资源或者主题的时候。 initLocaleResolver(context); //用于解析主题。SpringMVC中一个主题对应一个properties文件,里面存放着跟当前主题相关的所有资源、 //如图片、css样式等。SpringMVC的主题也支持国际化, initThemeResolver(context); //用来查找Handler的。 initHandlerMappings(context); //从名字上看,它就是一个适配器。Servlet需要的处理方法的结构却是固定的,都是以request和response为参数的方法。 //如何让固定的Servlet处理方法调用灵活的Handler来进行处理呢?这就是HandlerAdapter要做的事情 initHandlerAdapters(context); //其它组件都是用来干活的。在干活的过程中难免会出现问题,出问题后怎么办呢? //这就需要有一个专门的角色对异常情况进行处理,在SpringMVC中就是HandlerExceptionResolver。 initHandlerExceptionResolvers(context); //有的Handler处理完后并没有设置View也没有设置ViewName,这时就需要从request获取ViewName了, //如何从request中获取ViewName就是RequestToViewNameTranslator要做的事情了。 initRequestToViewNameTranslator(context); //ViewResolver用来将String类型的视图名和Locale解析为View类型的视图。 //View是用来渲染页面的,也就是将程序返回的参数填入模板里,生成html(也可能是其它类型)文件。 initViewResolvers(context); //用来管理FlashMap的,FlashMap主要用在redirect重定向中传递参数。 initFlashMapManager(context); }
主要关注 的方法有 initHandlerMappings initHandlerAdapters initHandlerExceptionResolvers
软件开发
2020-07-20 12:36:00
Java是一种简单的,面向对象的,分布式的,解释型的,健壮安全的,结构中立的,可移植的,性能优异、多线程的静态语言。那么java语言的特点是什么呢?
1、Java语言是简单的
Java 语言的语法与C语言和C++语言很接近,使得大多数程序员很容易学习和使用Java。另一方面,Java丢弃了C++中很少使用的、很难理解的、令人迷惑 的那些特性,如操作符重载、多继承、自动的强制类型转换。特别地,Java语言不使用指针,并提供了自动的废料收集,使得程序员不必为内存管理而担忧。
2.java语言是一个面向对象的
Java 语言提供类、接口和继承等原语,为了简单起见,只支持类之间的单继承,但支持接口之间的多继承,并支持类与接口之间的实现机制(关键字为 implements)。贵州学致私教java培训讲师表示,Java语言全面支持动态绑定,而C++语言只对虚函数使用动态绑定。总之,Java语言是 一个纯的面向对象程序设计语言。
3.java语言是分布式的
Java 语言支持Internet应用的开发,在基本的Java应用编程接口中有一个网络应用编程接口(java net),java 框架项目案例:www.1b23.com 它提供了用于网络应用编程的类库,包 括URL、URLConnection、Socket、ServerSocket等。Java的RMI(远程方法激活)机制也是开发分布式应用的重要手 段。
4.java语言是健壮的
Java的强类型机制、异常处理、废料的自动收集等是Java程序健壮性的重要保证。对指针的丢弃是Java的明智选择。Java的安全检查机制使得Java更具健壮性。
5.java语言是安全的
Java 通常被用在网络环境中,为此,Java提供了一个安全机制以防恶意代码的攻击。贵州学致私教java培训讲师表示,除了Java语言具有的许多安全特性以 外,Java对通过网络下载的类具有一个安全防范机制(类ClassLoader),如分配不同的名字空间以防替代本地的同名类、字节代码检查,并提供安 全管理机制(类SecurityManager)让Java应用设置安全哨兵。
6.java语言是体系结构中立的
Java程序(后缀为java的文件)在Java平台上被编译为体系结构中立的字节码格式(后缀为class的文件),然后可以在实现这个Java平台的任何系统中运行。这种途径适合于异构的网络环境和软件的分发。
7.java语言是可移植的
这种可移植性来源于体系结构中立性,另外,Java还严格规定了各个基本数据类型的长度。Java系统本身也具有很强的可移植性,Java编译器是用Java实现的,Java的运行环境是用ANSI C实现的。
8.java语言是解释型的
如前所述,Java程序在Java平台上被编译为字节码格式,然后可以在实现这个Java平台的任何系统中运行。在运行时,Java平台中的Java解释器对这些字节码进行解释执行,执行过程中需要的类在联接阶段被载入到运行环境中。
9.java是高能性的
与那些解释型的高级脚本语言相比,Java的确是高性能的。事实上,Java的运行速度随着JIT(Just-In-Time)编译器技术的发展越来越接近于C++。
10.java语言是多线程的
在 Java语言中,线程是一种特殊的对象,它必须由Thread类或其子(孙)类来创建。通常有两种方法来创建线程:其一,使用型构为 Thread(Runnable)的构造子将一个实现了Runnable接口的对象包装成一个线程,其二,从Thread类派生出子类并重写run方法, 使用该子类创建的对象即为线程。值得注意的是Thread类已经实现了Runnable接口,因此,任何一个线程均有它的run方法,而run方法中包含 了线程所要运行的代码。线程的活动由一组方法来控制。Java语言支持多个线程的同时执行,并提供多线程之间的同步机制(关键字为 synchronized)。
11.java语言是动态的
Java语言的设计目标之一是适应于动态变化的环境。Java程序需要的类能够动态地被载入到运行环境,也可以通过网络来载入所需要的类。这也有利于软件的升级。另外,Java中的类有一个运行时刻的表示,能进行运行时刻的类型检查。
软件开发
2020-07-20 12:04:00
最近在github上贡献源码修改的场合,碰见一个比较棘手的问题:fork开源代码到自己git空间上,在解决新的issue时,需要同步最新的代码到自己fork代码,继续在基础上开发,下面就介绍一下几种常见的方法: 删除原有项目,在重新fork。(这个比较耗费时间) 在电脑本地同时git clone原项目和fork项目,用git pull更新,然后对比,再git push到github上个人代码库。(也比较繁琐) 本地git clone已fork的项目,然后添加远程库为原项目地址。(注意点:要有项目访问权限) windows下的 github for desktop客户端.
删除原有项目,在重新fork :
方法如下: 点击已fork的repo内(注意是进入自己copy别人的repo页面,而不是原作者的页面) 点击settings的options 页面拉到最底,在Danger Zone直接delete this repository即可。需要输入repo的名字才能确认删除,这一步实际上就是取消fork了。
使用远程代码库方式: 首先先通过 github 的 web 页面 fork 目标的项目, 前提是自己已经设置好了git,并且配置了相应的权限. 使用git clone命令在本地克隆自己 fork 的项目: git clone https://github.com/YOUR-USERNAME/project—name 需要复制被自己fork的项目的git地址,切换到自己之前克隆的项目的路径下,使用: git remote -v , 就可以看到当 前项目的远程仓库配置 使用下面的命令: git remote add upstream 原始项目仓库的git地址 如果你继续使git remove -v命令查看的话,就会发现这个时候已经和原始的被fork的项目产生了关联。 想 保持项目同步的话,使用下面的命令: git fetch upstream git merge upstream/master


软件开发
2020-07-19 09:12:00