1)bit指“位”,是数据传输速度的计量单位,常简写为“b”;Byte指“字节”,是文件大小的计量单位,常简写为“B”。java
2)Byte和bit的换算关系是,1 Byte=8 bits。在电脑上,一个英文字母须要占用1 Byte的硬盘空间,一个汉字则需占用2 Byte。 以下图: android
例如,在咱们java语言中,一个int 占4 byte,也就是占32bit,后面我会讲到int在一些源码里面的妙用程序员
一个数在计算机中的二进制表示形式, 叫作这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1.markdown
将一个数字转换成二进制(机器数)就是这个数值框架
反码的表示方法是:正数的反码是其自己;负数的反码是在其原码的基础上, 符号位不变,其他各个位取反。ide
补码的表示方法是:正数的补码就是其自己;负数的补码是在其原码的基础上, 符号位不变, 其他各位取反, 最后+1。 (即在反码的基础上+1)函数
十进制原数 | 原码 | 反码 | 补码 |
---|---|---|---|
10 | 0000 1010 | 0000 1010 | 0000 1010 |
-10 | 1000 1010 | 1111 0101 | 1111 0110 |
5 | 0000 0101 | 0000 0101 | 0000 0101 |
-5 | 1000 0101 | 1111 1010 | 1111 1011 |
简化了计算机的设计,计算机只能进行加法运算,经过补码的设计,使之能够在这种设计下,进行减法运算。 好比 1-1 在计算机中执行的 其实是 1 +(-1) 即补码运算,也就是说,全部计算都是使用该数的补码,计算完成之后再换回源码。后面基于负数的计算能够详细了解。测试
两个数,从最低位到最高位,一一对应。若是某 bit 的两个数值对应的值都是 1,则结果值相应的 bit 就是 1,不然为 0.this
int x = 1; // 0000 0001
int y = 2; // 0000 0010
复制代码
为方便后续运算和对比,我后面的运算符均使用这两个数加密
x&y = 0001 & 0010 = 0000 = 0
y&x = 0010 & 0001 = 0000 = 0
x&x = 0001 & 0001 = 0001 = 1
y&y = 0010 & 0010 = 0010 = 1
复制代码
两个数,从最低位到最高位,一一对应。若是某 bit 的两个数值其中一个是 1,则结果值相应的 bit 就是 1,不然为 0.
x|y = 0001 | 0010 = 0011 = 3
y|x = 0010 | 0001 = 0011 = 3
x|x = 0001 | 0001 = 0001 = 1
y|y = 0010 | 0010 = 0010 = 2
复制代码
两个操做数进行异或时,对于同一位上,若是数值相同则为 0,数值不一样则为 1。
x^y = 0001 ^ 0010 = 0011 = 3
y^x = 0010 ^ 0001 = 0011 = 3
x^x = 0001 ^ 0001 = 0000 = 0
y^y = 0010 ^ 0010 = 0000 = 0
复制代码
对于这个数每一位 1变0、0变1
~x = ~0000 0001 = 1111 1111 ......(省略) 1111 1110
复制代码
规则 a >> b 将数值 a 的二进制数值从 0 位算起到第 b - 1 位,总体向右方向移动 b 位,符号位不变,高位空出来的位补数值 0。
y>>1 = 0000...0010(源码) >>1 = 0000...0010(补码)>>1 = 0000...0001(运算后的补码)=0000 ... 0001(源码)= 1
-y>>1 = 1000 ... 0010(源码) >>1 = 1111 ... 1110(补码)>>1 = 1111 ...1111(运算后的补码)= 1000...0001(源码)= -1
//其实全部运算都经历了源码-补码-计算-源码的过程,下面就省略这个过程直接给结论
复制代码
规则 a << b 将数值 a 的二进制数值从 0 位算起到第 b - 1 位,总体向左方向移动 b 位,符号位不变,低位空出来的位补数值 0。
y<<1 = 0000 ... 0010<<1= 0000 ... 0100 = 4
-y<<1 = 1000 ... 0010<<1= 1000 ... 0100 = -4
复制代码
公式总结:
无符号右移规则和右移运算是同样的,只是填充时无论左边的数字是正是负都用0来填充,无符号右移运算只针对负数计算,而且结果必定是一个正数,由于对于正数来讲这种运算没有意义
y>>>1 = 0000 ... 0010 >>>1 = 0000 ... 0001 = 1
-y>>>1 = 1000 ... 0010 (源码)>>1 = 1111 ... 1110 (补码)>> 1 == 0111 ... 1111(计算以后的补码) = 0111 ... 1111(源码)= 2147483647
//由于负数最高位补0 变成了正数,正数的补码源码都是它本身,因此变成了一个很大的数,这一点要特别注意。
复制代码
x = x^y = 0001 ^ 0010 = 0011 = 3
y = y^x = 0010 ^ 0011 = 0001 = 1
x = x^y = 0011 ^ 0001 = 0010 = 2
复制代码
正好互换了,因此之后就能够这么写:
x^=y,
y^=x,
x^=y
复制代码
基于以上特性,能够实现对一个数字进行加密,一串数字,对一个中间数字进行异或运算,获得加密数据,解密再次对中间数字进行异或便可。
平时你们写代码是否遇到过这样的场景:一个类,有一个属性是用boolean表示,隔了一段时间,又须要新加一个boolean表示新的属性。。。因此就如同下面的代码:
public class Human {
/** * 是不是学生 */
private boolean isStudent;
/** * 是否已经成年 */
private boolean isAdult;
/** * 是否单身 */
private boolean isSingle;
// private boolean is.....
public void setStudent(boolean student) {
isStudent = student;
}
public boolean isStudent() {
return isStudent;
}
// setter and getter...
}
复制代码
那如今,经过位运算,咱们能够这么写:
public class Human {
/** * 是不是学生 */
public static final int IS_STUDENT = 1;
/** * 是否已经成年 */
public static final int IS_ADULT = 2;
/** * 是否单身 */
public static final int IS_SINGLE = 4;
private int properties;
public void setProperties(int properties) {
this.properties = properties;
}
public boolean isStudent() {
return (properties & IS_STUDENT) != 0;
}
public boolean isAdult() {
return (properties & IS_ADULT) != 0;
}
public boolean isSingle() {
return (properties & IS_SINGLE) != 0;
}
@Override
public String toString() {
return "是不是学生 " + isStudent() + " 是否成年 " + isAdult() + " 是否单身 " + isSingle();
}
}
复制代码
咱们在传入参数只提供一个setProperties 方法,咱们在传入参数的地方用 “|”运算符,分隔咱们想要指定的属性,下面是测试代码:
public static void main(String args[]) {
Human human = new Human();
human.setProperties(Human.IS_STUDENT);
System.out.println(human.toString());
human.setProperties(Human.IS_STUDENT | Human.IS_SINGLE);
System.out.println(human.toString());
human.setProperties(Human.IS_SINGLE | Human.IS_ADULT);
System.out.println(human.toString());
human.setProperties(Human.IS_STUDENT | Human.IS_SINGLE | Human.IS_ADULT);
System.out.println(human.toString());
}
复制代码
输出结果
是不是学生 true 是否成年 false 是否单身 false
是不是学生 true 是否成年 false 是否单身 true
是不是学生 false 是否成年 true 是否单身 true
是不是学生 true 是否成年 true 是否单身 true
复制代码
原理分析: 首先,注意看,我定义的常量除了0以外都是一、二、4 、即2 ^ n
a = 1 = 0000 0001
b = 2 = 0000 0010
c = 4 = 0000 0100
d = 8 = 0000 1000
e = 16 = 0001 0000
f = 32 = 0010 0000
//......即我能够定义最多31个数(第32位表正负)
复制代码
这样一来,咱们用“|” 运算符将其中任意两个或者多个进行计算的时候,其实是把它们按照本身的占位保存了例如:
int x = c|d|e = 0001 1100 = 28
//此时判断x是否包含 c 或者 d
//用&便可
int y = z&c = 0000 0100 = 4 = c
int z = z&d = 0000 1000 = 8 = d
其实只要结果不为 0000 0000 也就是0 表示&运算符成立 ,就能够判断是否包含该数字,即上面函数的方法的定义。
复制代码
下面我看看,众所周知,咱们Android LinearLayout 有这样一个属性"showDividers":
<LinearLayout
android:showDividers="beginning|middle|end"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
复制代码
分别表示你能够在view的几个位置展现分割线,为何能够用这个属性表示三个位置呢?咱们进入源码看看:
private int mShowDividers;
public static final int SHOW_DIVIDER_NONE = 0;
public static final int SHOW_DIVIDER_BEGINNING =1;
public static final int SHOW_DIVIDER_MIDDLE = 2;
public static final int SHOW_DIVIDER_END = 4;
public void setShowDividers( int showDividers) {
if (showDividers == mShowDividers) {
return;
}
mShowDividers = showDividers;
setWillNotDraw(!isShowingDividers());
requestLayout();
}
protected boolean hasDividerBeforeChildAt(int childIndex) {
if (childIndex == getVirtualChildCount()) {
return (mShowDividers & SHOW_DIVIDER_END) != 0;
}
boolean allViewsAreGoneBefore = allViewsAreGoneBefore(childIndex);
if (allViewsAreGoneBefore) {
return (mShowDividers & SHOW_DIVIDER_BEGINNING) != 0;
} else {
return (mShowDividers & SHOW_DIVIDER_MIDDLE) != 0;
}
}
复制代码
我只列出了上面核心的几行代码,首先,全部的的显示属性都是 一个int 的 mShowDividers 表示,set方法用“|” 进行指定,在看核心的代码在onDraw 方法中绘制分割线的时候,会调用这个方法,判断方法就是用&运算符。 另外,View.MeasureSpec 和Gravity 的类也用到了位运算符,具体这里就不深刻探讨了。
参考源码: Android :