计算机概论

Posted by Liber Sun on July 10, 2018

一些计算机常识问题

计算机为什么能够进行计算?

“数”以二进制的方式在计算机中进行存储。

“数”通过 与 或 非 异或 同或等布尔电路进行计算。

我们知道了计算机可以利用电路来计算

程序运行的基本原理

虽然我们可以通过组合不同的 “原子电路” 来完成计算任务,但是我们应该通过 命令 来控制计算机,让计算机按照某种命令(电信号)来执行。

想做的事情—>程序—>汇编代码—>CPU指令集(二进制编码)—>CPU计算

二进制如何表示信息

我们知道二进制的可以用来进行存储之后,我们需要了解如何把信息编码为位模式。

位表示文本

文本形式的信息通常由一种代码表示,其中文本的每一个符号均为赋予唯一的位模式。这样,文本就是一个长长的位串,逐一的位来表示原文本中的符号。

  • ASCII用了1个字节来表示字母、标点符号、数组以及控制字符(最高位为0,表示2的7次方,共127个字符)
  • 扩展的ASCII将最高为的0,设置为1,以表示除英语字母和关联的标点符号以外的字符。
  • UNICODE用了2个字节,以适应更多语言书写的文本。其中UTF-8,UTF-16等是UNICODE的实现。

详情

注意文本的存储是位串,但是文本的显示是由操作系统中定义的字的点阵来决定的,将这个点阵制作成一个图像,发送到显示器上。

可以用一个unsigned char类型点阵数组fontdata_8x16[]来实现所有ASCII码字符的点阵形状,每一个字符的点阵由16个元素构成。

对于点阵,他的首地址索引值就是字符的ASCII值,即可根据字符的ASCII码值得到字符点阵首地址, 首地址为:fontdata_8x16[ASCII码值16]的地址。 比如字符’A’ 他的首地址就是fontdata_8x16[1040],然后往后面取16个元素。 1616个像素进行描绘。

位表示数值

二进制补码表示 整数 浮点记数法表示 分数

位表示图像

通常将一个图像表示为一组点,每一个点成为一个pixel,每个像素被编码,整个图像就表示为这些像素的集合。这个集合被称为位图。 在黑白图像中,每个像素由一个位表示。 但对于彩色图像,就需要RPG(255+255+255),需要三个字节来存储。

位图存在的一个问题是图像不能轻易的调节到任意大小。基本上,放大图像就是变大像素,这样会使图片呈现颗粒感。 为了避免缩放,表示图像有时候还可以表示为集合结构的集合。这些集合结构可以解析为几何级数来编码。

位运算的妙用

&操作

  • 判断 奇偶
  if(a&1){
    //说明是奇数
    //任何一个偶数的二级制的第一位都必定是0,与1(01)做与操作,若为0,则为偶数,若为1,则为奇数
    // 0->   0
    // 2->  10
    // 4-> 100
    // 6-> 110
    // 8->1000
}
  • a% 2 等价于 a&1
  • a% 2^n 等价于 a&(2^n-1) 2^n意味着所有的二级制 都是 00 10 110 1110 11110

  • 清除特定位和获取特定位
  s=s&mask //mask特定位置为0,其余为1,清除操作
  s=s&mask// mask特定位置为1,其余为0,获取操作

|操作

  • 对于某一位 无条件赋值1
s=s|mask //mask特定位置为1,其余位置为0

« 和  »

  • 乘除法
  x<<n  //左移一位相当于乘以2
  x>>n  //右移一位相当于除以2

»>

无符号右移,忽略符号位,空位用0补齐

^

参与运算的两个值,如果两个相应bit位相同,则结果为0,否则为1。 异或满足交换律 a ^ b = b ^ a。 满足结合律 a ^ b ^ c = a ^ (b ^ c) = (a ^ b) ^ c;

  • 0^任何数=任何数本身
  • 任何数^自己=0
  • 1^任何数= -(~任何数) // 1^2 等价于-(~2) 或者~(~2)+1

应用:

  • 交换a、b的值,不能使用额外的空间(异或 同一数两次,会恢复原状)
    a=a^b;
    b=b^a;//b=b^a^b=a;
    a=a^b;//a=a^b^a=b;
  • 特定位翻转 10100001 ^ 00100000 = 10000001

  • 底层实现加法:异或实现不带进位的结果,与操作是进位。

