文章目录
卫语句
卫语句就是把复杂的条件表达式拆分成多个条件表达式,比如一个很复杂的表达式,嵌套了好几层的if -then-else
语句,转换为多个if语句,实现它的逻辑,这多条的if语句就是卫语句。比如:
public void function() {
if(IsWorkDay()) { //boolean
Sysetm.out.println("Error,is work day");
} else {
if(IsWorkTime()) { //boolean
Sysetm.out.println("Error ,is work time");
} else {
... //执行内容
}
}
}
//写成这样的逻辑
public void function(){
if(IsWorkDay()) {
Sysetm.out.println("Error,is work day");
return;
}
if(IsWorkTime()) {
Sysetm.out.println("Error ,is work time");
return;
}
...//执行内容
}
类之间的关系
实现关系
表示一个类实现一个或多个接口的方法。接口定义好操作的集合,由实现类去完成接口的具体操作。在java中使用implements表示。UML图例中,实现关系用虚线+空心箭头表示,箭头指向接口。
继承关系 :
继承表示类与类(或者接口与接口)之间的父子关系。在java中,用关键字extends表示继承关系。UML图例中,继承关系用实线+空心箭头表示,箭头指向父类。
依赖关系:
对象之间最弱的一种关联方式,是临时性的关联。代码中一般指由局部变量、函数参数、返回值建立的对于其他对象的调用关系
class A{
public B method(C c,D d){
E e = new E();
// do someThing
B b = new B();
// do someThing
return b;
}
}
这个代码结构中,表示 A 类依赖了 B,C,D,E 类
关联关系:
对象之间一种引用关系,比如客户类与订单类之间的关系。这种关系通常使用类的属性表达。关联可以有方向,即导航。一般不作说明的时候,导航是双向的,不需要在线上标出箭头。 大部分情况下导航是单向的,可以加一个箭头表示。
单向关联: A类关联B类。
双向关联:A类关联B类,B类关联A类;
自身关联:A类关联A类
解决一对多的关联的方案:
class Employee{
private int eid;//员工编号
private String name;//员工姓名
private Computer coumputer;//员工所使用的电脑
//....
}
class Computer{
}
class Husband{
private Wife wife;
}
class Wife{
private Husband husband;
}
关联表示类之间的“持久”关系,这种关系一般表示一种重要的业务之间的关系,需要保存的,或者说需要“持久化”的,或者说需要保存到数据库中的。另外,依赖表示类之间的是一种“临时、短暂”关系,这种关系是不需要保存的.
聚合关系:
聚合(关联关系的一种):表示 has-a 的关系。与关联关系一样,聚合关系也是通过实例变量来实现这样关的。关联关系和聚合关系来语法上是没办法区分的,从语义上才能更好的区分两者的区别。如汽车类与引挚类,轮胎类之间的关系就是整体与个体的关系。与关联关系一样,聚合关系也是通过实例变量来实现的。空心菱形
class Car{
private Engine engine;//引擎
private Tyre[] tyres;//轮胎
}
关联和聚集(聚合)的区别:
关联关系所涉及的两个对象是处在同一个层次上的。比如人和自行车就是一种关联关系,而不是聚合关系,因为人不是由自行车组成的。
聚合关系涉及的两个对象处于不平等的层次上,一个代表整体,一个代表部分。比如电脑和它的显示器、键盘、主板以及内存就是聚集关系,因为主板是电脑的组成部分。
组合关系:
对象 A 包含对象 B,对象 B 离开对象 A 没有实际意义。是一种更强的关联关系。人包含手,手离开人的躯体就失去了它应有的作用。
组合:表示 contains-a 的关系,是一种强烈的包含关系。组合类负责被组合类的生命周期。也使用属性表达组合关系,是关联关系的一种,是比聚合关系强的关系。
class Window{
private Menu menu;//菜单
private Slider slider;//滑动条
private Panel panel;//工作区
}
正则表达式
身份证正则表达式
/^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{4}$/
MAC地址正则表达式
MAC地址,也就是网卡MAC码。网卡MAC码是由IEEE的注册管理机构固定分配的,因此每一个主机会有一个MAC地址,具有全球唯一性。
因为业务需求需要用验证MAC地址是否合法,因而想到用正则进行匹配。
看过了大神chenyufeng1991的相关博客,有了一些大体思路。
原正则表达式为:
([A-Fa-f0-9]{2}-){5}[A-Fa-f0-9]{2}
因为考虑到MAC地址一般有两种格式,使用-
连接或是:
连接,于是我稍作改动,改动结果如下
/(([a-f0-9]{2}:)|([a-f0-9]{2}-)){5}[a-f0-9]{2}/gi
以上正则表达式就是最终版的MAC地址验证表达式,如果不清楚具体原理可以接着往下看。
拆解分析
[a-f0-9]
匹配a
到f
或0
到9
中的任意一位字符。匹配的结果例如2
或d
。
[a-f0-9]{2}
匹配连续两位的括号中任意字符。匹配的结果例如d2
或ac
。
[a-f0-9]{2}:
连续两位的括号中任意字符再拼接一个:
(冒号)。匹配的结果例如b2:
或23:
([a-f0-9]{2}:)|([a-f0-9]{2}-)
在第三步的基础上可以将冒号替换为横杠。匹配的结果例如f3:
或79-
(([a-f0-9]{2}:)|([a-f0-9]{2}-)){5}
将第四步的结果重复5次。匹配的结果例如00-01-6C-06-A6-
或00:01:6C:06:A6:
(([a-f0-9]{2}:)|([a-f0-9]{2}-)){5}[a-f0-9]{2}
在第五步的结果上再拼接两个[A-F0-Z]
范围内的两个字符。匹配的结果例如00-01-6C-06-A6-29
或00:01:6C:06:A6:29
- 设置大小写不敏感与全局匹配,在正则最后加上
/ig
。
完整的正则表达式也就是:
/(([a-f0-9]{2}:)|([a-f0-9]{2}-)){5}[a-f0-9]{2}/gi
Unicode与utf8的关系
Unicode
需要注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。
比如,汉字“严”的unicode是十六进制数4E25,转换成二进制数足足有15位(100111000100101
),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。
这里就有两个严重的问题,第一个问题是,如何才能区别unicode和ascii?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。
它们造成的结果是:
- 出现了unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示unicode。
- unicode在很长一段时间内无法推广,直到互联网的出现。
UTF-8
互联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种unicode的实现方式。其他实现方式还包括UTF-16和UTF-32,不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。
UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8的编码规则很简单,只有二条:
-
对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
-
对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
下表总结了编码规则,字母x表示可用编码的位。
Unicode符号范围 | UTF-8编码方式 |
---|---|
(十六进制) | (二进制) |
0000 0000-0000 007F | 0xxxxxxx |
0000 0080-0000 07FF | 110xxxxx 10xxxxxx |
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
下面,还是以汉字“严”为例,演示如何实现UTF-8编码。
已知“严”的unicode是4E25
(100111000100101
),根据上表,可以发现4E25
处在第三行的范围内(0000 0800-0000 FFFF
),因此“严”的UTF-8编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx
。然后,从“严”的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,“严”的UTF-8编码是11100100 10111000 10100101
,转换成十六进制就是E4B8A5
。
全、半角空格字符
String.valueOf((char)12288) //中文全角空格
String.valueOf((char) 32) //英文半角空格
import和import static的区别
import static静态导入是JDK1.5中的新特性。
一般我们导入一个类都用 import com.....ClassName
;而静态导入是这样:import static com.....ClassName.*
;这里的多了个static
,还有就是类名ClassName
后面多了个 .*
,意思是导入这个类里的静态方法。
当然,也可以只导入某个静态方法,只要把 .*
换成静态方法名就行了。然后在这个类中,就可以直接用方法名调用静态方法,而不必用ClassName.方法名
的方式来调用。
同样也可以导入静态常量。比如枚举类型。导入后,可以直接使用,不用添加类名。
字符串hashCode计算算法
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
公式:
\sum_{i=0}^{n-1} s[i]*31^(n-i-1) (0<=i<n;n=s.length())
例如长度为3的字符串s。
hashcode = s[0]*31^(3-0-1) + s[1]*31^(3-1-1) + s[2]*31^(3-2-1)
字符的ASCII值
a-z:97-122
A-Z:65-90
0-9:48-57
Java 访问控制
关键字 | 同一个类 | 同一个包 | 子类 | 所有类 |
---|---|---|---|---|
private | * | |||
default | * | * | ||
protected | * | * | * | |
public | * | * | * | * |
Java 基本数据类型
Java语言提供了八种基本类型。六种数字类型(四个整数型,两个浮点型),一种字符类型,还有一种布尔型。
byte
- byte 数据类型是8位、有符号的,以二进制补码表示的整数;
- 最小值是 -128(-2^7);
- 最大值是 127(2^7-1);
- 默认值是 0;
- byte 类型用在大型数组中节约空间,主要代替整数,因为 byte 变量占用的空间只有 int 类型的四分之一;
- 例子:
byte a = 100,byte b = -50
。
short
- short 数据类型是 16 位、有符号的以二进制补码表示的整数
- 最小值是 -32768(-2^15);
- 最大值是 32767(2^15 - 1);
- Short 数据类型也可以像 byte 那样节省空间。一个short变量是int型变量所占空间的二分之一;
- 默认值是 0;
- 例子:
short s = 1000,short r = -20000
。
int
- int 数据类型是32位、有符号的以二进制补码表示的整数;
- 最小值是 -2,147,483,648(-2^31);
- 最大值是 2,147,483,647(2^31 - 1);
- 一般地整型变量默认为 int 类型;
- 默认值是 0 ;
- 例子:
int a = 100000, int b = -200000
。
long
- long 数据类型是 64 位、有符号的以二进制补码表示的整数;
- 最小值是 -9,223,372,036,854,775,808(-2^63);
- 最大值是 9,223,372,036,854,775,807(2^63 -1);
- 这种类型主要使用在需要比较大整数的系统上;
- 默认值是 0L;
- 例子:
long a = 100000L,Long b = -200000L
。"L"理论上不分大小写,但是若写成"l"容易与数字"1"混淆,不容易分辩。所以最好大写。
float
- float 数据类型是单精度、32位、符合IEEE 754标准的浮点数;
- float 在储存大型浮点数组的时候可节省内存空间;
- 默认值是 0.0f;
- 浮点数不能用来表示精确的值,如货币;
- 例子:
float f1 = 234.5f
。
double
- double 数据类型是双精度、64 位、符合IEEE 754标准的浮点数;
- 浮点数的默认类型为double类型;
- double类型同样不能表示精确的值,如货币;
- 默认值是 0.0d;
- 例子:
double d1 = 123.4
。
boolean
- boolean数据类型表示一位的信息;
- 只有两个取值:true 和 false;
- 这种类型只作为一种标志来记录 true/false 情况;
- 默认值是 false;
- 例子:
boolean one = true
。
char
- char类型是一个单一的 16 位 Unicode 字符;
- 最小值是
\u0000
(即为0); - 最大值是
\uffff
(即为65,535); - char 数据类型可以储存任何字符;
- 例子:
char letter = 'A'
;。
Java移位运算深入
超过自身位数的移位
我们知道,int类型占用4字节,32位,而long类型占用8字节,64位。那么,如果将int类型(long类型)移动超过31位(63位)便失去了意义,因为用通俗的话来说,就是“全移走了”。不过幸运的是,系统对这种情况做了处理。
是怎么处理的呢?普遍都是这样认为的:如果左侧操作数是int类型,会对右侧操作数进行除数为32的求余运算,如果左侧操作数为long类型,会对右侧操作数进行除数为64的求余运算。是的,当要移位的个数为正数时是这样的,但当要移位的个数为负数时却不正确。
例如,假如有如下的赋值运算:
int i = 5 << -10;
-10对32取余还是-10,向左移动-10位,该怎么移动?
实际上,当左侧操作数为int时类型时,右侧操作数只有低5位是有效的(低5位的范围是0~31),也就是说可以看作右侧操作数会先与掩码0x1f(00011111)做与运算,然后左侧操作数再移动相应的位数。类似地,当左侧操作数为long类型时,右侧操作数只有低6位是有效的,可以看作右侧操作数先与掩码0x3f(00111111)做与运算,然后再移动相应的位数。
-10的补码为:
1111 1111 1111 1111 1111 1111 1111 0110
取其低5位,结果为:
1 0110
这个值就是22,也就是相当于:
int i = 5 << 22;
因此,不要把移位运算右侧的操作数与求余运算联系在一起,那是不完全正确的。
移位运算与乘除运算
由于数据采用二进制来表示,因此就会普遍存在这样的想法:左移一位就相当于乘以2,而右移一位就相当于除以2,这种想法正确吗?
在Java中,当两个操作数都是整型的时候,结果也是整型的。如果不能整除,则结果是向0舍入的,也就是说,向靠近0的方向取值。如:
9/2的结果为4
-9/2的结果为-4
而对移位运算来说:
9>>1的结果为4
-9>>1的结果为-5(特殊,移位向下舍入)
永远的-1
本质是类型的自动提升
“>>>”为无符号右移运算符,其与“>>”不同的是,“>>>”是以0来填补左侧移出的空位,而“>>”是以符号位来填补左侧移出的空位。如果是正数,“>>”与“>>>”是相同的,因为都是用0来补位的,如果是负数,“>>>”就可能移出正数值来。
int a = -1;
for(int i =1;i<=Integer.SIZE;i++){
a>>>=1;
System.out.println(a);
}
short b = -1;//换成byte也是一样
for(int i =1;i<=Short.SIZE;i++){
b>>>=1;
System.out.println(b);
}
对于int类型变量而言,移位产生了int类型变量的最大值2147483647(2^31),以后每移动一位,值就减半,直到为0。对于byte类型变量,值始终是-1,为什么?
int类型变量-1的补码是:
1111 1111 1111 1111 1111 1111 1111 1111
经过无符号右移后(<<<),使用0补位:
0111 1111 1111 1111 1111 1111 1111 1111
该值即2147483647(2^31)。以此类推,每移动一位,值就会减半,直到所有“1”都被移出,值变为0。
而byte类型变量-1的补码为:
1111 1111
因为是byte类型,所以在参与移位运算之前,会首先扩展为int类型,又因为byte是有符号类型,所以进行符号位扩展,如下:
1111 1111 1111 1111 1111 1111 1111 1111
然而,无符号右移后一位,使用0补位:
0111 1111 1111 1111 1111 1111 1111 1111
因为复合赋值运算符“>>>=”可以自动将结果转换为左侧操作数的类型,因此将结果转换为byte,这只需要进行简单的截断,即丢弃高24位,结果为:
1111 1111
因此,该值还是-1。只要是这样一位一位的移动,不管循环多少次,都是-1,也就是说结果是一个永远的“-1”。
位运算符的常见使用方法
计算m*2^n次方
计算3*8
3<<3
判断一个数n的奇偶性
a&1 = 0 偶数
a&1 = 1 奇数
n&1==1?“奇数”:“偶数”
return a&1 == 0?true:false;
为什么与1能判断奇偶?所谓的二进制就是满2进1,那么好了,偶数的最低位肯定是0(恰好满2,对不对?),同理,奇数的最低位肯定是1.int类型的1,前31位都是0,无论是1&0还是0&0结果都是0,那么有区别的就是1的最低位上的1了,若n的二进制最低位是1(奇数)与上1,结果为1,反则结果为0.
不用临时变量交换两个数
a = a^b;
b = b^a;
a = a^b;
取绝对值
(a^(a>>31))-(a>>31)
先整理一下使用位运算取绝对值的思路:若a为正数,则不变,需要用异或0保持的特点;若a为负数,则其补码为源码翻转每一位后+1,先求其源码,补码-1后再翻转每一位,此时需要使用异或1具有翻转的特点。
任何正数右移31后只剩符号位0,最终结果为0,任何负数右移31后也只剩符号位1,溢出的31位截断,空出的31位补符号位1,最终结果为-1.右移31操作可以取得任何整数的符号位。
那么综合上面的步骤,可得到公式。a>>31取得a的符号,若a为正数,a>>31等于0,a0=a,不变;aa=0;若a为负数,a>>31等于-1 ,a-1(a0xFFFFFFFF)翻转每一位.
取int型变量a的第k位
a>>k&1 //(k=0,1,2……sizeof(int))。
将int型变量a的第k位清0
a=a&~(1 <<k) //(k=0,1,2……sizeof(int))。
将int型变量a的第k位置1
a=a |(1 <<k)//(k=0,1,2……sizeof(int))。
int型变量循环左移k次
a=a <<k|a>>16-k // (设sizeof(int)=16)
int型变量a循环右移k次
a=a>>k |a <<16-k (设sizeof(int)=16)
整数的平均值
对于两个整数x,y,假设用 (x+y)/2 求平均值。会产生溢出。由于 x+y 可能会大于INT_MAX,可是我们知道它们的平均值是肯定不会溢出的。我们用例如以下算法:
int average(int x, int y) //返回X,Y 的平均值
{
return (x&y)+((x^y)>>1);
}
推断一个整数是不是2的幂,对于一个数 x >= 0,推断他是不是2的幂
boolean power2(int x)
{
return ((x&(x-1))==0)&&(x!=0)。
}
取模运算转化成位运算 (在不产生溢出的情况下)
a % (2^n) 等价于 a & (2^n - 1)
乘法运算转化成位运算 (在不产生溢出的情况下)
a * (2^n) 等价于 a < < n
除法运算转化成位运算 (在不产生溢出的情况下)
a / (2^n) 等价于 a>> n //例: 12/8 == 12>>3
a % 2 等价于
a & 1
太长,请点击标题,具体看详情
假设现在参数X的取值只可能a,b两个数,现在的实现逻辑是,如果x==b时,则把b的值赋给X;如果x==b时,则把a的值赋给X。
第一种:
if (x == a){
x= b;
}else{
x= a;
}
第二种
x= a ^ b ^ x;
这种替换有限制的:
x只可能等于a,b两个数之间选择,如果x有第三种取值情况,上述等价替换不成立则不成立。
x的相反数
(~x+1)
Java 创建数组的几种方式
3种方式示例:
//元素类型[] 数组名 = new 元素类型[元素个数或数组长度];
int[] arr = new int[5];//1
//元素类型[] 数组名 = new 元素类型[]{元素,元素,……};
int[] arr = new int[]{3,5,1,7};//2
int[] arr = {3,5,1,7};//3
try-with-resources 语句try(){}
从 Java 7 build 105 版本开始,Java 7 的编译器和运行环境支持新的 try-with-resources 语句,称为 ARM 块(Automatic Resource Management) ,自动资源管理。
使用try(){}catch(){}效果:
private static void customBufferStreamCopy(File source, File target) {
try (InputStream fis = new FileInputStream(source);
OutputStream fos = new FileOutputStream(target)){
byte[] buf = new byte[8192];
int i;
while ((i = fis.read(buf)) != -1) {
fos.write(buf, 0, i);
}
}
catch (Exception e) {
e.printStackTrace();
}
}
在这个例子中,数据流会在 try 执行完毕后自动被关闭,前提是,这些可关闭的资源必须实现Closeable
或java.lang.AutoCloseable
接口。
Java四种代码块
- 普通代码块:
类中方法的方法体,定义在方法中,不管是普通还是静态,方法被调用的时候执行。 - 构造代码块:
构造块会在创建对象时被调用,每次创建时都会被调用,优先于类构造函数执行。类加载时不调用(如调用静态函数时outClass.out(),只执行静态代码块,不执行构造代码块) - 静态代码块:
用static{}包裹起来的代码片段,类加载时调用,只会执行一次。静态代码块优先于构造块执行。 - 同步代码块:
使用synchronized(){}包裹起来的代码块,在多线程环境下,对共享数据的读写操作是需要互斥进行的,否则会导致数据的不一致性。同步代码块需要写在方法中。