MSB与LSB

MSB和LSB指的分别是:Most Significant Bit和Least Significant Bit

最高有效位和最低有效位

最高有效位指二进制数中最高值的比特位。例如十进制数:15389。这个数对数值影响最大的是在万位上的数字1。而二进制数,例如:1010b,对这个二进制数影响最大的是最左边的数字1。

字节序的问题产生于编译平台所在的CPU不同。

C/C++语言编写的程序中,数据存储顺序与编译平台所在的CPU相关。而Java编写的程序则唯一采用big endian方式来存储数据。所有网络协议也都是采用big endian的方式来传输。因此big endian方式也称之为网络字节序。

MSB和LSB表示的是某个值中的哪一个数对该值的影响最大或最小。


Big Endian 大端(Motorola)

数据的高位字节放到内存的低地址端,低位字节放到内存的高地址端。

Little Endian 小端(Intel)

数据的低位字节放到内存的低地址端,高位字节放到内存的高地址端。

大端和小端所描述的是字节与字节之间的关系。


假设一个字节数组Array与内存对应,也就是Array[0]的内存地址在前面,也就是低地址位置;而Array[1]的地址在后面,也就是高地址后面。

因此大端就会把十六进制数0x1234中的12存入Array[0],34存入Array[1]。

小端会把34存入Array[0],12存入Array[1]。

递归

递归是一种思想

在变成中的递归方法是调用自身的方法。

最简单的情况称为基础情况(base case)或终止条件(stopping condition)。

子问题和原始问题具有相同的性质,所以可以使用不同的参数调用这个方法,这称作递归调用(recursive call)

一个递归调用可以产生更多的递归调用,因为这个方法继续把每个子问题分解成新的子问题。要终止一个递归方法,问题最后必须达到一个终止条件。

如果递归不能使问题简单化并最终收敛到基础情况,就有可能出现无限递归。

调用自身的递归方法,被称为直接递归。而当方法A调用方法B,而方法B再调用方法A,间接递归就产生了。甚至有更多的方法参与到递归中来。

任何递归问题都可以用非递归的迭代解决。

递归有很多副作用:它耗费了太多时间并占用了太多内存。

但为什么还要用递归呢?因为在某些情况下,递归特有的问题很难用其他方法解决,而递归可以给出一个清晰、简单的解决方案。

迭代通常比递归效率高。

尾递归对于减小栈大小比较有效。

SpringBoot的一些启动过程

SpringBoot的一些启动过程

在SpringBoot的启动类上有个注解叫@SpringBootApplication这个注解会让Spring去扫描被@SpringBootApplication修饰的类的文件夹中的Bean。

也就是说,如果被@SpringBootApplication修饰的启动类叫MyApplication,这个MyApplication如果在项目文件夹例如/com.demo,那Spring就会去扫描/com.demo路径下的所有Bean。

这个扫描发现Bean的方式有几种,在SpringBoot中可以用@Configration和@Bean来修饰一个类当作Bean。如果被扫描的类不在/com.demo中,那@SpringBootApplication就没法自动扫描到对应的Bean,只能通过其它方式来加载Bean。

如果Spring扫描不到配置类或Bean,那后续也谈不上使用Bean了。你无法去使用一个不存在的东西。

SpringBoot启动的时候,如果你的包导入了org.springframework.book:spring-boot-autocofigure,则会来解析包中的META-INF下的spring.factories。只要这个路径下有这个文件,那么SpringBoot会解析出你有哪些自动配置类。这些配置类不用被Spring扫描到就可以直接使用。

如果你的项目路径下有META-INF,也有spring.factories,并且格式保持一致,那就不需要在配置类上添加@Configration注解,这个类就可以被Spring解析到。这是SpringBoot的特性。

这个特性怎么来的,就是SpringBoot需要去支持一些我们程序扫描不到的位置的类。比如有些在包中的类,SpringBoot需要用到,但我们的程序@Configraion扫描不到,那就得使用这种类似开后门的方式使用配置文件的方式让SpringBoot直接去解析使用。

「池化」思想