~:取相反数(减法变加法)

  (~x+1)  等价于 -x
  y+(~x+1) 等价于 y+(-x)

32位(X86)和64位(X64)

这里的位数关键在于CPU是否支持对应位数的寻址,即CPU的GPRs(通用寄存器)的数据宽度,一次处理可以提取多少位的数据。

32位的电脑 最大内存 是2的32次方 及4G的内存大小 64位的电脑 最大内存 是2的64次方 但为了和CPU处理能力匹配 人为规定为128G

编码

世界上存在着多种编码方式,同一个二进制数字可以被解释为不同的符号。因此要打开一个文本文件,就必须知道它的编码方式 否则用错误的编码方式解读,就会出现乱码。

ASCII

ASCII是八位的二进制数组合来表示128或者256种可能的字符。

ASCII规定了128个字符的编码,这128个符号,只占用了一个字节的后面7位,最前面的那1位统一规定为0

其中0~31 以及127 是控制字符以及通信专用字符,如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(响铃)等;通信专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等;

ASCII设计为一个字节,只能表示0~255种符号。但是要表示中文,显然一个字节是不够的,至少需要2个字节,而且不能和Ascii冲突,所以中国制定了GB2312编码,来实现中文编码。

而GBK是在GB2312的基础上进行的扩展,支撑了更多的中文的显示。

同时我们要知道 繁体中文使用的是 Big5码。

但是类似的 日文和韩文也出现了这种问题,为了统一所有文字的编码。Unicode应用而生,由ISO提出。

Unicode

Unicode把所有语言都统一到一套编码里,每一个符号都给予一个独一无二的编码,这样就不会再有乱码问题了。Unicode通常用两个字节表示一个字符,原有的英文编码从单字节变成双字节,只需要把高字节全部填为0就可以。

但是填充0必然对存储是极大的浪费。它们造成的结果是:

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

UTF-8

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

UTF-8是一种变长字节编码方式。UTF-8使用一至四个字节

  • 1.128个US-ASCII字符只需一个字节编码(Unicode范围由U+0000至U+007F)。
  • 2.带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母则需要二个字节编码(Unicode范围由U+0080至U+07FF)。
  • 3.其他基本多文种平面(BMP)中的字符(这包含了大部分常用字)使用三个字节编码。
  • 4.其他极少使用的Unicode辅助平面的字符使用四字节编码。

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

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

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

0000 0000 ~ 0000 007F | 10xxxxxx
0000 0080 ~ 0000 07FF | 110xxxxx 10xxxxxx  //使用两个字节
0000 0800 ~ 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx //使用三个字节
0001 0000 ~ 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx  //使用四个字节

已知 “严”的unicode是\u4e25 对应二进制为 ‭ 0100 111000 100101‬ 经过UTF-8编码后为 1110 0100 10 111000 10 100101 =>十六进制为‭E4B8A5‬

严16进制存储为:利用gitbash的vim 1)vim -b xxx.txt 2):%!xxd

ANSI: "D1 CF" 是GB2312编码  //如果是繁体,会采用BIG5编码,如果是英文就是ASCII编码
Unicode: " fffe 254e"  
Unicode-big: "feff 4e25"   //java 是feff
utf-8:       "e4b8 a5"

在了解了他们的关系之后,我们来总结字符编码工作的方式: 在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者传输时,就转换为UTF-8编码。

补码

数在计算机中的表示,并不是就直接以我们认知中的二进制进行表示,他们都说是利用了补码就行存储。 如:有符号数在计算机中用最高位表示符号,如1000 0001 其真正的数值是-127 而不是65(无符号数1000 0001转换成十进制等于65)

127  符号为0             //正数的源码,补码,反码是一样的。
源码   0000 0000 0000 0000 0000 0000 0111 1111

-127 符号为1
补码   1111 1111 1111 1111 1111 1111 1000 0001(127取反,+1)


127+(-127)=0
  0000 0000 0000 0000 0000 0000 0111 1111
+ 1111 1111 1111 1111 1111 1111 1000 0001
-----------------------------------------
1 0000 0000 0000 0000 0000 0000 0000 0000 //最高位溢出
  0000 0000 0000 0000 0000 0000 0000 0000 //0

