java编程杂记一

卫语句

卫语句就是把复杂的条件表达式拆分成多个条件表达式,比如一个很复杂的表达式,嵌套了好几层的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图例中,继承关系用实线+空心箭头表示,箭头指向父类。

![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/d211dfacc0184279bf90ef2394f3598d.png)

依赖关系:

对象之间最弱的一种关联方式,是临时性的关联。代码中一般指由局部变量、函数参数、返回值建立的对于其他对象的调用关系

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 类

image

关联关系:

对象之间一种引用关系,比如客户类与订单类之间的关系。这种关系通常使用类的属性表达。关联可以有方向,即导航。一般不作说明的时候,导航是双向的,不需要在线上标出箭头。 大部分情况下导航是单向的,可以加一个箭头表示。
单向关联: A类关联B类。
双向关联:A类关联B类,B类关联A类;
自身关联:A类关联A类
解决一对多的关联的方案:

class Employee{
private int eid;//员工编号
private String name;//员工姓名
private Computer coumputer;//员工所使用的电脑
//....
}
class Computer{
}

image

class Husband{
private Wife wife;
}
class Wife{
private Husband husband;
}

image

关联表示类之间的“持久”关系,这种关系一般表示一种重要的业务之间的关系,需要保存的,或者说需要“持久化”的,或者说需要保存到数据库中的。另外,依赖表示类之间的是一种“临时、短暂”关系,这种关系是不需要保存的.

聚合关系:

聚合(关联关系的一种):表示 has-a 的关系。与关联关系一样,聚合关系也是通过实例变量来实现这样关的。关联关系和聚合关系来语法上是没办法区分的,从语义上才能更好的区分两者的区别。如汽车类与引挚类,轮胎类之间的关系就是整体与个体的关系。与关联关系一样,聚合关系也是通过实例变量来实现的。空心菱形

class Car{
private Engine engine;//引擎
private Tyre[] tyres;//轮胎
}

image

关联和聚集(聚合)的区别:
关联关系所涉及的两个对象是处在同一个层次上的。比如人和自行车就是一种关联关系,而不是聚合关系,因为人不是由自行车组成的。
聚合关系涉及的两个对象处于不平等的层次上,一个代表整体,一个代表部分。比如电脑和它的显示器、键盘、主板以及内存就是聚集关系,因为主板是电脑的组成部分。

组合关系:

对象 A 包含对象 B,对象 B 离开对象 A 没有实际意义。是一种更强的关联关系。人包含手,手离开人的躯体就失去了它应有的作用。
组合:表示 contains-a 的关系,是一种强烈的包含关系。组合类负责被组合类的生命周期。也使用属性表达组合关系,是关联关系的一种,是比聚合关系强的关系。

class Window{
private Menu menu;//菜单
private Slider slider;//滑动条
private Panel panel;//工作区
}

image


正则表达式

身份证正则表达式

/^[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地址验证表达式,如果不清楚具体原理可以接着往下看。

拆解分析

  1. [a-f0-9]

匹配af09中的任意一位字符。匹配的结果例如2d

  1. [a-f0-9]{2}

匹配连续两位的括号中任意字符。匹配的结果例如d2ac

  1. [a-f0-9]{2}:

连续两位的括号中任意字符再拼接一个:(冒号)。匹配的结果例如b2:23:

  1. ([a-f0-9]{2}:)|([a-f0-9]{2}-)

在第三步的基础上可以将冒号替换为横杠。匹配的结果例如f3:79-

  1. (([a-f0-9]{2}:)|([a-f0-9]{2}-)){5}

将第四步的结果重复5次。匹配的结果例如00-01-6C-06-A6-00:01:6C:06:A6:

  1. (([a-f0-9]{2}:)|([a-f0-9]{2}-)){5}[a-f0-9]{2}

在第五步的结果上再拼接两个[A-F0-Z]范围内的两个字符。匹配的结果例如00-01-6C-06-A6-2900:01:6C:06:A6:29

  1. 设置大小写不敏感与全局匹配,在正则最后加上/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,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。

它们造成的结果是:

  1. 出现了unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示unicode。
  2. unicode在很长一段时间内无法推广,直到互联网的出现。

UTF-8

互联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种unicode的实现方式。其他实现方式还包括UTF-16和UTF-32,不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。

UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。

UTF-8的编码规则很简单,只有二条:

  1. 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。

  2. 对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。

下表总结了编码规则,字母x表示可用编码的位。

Unicode符号范围UTF-8编码方式
(十六进制)(二进制)
0000 0000-0000 007F0xxxxxxx
0000 0080-0000 07FF110xxxxx 10xxxxxx
0000 0800-0000 FFFF1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

下面,还是以汉字“严”为例,演示如何实现UTF-8编码。

已知“严”的unicode是4E25100111000100101),根据上表,可以发现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 执行完毕后自动被关闭,前提是,这些可关闭的资源必须实现Closeablejava.lang.AutoCloseable 接口。

Java四种代码块

  1. 普通代码块
    类中方法的方法体,定义在方法中,不管是普通还是静态,方法被调用的时候执行。
  2. 构造代码块
    构造块会在创建对象时被调用,每次创建时都会被调用,优先于类构造函数执行。类加载时不调用(如调用静态函数时outClass.out(),只执行静态代码块,不执行构造代码块)
  3. 静态代码块
    用static{}包裹起来的代码片段,类加载时调用,只会执行一次。静态代码块优先于构造块执行。
  4. 同步代码块
    使用synchronized(){}包裹起来的代码块,在多线程环境下,对共享数据的读写操作是需要互斥进行的,否则会导致数据的不一致性。同步代码块需要写在方法中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风吹千里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值