以数据库为例,系统在和数据库进行交互之前,数据库驱动会帮助我们建立好连接。之后我们只需要发送SQL语句就可以执行对应的数据库操作了。

但如果系统不是一个人在使用,存在多个请求同时去争抢连接的情况。这时候如果并发处理多个请求,会导致多个请求去建立多个连接,并且使用完后再去关闭。这会导致不必要的资源浪费和性能下降。

这说明在多线程请求的时候频繁创建和销毁连接是不合理的。但如果我们可以提供一些固定的用来连接的线程,这样就不需要反复的创建和销毁连接了。而这就是数据库连接池。

数据库连接池:维护一定的连接数,方便系统获取连接,使用时去池子中获取,用完放回去就可以。我们不需要关系连接的创建和销毁,也不需要关心线程池是怎么维护这些连接的。

常见的Druid、C3P0、DBCP,这些连接池大大节省了不断创建于销毁线程的开销。

扩展到操作系统线程池和HTTP连接池,这就是「池化」思想。

常见的「池化」应用实例有:

  • 内存池
  • 连接池
  • 实例池
  • 线程池

「池化」思想可以带来效率的提升、降低资源的消耗、提升资源的可管理性。

Java的异常

异常也是对象,而对象都用类来定义。异常类的根类是java.lang.Throwable。

抛出的异常都是这个图中给出的类的实例,或者是这些类的子类的实例。

我们也可以定义自己的异常类。

Throwable类是所有异常类的根。所有异常类都直接或间接地继承自Throwable。这些异常可以分为三种类型:系统错误、异常和运行时异常。

RuntimeException和Error都称为免检异常(unchecked exception)。所有的其他异常都称为必检异常(checked exception),表示编译器会强制程序员检查并通过try-catch块处理它们,或在方法头处理它们。

Java语言不强制要求编写代码捕获或声明免检异常。

异常的处理器是通过从当前的方法开始,沿着方法调用链,按照异常的反向传
播方向找到的。

Java的异常处理模型基于三种操作:声明异常、抛出异常和捕获异常。

Java对面向对象的语言。在Java中,当前执行的语句必须属于某个方法。Java解释器调用main方法开始执行一个程序。每个方法都必须声明它可能抛出的必检异常的类型。这称为声明异常。这样方法的调用者会被告知有异常。

处理异常的这段代码称为异常处理器(exception handler)

可以从当前的方法开始,沿着方法调用链,按照异常的反向传播方向找到这个处理器。如果调用链找不到处理器,程序就会终止并且在控制台上打印出错信息。寻找处理器的过程称为捕获一个异常。

JDK7可以使用同样的处理代码处理多个异常的情况。