首先,要明确一点。计算机内部用什么方式表示负数,其实是无所谓的。只要能够保持一一对应的关系,就可以用任意方式表示负数。所以,既然可以任意选择,那么理应选择一种最方便的方式。

补码就是最方便的方式。它的便利体现在,所有的加法运算可以使用同一种电路完成。

证明补码最方便

已知 X、Y为正整数,Z=X-Y=X+(-Y)

证明 Z=X的补码+(-Y的补码)

X的补码 = X; // 正数的补码为其自身
-Y的补码 = (1111 1111 - Y) + 1; // 负数的补码为  负数的绝对值取反再加1
Z = X的补码 + (-Y的补码)
  = X +  (1111 1111 - Y) + 1
  = X - Y + 1 0000 0000
  = X - Y + 0000 0000
  = X - Y
// 1 0000 0000就相当于0000 0000(舍去了最高位)

这就证明所有的减法都可以由加法实现,因此利用加法电路,就可以完成所有整数的加法。 如果采用直接计数法,那么我们就需要涉及一个加法电路和一个减法电路。

补码溢出问题

超上限的X=X-256 超下线的X=x+256

下限的相反数与下限相等 上限的相反数是上限直接取负值

补码转型问题

如果最初的数值类型是有符号的,那么进行符号扩展。 如果是char型(无符号),那么进行零扩展。 如果目标长度小于源类型的长度,那么将进行截断。

System.out.println((int)(char)(byte)-1); //65535
(-1)        //0xffffffff
(byte)(-1) //截断 0xff
(char)(0xff)//符号扩展 0xffff
(int)(0xffff)//零扩展 0x0000ffff 对应二进制是 65535



System.out.println((int)(char)(-1 & 0xff));//255
(-1)      //0xffffffff
(-1& 0xff)//前24被强制设为0 后8位为1 0x000000ff
(char)(0xff)//截断 0x00ff
(int)(0x00ff)//零扩展,0x000000ff 对应二进制是255

四则运算计算机中的实现原理

加法

A=10010001,B=11010011。我们首先人工进行计算

  10010001
+ 11010011
 101100100

我们在人工进行计算时从右往左依次将后两位进行相加,如果有进位则进行进位相加。 其实计算机在实现二进制加法时也是这样逐位相加的,有进位则进位。

我们将其分为加法位与进位

1.加法位

直接利用^, A^B得到的就是加法位的运算结果。

2.进位

利用&操作,A&B得到进位的信息。

这样我们就可以得出加法的步骤:

  1.本位 异或操作
  2.进位 与操作+左移1
  3.重复步骤1,直到进位为0

利用异或电路和与电路,我们可以实现一位的加法,再通过电路的组合,我们可以实现多位的加法。

减法

(-y) 等价于 ~y+1,这里是因为-y的二进制存储是 由y取反加1获得的 因此x-y=x+(~y+1),即所有的减法都可以转换为加法。

乘法

比如 1011(被乘数)*1010(乘数)

(1*2^3+0*2^2+1*2^1+1*2^0)*(1*2^3+0*2^2+1*2^1+0*2^0)  //x*2^n 等价于左移 n位
1011 左移3位+ 1011 左移1位
1011000 + 10110
1101110(十进制为156)

除法

比如 1011(被除数)/1010(除数) 模拟除法就行了

C盘为什么要快一点

对于机械硬盘(HDD)的读取,硬盘的主轴的工作方式都是CAV(Constant Angular Velocity,恒定角速度,单位时间内放置的角度一致),所以在相同时间内,读取位于硬盘外圈的数据,比读取硬盘内圈的数据要多。

就是说 外圈读取速度快。而按照正常的分区方法,C盘就位于硬盘的外圈,然后D、E、F逐渐向内。

对于固态硬盘(SSD)而言,我们做的就是将C盘存储到SSD中,顾名思义,会快。

编译与反编译

词法分析、语法分析、语义分析、中间代码生成、代码优化(非必须)和目标代码生成。

目标代码有三种:

  • 可以立即执行的机器语言代码,所有地址都重定位(不包含没有定位的);
  • 待装配的机器语言模块,当需要执行时,由连接装入程序把它们和某些运行程序连接起来,转换成能执行的机器语言代码;
  • 汇编语言代码,须经过汇编程序汇编后,变成为可执行的机器语言代码

计算机语言

计算机语言(Computer Language)指用于人与计算机之间通讯的语言。计算机语言是人与计算机之间传递信息的媒介。

计算机系统最大特征是指令通过一种语言传达给机器。为了使电子计算机进行各种工作,就需要有一套用以编写计算机程序的数字、字符和语法规划,由这些字符和语法规则组成计算机各种指令(或各种语句)。这些就是计算机能接受的语言。

机器语言

机器语言是用二进制代码表示的计算机能直接识别和执行的一种机器指令的集合。机器语言具有灵活、直接执行和速度快等特点。但是不同型号的计算机其机器语言是不相通的,按着一种计算机的机器指令编制的程序,不能在另一种计算机上执行。

因为机器语言是使用二进制表示的,所以编出的程序全是些0和1的指令代码。

机器语言的优点就是可以直接被计算机识别和执行,比较高效,但是同时也有很多缺点,如:

1、机器只认识0和1,程序员很难记住每个指令转成0和1的组合是什么,需要查大量的表格来确定每个数字表示什么意思

2、因为它的书面形式全是”密”码,所以可读性差,不便于交流与合作。

3、因为它严重地依赖于具体的计算机,所以可移植性差,重用性差。

因此有了汇编语言。

汇编语言

汇编语言使用助记符(Mnemonics)来代替和表示特定低级机器语言的操作。

助记符(mnemonic)是便于人们记忆、并能描述指令功能和指令操作数的符号,助记符是表明指令功能的英语单词或其缩写。如用ADD表示加法、MOV表示传送、SUB表示减法等。

但是汇编语言只是让用户理解的语言,计算机并不认识汇编语言,因此我们需要将汇编语言转为机器语言代码(二进制)。这一过程称为汇编过程

由于汇编更接近机器语言,能够直接对硬件进行操作,生成的程序与其他的语言相比具有更高的运行速度,占用更小的内存,因此在一些对于时效性要求很高的程序、许多大型程序的核心模块以及工业控制方面大量应用。

但是其不存在语法的概念,由此高级语言出现了。

高级语言

高级语言是高度封装了的语言,与低级语言(包括机器语言与汇编语言)相对。

它是以人类日常语言为基础的一种编程语言,使用一般人易于接受的文字来表示(例如汉字、不规则英文或其他外语),从而使程序编写员编写更容易,亦有较高的可读性,以方便对电脑认知较浅的人亦可以大概明白其内容。

高级语言带来的好处包括:

  1. 高级语言接近算法语言,易学、易掌握,一般工程技术人员只要几周时间的培训就可以胜任程序员的工作;

  2. 高级语言为程序员提供了结构化程序设计的环境和工具,使得设计出来的程序可读性好,可维护性强,可靠性高;

  3. 高级语言远离机器语言,与具体的计算机硬件关系不大,因而所写出来的程序可移植性好,重用率高;

  4. 由于把繁杂琐碎的事务交给了编译程序去做,所以自动化程度高,开发周期短,且程序员得到解脱,可以集中时间和精力去从事对于他们来说更为重要的创造性劳动,以提高程序的质量。

当然它也是计算机不能理解的,甚至于离机器语言更加远了,计算机没法识别高级语言,因此需要将其转换为机器语言。

异步与同步

同步:顺序执行。同步就是顺序执行,执行完一个再执行下一个,需要等待、协调运行。 异步:异步和同步是相对的,异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作。线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步等待另一线程的完成,从而可以让主线程干其它的事情。

异步是当一个调用请求发送给被调用者,而调用者不用等待其结果的返回而可以做其它的事情。实现异步可以采用多线程技术或则交给另外的进程来处理。

进程和线程

进程是资源分配的最小的单位,简单的理解是我们的Main函数,就是我们的EXE,就是我们打开ctrl+alt+del的任务管理器中的一项。 进程是程序启动后的实例。

线程是程序执行的最小单位,一个进程往往包括多个线程。就是我们在Main函数中声明的Thread。所有的线程可以共享进程的资源。

进程往往是相互独立的、互不干扰,因此进程之间的通信会相对复杂,包括有名管道、无名管道、消息队列、共享内存、信号量、以及套接字等通信机制。

线程往往位于一个进程之内,因此可以使用共享内存进行通信,当然也可以通过消息通知来进行通信。