catch(exception1 | exception2 | exception3){ //code }

何时使用异常?

异常处理可以分离正常的程序和错误处理程序。异常处理需要初始化新的对象,需要从调用栈返回,还要沿着方法调用链来传播异常以便找到它的异常处理程序,所以异常处理通常需要花费更多的时间。如果想让该方法的调用者处理异常,应该创建一个异常对象并将其抛出。

当必须处理不可预料的错误状况时应该使用try-catch块。不要用异常处理来处理简单、可预料的情况。不要把异常处理当作逻辑判断。

允许异常处理器再抛出异常。这样是为了让调用者注意到这个异常。

在异常处理器中同原始异常一起再抛出一个新的异常叫链式异常(chained exception)。

自定义异常最好不要继承免检异常。继承必检异常可以让编译器在程序中强制捕获这些异常。

SpringBoot项目的目录结构及其作用

servicex                 // 项目主文件夹(用项目名称命名)
    |- admin-ui          // 管理服务前端代码(一般将UI和SERVICE放到一个工程中,便于管理)
    |- servicex-auth     // 模块1
    |- servicex-common   // 模块2
    |- servicex-gateway  // 模块3
    |- servicex-system   // 模块4
        |- src
            |- main                  // 业务逻辑
                |- assembly          // 基于maven assembly插件的服务化打包方案
                    |- bin           // 模块脚本(启动、停止、重启)
                    |- sbin          // 管理员角色使用的脚本(环境检查、系统检测等等)
                    |- assembly.xml  // 配置文件
                |- java              // 源码
                    |- com
                        |- hadoopx
                            |- servicex
                                |- system
                                    |- annotation     // 注解
                                    |- aspect         // 面向切面编程
                                    |- config         // 配置文件POJO
                                    |- filter         // 过滤器
                                    |- constant       // 存放常量
                                    |- utils          // 工具
                                    |- exception      // 异常
                                    |- controller     // 控制层(将请求通过URL匹配,分配到不同的接收器/方法进行处理,然后返回结果)
                                    |- service        // 服务层接口
                                        |- impl       // 服务层实现
                                    |- mapper/repository // 数据访问层,与数据库交互为service提供接口
                                    |- entity/domain     // 实体对象
                                        |- dto // 持久层需要的实体对象(用于服务层与持久层之间的数据传输对象)
                                        |- vo // 视图层需要的实体对象(用于服务层与视图层之间的数据传输对象)
                                    |- *Application.java  // 入口启动类
                |- resources         // 资源
                    |- static        // 静态资源(html、css、js、图片等)
                    |- templates     // 视图模板(jsp、thymeleaf等)
                    |- mapper        // 存放数据访问层对应的XML配置
                        |- *Mapper.xml
                        |- ...
                    |- application.yml        // 公共配置
                    |- application-dev.yml    // 开发环境配置
                    |- application-prod.yml   // 生产环境配置
                    |- banner.txt    
                    |- logback.xml            // 日志配置
            |- test                  // 测试源码
               |- java               
                    |- com
                        |- hadoopx
                            |- servicex
                                |- system
                                    |- 根据具体情况按源码目录结构存放编写的测试用例
        |- target     // 编译打包输出目录(自动生成,不需要创建)
        |- pom.xml    // 该模块的POM文件
    |- sql            // 项目需要的SQL脚本
    |- doc            // 精简版的开发、运维手册
    |- .gitignore     // 哪些文件不用传到版本管控工具中
    |- pom.xml        // 工程总POM文件
    |- README.md      // 自述文件

C语言的一些“约定”

在C语言中以“#”开头的为预处理指令。在预处理阶段,由预处理器处理。这部分不属于C代码中被执行的一部分。通常是告诉预处理器这里应该如何处理源码。


防止重复引用头文件。

#pragma once

但#pragam是编译器相关的,它的设定的状态,或指示编译器完成一些特定的动作。如果编译器没有实现功能可能会编译报错。

也可以使用编译器无关的宏定义方法:

// 文件名为:GateWay_private.h
#ifndef RTW_HEADER_GateWay_private_h_
#define RTW_HEADER_GateWay_private_h_

// 头文件

// 主要代码

#endif                                 /* RTW_HEADER_GateWay_private_h_ */

其中宏定义的名称没有特定的要求,只要在工程中命名唯一就行。可以根据公司规定、项目约束和个人习惯来设置。可以使用“项目+文件名”的方式,例如RTW_HEADER表示RTW工程的头文件,GateWay_private_h_为该文件的文件名。并在最后给出结束位置的提示/* RTW_HEADER_GateWay_private_h_ */

也可以直接使用文件名,只要命名唯一即可,但这种方式通常用来公用的头文件上,例如变量类型的定义:

// 文件名为:GateWay_private.h
#ifndef GATEWAY_PRIVATE_H
#define GATEWAY_PRIVATE_H

// 引用头文件

// 主要代码

#endif // !GATEWAY_PRIVATE_H

为了在C++代码中调用C写成的库文件,就需要用extern”C”来告诉编译器:这是一个用C写成的库文件,请用C的方式来链接它们。

extern “C” 表示编译生成的内部符号名使用C约定。

#ifdef  __cplusplus
extern "C" {
#endif

// 引用头文件

// 主要C代码

#ifdef  __cplusplus
}
#endif

这段话的上下文意思是,当C++程序调用C语言程序时,需要使用C语言的编译方式来处理这段代码。

因为C++支持函数重载,而C不支持函数重载。两者语言的编译规则不一样。编译器对函数名的处理方法也不一样。

void func(int a,int b)
{
  //code  
}

针对上面这个函数,C编译之后,可能为_func,而C++编译之后会产生_func_int_int之类的名字(不同的编译器可能生成的名字不 同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。_foo_int_int这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。

如果不区分的话,之后的链接会出现找不到具体函数的错误,会导致链接失败。

一般我们都将函数声明放在头文件,当我们的函数既有可能C使用,也有可能被C++使用时,我们无法确定是否要将函数声明在extern “C”里,所以,我们应该使用上面提到的#ifdef __cplusplus方式。


为什么一个字节是8个比特?

为什么一个字节byte是8个bit呢?而不是6个或10个或其他数量呢?

往前追溯,byte是IBM公司在1956年提出的概念,原本叫做bite,但为了不和bit混淆,改为byte。

ASCII编码于1967年提出。

在那时候,开发一个东西,肯定是从简单开始,也就是最接近某一类人能使用的情况下开发的。这里自然指的是使用英语的人。而英语只有26个字母,加上大小写,8个比特位足以。基于简单的下,就使用了8个比特位来使用。

比ASCII更简单的还有BCD码(Binary-Coded Decimal‎)。用4位二进制数来表示1位十进制数中的0~9这10个数码。是一种二进制的数字编码形式,用二进制编码的十进制代码。

在当时8个比特位已经可以表示很多东西。

字节的单位符号被指定为大写的“B”,1B表示的是1byte

参考:Byte – Wikipedia

开发软件环境和工具

下载软件

Free Download Manager,免费。Free Download Manager – 從網路下載任何東西

文件对比

DiffMerge,免费,但功能和界面比较单一。支持Windows、OS X、and Linux。SourceGear | DiffMerge

Beyond Compare,收费,但可以永久使用。功能强大,适用于绝大多数场景。Scooter Software: Home of Beyond Compare

UItraCompare,收费,且按年收费。功能强大。Compare Files, Folders, Text | UltraCompare (ultraedit.com)

反编译器

ILSpy,.Net反编译器,开源免费。icsharpcode/ILSpy: .NET Decompiler with support for PDB generation, ReadyToRun, Metadata (&more) – cross-platform! (github.com)

jclasslib bytecode viewer,字节码查看软件。开源免费。ingokegel/jclasslib: jclasslib bytecode editor is a tool that visualizes all aspects of compiled Java class files and the contained bytecode. (github.com)

API 接口测试

Postman:有免费版,也有付费版。https://www.postman.com/downloads/

Postcat,开源免费,并且可以在线测试:https://github.com/Postcatlab/postcat

小工具

Md5Checker,MD5比较工具,免费。Md5Checker (getmd5checker.com)

Mouse without Borders,微软的免费软件。多主机、多屏幕控制,单个鼠标和键盘控制多达四台计算机。Download Microsoft Garage Mouse without Borders from Official Microsoft Download Center

Notepad3,Windows记事本增强工具,免费开源。Download Notepad3 – Notepad replacement with syntax highlighting. (rizonesoft.com)

Snipaste,桌面截图工具。轻量、免费、开源。Snipaste官网github仓库

Listary,文件搜索增强,免费。但好像开机启动时会进行扫盘操作,会导致磁盘占用较高。Listary – File Search & App Launcher

魔法道具

Pigcha,魔法跃迁道具,有流量收费,也有包月,价格优惠。Pigcha官方地址

M考拉,魔法跃迁道具,有免费也有收费,价格优惠。M考拉 | Secured Private Networks (kao-la.best)

EasyConnect,深信服产品,EasyConnect官网

Java开发工具

Eclipse,开源免费。Eclipse官网

MyEclipse,免费试用30天,35$/年。MyEclipse官网

IDEA,有免费的社区版,IDEA官网

Redis可视化工具

QuickRedis,国人开源、免费的Redis可视化管理工具。官网:QuickRedis官网,开源地址:QuickRedis开源地址

Another Redis Desktop Manager:开源的Redis可视化工具,比QuickRedis的体验更好。开源地址:Another Redis Desktop Manager开源地址

Linux

VMware Workstation Player,免费的Linux虚拟机工具。VMware Workstation Pro是收费的。VMware Workstation Player | VMware | CN

版本控制

Sourcetree,免费。Sourcetree | Free Git GUI for Mac and Windows (sourcetreeapp.com)

TortoiseSVN,免费。Home · TortoiseSVN

git,免费。Git (git-scm.com)

数据库设计

Open System Architect,免费软件,Open System Architect官网

Mysql Workbench,免费软件,同时可以负责数据库。

其它

Potplayer,免费且强大的视频播放器,Global Potplayer

geek,用来删除程序的软件,有免费也有收费版本,收费版本是终身制。geek官网

Bandizip,解压缩软件,分为Windows版本和Mac版本,免费但稍微有广告,也有永久许可证的收费版本。bandisoft官网

7-Zip,开源解压缩软件,7-Zip网站

NanaZip,开源解压缩软件,在7-Zip的基础上新增了符合现代操作系统的界面。NanaZip网站

计算机性能工具

CPU-Z,免费软件,可以收集系统的主要设备信息,CPU-Z | Softwares | CPUID

CrystalDiskMark,免费软件,免费的磁盘测试软件,Crystal Dew World [en] – (crystalmark.info)

DiskGenius,有免费版本,也可以升级为付费版本。系统迁移和硬盘分区软件。Free Download DiskGenius Online

Fritz Chess Benchmark,免费软件,用于测试CPU性能。Fritz Chess Benchmark – Download (updatestar.com)

序列化和反序列化

什么是序列化和反序列化呢?

序列化就是将对象转成字节序列的过程。

反序列化就是将字节序列重组成对象的过程。

为什么要有对象序列化机制

程序中的对象,都是存放在内存中的。当我们关闭程序或内存掉电后,无论如何它都不会继续存在了。那么有没有一种机制能让对象具有“持久性”呢?

序列化机制提供了一种方法,你可以将对象序列化的字节流输入到文件,并保存在磁盘或数据库上。

序列化机制的另外一个应用场景是,我们可以通过网络传输对象。Java中的远程方法调用(RMI),底层就需要序列化机制的保证。

序列化机制从某种意义上,也弥补了不同平台带来的差异。毕竟转换后的字节流可以在其他平台上进行反序列化来恢复对象。


Java中的序列化和反序列化

假设我们需要将一个Student类的对象进行序列化。无论是用于持久化还说网络传输都可以。

import java.io.Serializable;

public class StudentClass implements Serializable {

    //...类属性和方法。

}

需要被序列化的类都必须实现Serializable接口。但Serializable接口实际上是一个空接口,里面并没有包含任何接口方法。但这并不代表Student类实现Serializable接口是没必要的。

在这里Serializable起到一个标记作用。它告诉底层代码,该类是可以被序列化的。但真正序列化的代码并不是由Serializable完成。

如果对一个没有实现Serializable接口的类进行序列化会抛出java.io.NotSerializableException异常。

还有一个非常重要的是就是序列化ID:serialVersionUID。

在定义一个可序列化的类时,如果没有显式地定义一个serialVersionUID字段,则Java运行时环境会根据该类的各方面信息自动地为它生成一个默认的serialVersionUID。一旦更改了类的结构或信息,则类的serialVersionUID也会跟着变化。

在反序列化时,JVM会把字节流中的serialVersionUID和被序列化类中的serialVersionUID进行对比。只有两者一致,才能重写反序列化。否则就会报java.io.InvalidClassException异常。

因此,为了serialVersionUID的确定性。建议写代码时,凡是实现了Serializable的可序列化类,都最好显式地声明一个明确的serialVersionUID值。

import java.io.Serializable;

public class StudentClass implements Serializable {

    private static final long serialVersionUID = -4963266899668807475L;

    //...类属性和方法。

}

如果不想手动赋值,可以使用IDE的自动添加功能。例如IDEA可以参考:IDEA如何自动生成serialVersionUID

其中还有两种特殊情况:

  • 凡是被static修饰的字段是不会被序列化的。
  • 凡是被transient修饰的字段也是不会被序列化的。

参考:

7000字带你死磕Java I/O流知识

序列化/反序列化,我忍你很久了