燃尽了,写到半夜两点。
分类: 计算机
-
计算机网络 复习
这是个重量级的复习资料,因为疫情提前放假,我足足复习了一个月,只能说薄纱期末了。
-
Android 课设(记录不完整)
由于开发任务重,开发部分记录缺失。
一、目的与要求- 掌握基于 Android 的网络编程的相关知识。
- 学会如何定义网络协议。
- 学会如何传输表情、图片等特殊信息。
二、实验内容
- 建立简单的 SOCKET 通信服务端和客户端。服务端 ServerAddress=InetAddress.getByName(“192.168.1.142”);
socket=new Socket(ServerAddress,500);
commsThread=new CommsThread(socket);
public class CommsThread extends Thread {
private final Socket socket;
private final InputStream inputStream;
private final OutputStream outputStream;
public CommsThread(Socket sock) {
socket = sock;
InputStream tmpIn = null;
OutputStream tmpOut = null;
try {
// —创建用于通过套接字读写的输入流和输出流对象—
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
Log.d(“SocketChat”, e.getLocalizedMessage());
}
inputStream = tmpIn;
outputStream = tmpOut;
}
public void run() {
// —流的缓冲区存储—
byte[] buffer = new byte[1024];
// —bytes returned from read()—
int bytes;
// —一直监听 InputStream 直到异常发生—
while (true) {
try {
// —从inputStream读取—
bytes = inputStream.read(buffer);
// —更新主活动UI—
SocketsActivity.UIupdater.obtainMessage(0, bytes, -1, buffer).sendToTarget();
} catch (IOException e) {
break;
}
}
}
// —从主活动调用此函数将数据发送到远程设备—
public void write(byte[] bytes) {
try {
outputStream.write(bytes);
} catch (
IOException e) {
}
}
// —从主活动调用此函数以关闭连接—
public void cancel() {
try {
socket.close();
}
catch (IOException e) {
}
}
}注意在 androidManifest 中添加用户权限: <uses-permission android:name=*”android.permission.INTERNET”* />客户端 ServerAddress = InetAddress.getByName(“192.168.1.142”); socket = new Socket(ServerAddress, Serverport);
Socet.connect(SocketAddress remoteAddress);
InputeStream tmpIn = socket.getInputStream();
OutputStream tmpOut = socket.getOutputStream();
while(bRunning) {
tmpIn.read(byte[] buf,int offset,int length);
tmpOut.write(byte[] buffer,offset,int length);
}
登录完成后,进行前面类似的 Presence、Message、IQ 报文进行信息交互;
三、思路
建立一个服务器,服务器暂定用 Node.js 来写。
用到的工具:
Node.js
Android
Json
xml
存疑
看得出来建议使用的是
xml
配合xmpp 协议
。但我希望使用的是Json
,因为服务端好解析。可以研究XMPP 协议
的原理,再移植到Json
上。四、大致过程
测试
编好布局
写好主界面的功能
编写安卓 TCP 服务
在服务器上建立相对应的 TCP 服务端
-
汇编复习
考试范围
1、题型:简答(6 个,24 分);计算写出 OF SF ZF CF 值;指令执行题;内存绘制题;编程题;论述题
2、第一、二、三章(汇编语言的组合;计算机中数和符号的表示;补码的加减运算;逻辑运算规则;汇编程序的优势;寄存器的作用和组成;8086 结构特征;段;有效地址、物理地址;外设和接口;32 位 CPU 工作模式;汇编编程所需文件;上机步骤)
第四章(寻址方式)
第五章(指令系统)
第六章(伪指令,变量属性,赋值伪指令,过程定义伪指令,数值回送伪指令)
第七章(分支,循环)
第一章 基础
1.1 简介
1.1.1 机器语言与汇编语言
三类计算机程序设计语言:
- 机器语言
- 汇编语言
- 高级语言
汇编指令也称为符号指令。汇编语言也称为符号语言。符号也称为助记符。
汇编程序 是把汇编语言源程序翻译成机器码的程序。这个过程称为 汇编。
1.1.2 汇编语言的组成
- 汇编指令:机器码助记符,是汇编语言的核心
- 伪指令:没有对应机器码,计算机不执行
- 其他符号:如
+、-、*、/
编译器识别,没有对应机器码
1.1.3 学习意义
- 对于计算机应用开发有重要作用
- 是从根本上认识和理解计算机工作过程的最好办法
1.2 数据表示
1.2.1 进制转换
- B 二进制
- D 十进制
- H 十六进制
101101(B)=45(D)=101101B=45D=1011012=4510
1.2.2 二进制和十六进制的运算
略
1.2.3 带符号数的补码
补码 符号位 数据位 正数 0 原 0 0 0 负数 1 其正数数据位反码再把最低位加一 性质:补码的补码是原码。
1.2.4 补码加减法
[ X + Y ]补 = [ X ]补 +[ Y ]补
[ X – Y ]补 = [ X ]补 +[ – Y ]补
不必考虑数字的正负,符号位直接参与运算。最高位的进位丢弃。
1.2.7 逻辑运算
- 与 AND
- 或 OR
- 非 NOT
- 异或 XOR异或就是异,不一样则 true。
小结
- 汇编语言的优势?因为用汇编语言设计的程序最终被转换成机器指令,故能够保持机器语言的一致性,直接、简捷,并能像机器指令一样访问、控制计算机的各种硬件设备,如磁盘、存储器、CPU、I/O 端口等。使用汇编语言,可以访问所有能够被访问的软、硬件资源。目标代码简短,占用内存少,执行速度快,是高效的程序设计语言,经常与高级语言配合使用,以改善程序的执行速度和效率,弥补高级语言在硬件控制方面的不足,应用十分广泛。
第二章 计算机基本原理
2.1 计算机系统组成
基本工作原理
- 存储程序
- 程序控制
这种原理称为冯诺依曼原理,这样的计算机称为冯诺依曼计算机,这种计算机的体系结构称为冯诺依曼结构。
典型冯诺依曼结构如图所示。
主要部分:
- 中央处理器
- 存储器
- 输入/输出子系统
三部分由系统总线连在一起。
2.2 存储器
2.2.2 存储器
- 基本存储单元
- 最小单位:一个二进制位 bit
- 一个字节:八位二进制 Byte
- 一字 (Word):2 字节 Word
- 双字:2 字 32 位
- 字的存储一字 16 位,高八位为高位字节,低八位为低位字节。规定高位字节在高地址单元,低位字节在低地址单元。
2.2.3 存储器分段
- 分段概念8086 系统有 20 根地址线,可以寻址 1MB。但 8086 系统是 16 位机,16 位结构解决 20 位地址,采用分段法。段的划分并不是在内存,是来自 CPU。计算物理地址 段地址 * 16 + 偏移地址 = 物理地址。 (注意,* 16 是左移 4 位,不是左移 16 位!)每个段 64KB,因为偏移地址 16 位。意义:使得程序设计时,程序保持相对的完整性。
- 段的类型存储器逻辑分段类型:
- 代码段 存放指令,段基址存放在段寄存器 CS(Code Segment)
- 数据段 存放数据,段基址存放在段寄存器 DS(Data Segment)
- 附加段 辅助存放数据,段基址存放在段寄存器 ES(Extra Segment)
- 堆栈段 重要的数据结构,可用来保存数据、地址和系统参数,段基址存放在段寄存器 SS(Stack Segment)
2.2.4 逻辑地址
用户编程时使用的地址。
段地址和偏移地址都是 16 位二进制数。
有可能多个逻辑地址组织对应到同一个物理单元上。因此逻辑地址不是唯一的。
CPU 要想读写数据,需要进行下面三类交互:
- 存储单元地址
- 器件选择,读/写命令
- 读/写数据
地址、数据、控制信息传送是通过 总线 的。总线有三类:
- 地址总线
- 控制总线
- 数据总线
2.3 寄存器
2.3.1 寄存器介绍
典型 CPU 由 运算器、控制器、寄存器 等器件构成。
8086 CPU 有 14 个寄存器。
按使用类别划分,寄存器分三类:
- 通用寄存器
- 段寄存器
- 专用寄存器
寄存器组如下:
- 通用数据寄存器
- AX Accumulator,累加器,运算时较多使用
- BX Base,基址寄存器,除了存储数据,一般存放一段内存的起始偏移地址
- CX Count,计数寄存器,除了存储数据,一般存放重复操作的次数
- DX Data,数据寄存器,除了存储数据,有时存放 32 位数据的高 16 位
- 地址寄存器
- SP Stack Pointer,堆栈指针寄存器,存放堆栈栈顶偏移地址
- BP Base Pointer,基址指针寄存器,存放内存中数据的偏移地址
- SI Source Index,源变址寄存器,经常用来存放内存中源数据区的偏移地址。变址寄存器指的是,在某些指令作用下它可以自动递增或递减其中的值。
- DI Destination Index,目的变址寄存器,经常用来存放内存中目的数据区的偏移地址,在某些指令作用下它可以自动递增或递减其中的值。
- 段寄存器
- CS Code Segment,代码段寄存器,存放当前正在执行的程序段的地址。
- SS Stack Segment,堆栈段寄存器,存放堆栈段的段基址。
- DS Data Segment,数据段寄存器,用来存放数据段的段基址。
- ES Extra Segment,附加段寄存器,用来存放另一个数据段的段基址。 32 位 8086 处理器增加了两个段寄存器 FS、GS,功能类似 ES(EFG 可还行)。
- 指令指针寄存器
- IP Instruction Pointer,指令指针寄存器,存放即将执行指令的偏移地址。
- 标志寄存器
- FLAGS 存放 CPU 的两类标志:
- 状态标志反映处理器当前的状态(溢出,进位等)。六个状态标志:
- CF Carry Flag 进位/借位标志,表示加法有进位或者减法有借位。
- PF Parity Flag 奇偶标志,1 表示运算结果低八位有偶数个“1”。
- AF Assistent carry Flag 辅助进位标志。
- ZF Zero Flag 零标志,1 则运算结果为 0。
- SF Sort Flag 符号标志,是运算结果最高位(符号位),如果未溢出,1 为负数,0 为非负数。如果溢出了,结果就因错误而无意义,但符号标志仍然可以标志正负,溢出后,1 为正,0 为负(与正常结果相反)。
- OF Overflow Flag 溢出标志,1 则结果错误。无符号数也有这个标志但可以不处理。
- 控制标志控制 CPU 的工作方式,如 是否响应可屏蔽中断。三个控制标志:
- TF
- IF Interrupt Flag 中断允许,1 表示允许处理器响应可屏蔽中断请求信号,称为开中断。反之则关中断。
- DF Direction Flags 方向标志,0 时用增加的方式修改源或目的地址。见 SI/DI 寄存器
- 状态标志反映处理器当前的状态(溢出,进位等)。六个状态标志:
- FLAGS 存放 CPU 的两类标志:
2.3.2 CS 和 IP
CS 是 Code Segment 代码段寄存器,IP 是 Instruction Pointer 指令指针寄存器。
提供了 CPU 要执行的指令的地址。因此如果内存中的一段信息被 CPU 执行过,就一定被 CS:IP 指向过。
2.3.3 堆栈
数据存入 堆栈 是以 字 的方式存入(16 位),后存入的数据,物理地址小/低。栈指针 SP 指向栈顶,压栈则 -2。
常常用于保存调用的程序的返回地址和现场参数,也可以作为一种临时的数据存储区。
2.4 外设和接口
外设(外部设备)也称为 输入/输出设备,通过 输入/输出接口 与主机连接。
接口一方面连接外设,一方面通过总线与主机相连。接口内有若干寄存器,用于在 CPU 与外部设备之间传递信息。系统对外设接口中的寄存器进行统一编号,称为端口号。CPU 可以通过端口地址来区分和访问不同的外设。
端口/寄存器分为以下3类
- 数据端口 用来存放需要传递的数据。起数据缓冲作用。方向可以是输入/输出。
- 控制端口 传递 CPU 对外部设备的控制信号。例如启动磁盘工作。方向总是输出。
- 状态端口 协调外设与主机的同步。反映外设工作状态。例如某设备还未准备好接收数据,则不能向它发送数据。状态端口的传送方向对于 CPU 而言总是输入。
CPU与I/O接口中的端口的信息传输是通过数据总线进行的。
2.5 32 位 8086CPU 的工作模式
- 实模式 兼容 16 位机的特点,内存寻址范围 0 ~ 0FFFFFH 的 1MB 空间,地址线低 20 位有效。可以使用 32 位寄存器和 32 位操作数,MS DOS 只能在实模式下运行。
- 保护模式 是 32 位 8086CPU 的主要工作模式,特点是 全部地址线参与寻址 ,程序使用 逻辑地址,或者称为虚拟地址。虚拟地址的段选择符存放在寄存器中,应用程序不能更改,操作系统决定。 物理地址 是 16 位段选择符对应的 32 位段基址 和 32 位偏移地址组成。32 位即 4GB 大小。 采用虚拟地址使得各程序之间有严格的内存保护和隔离。
- 虚拟 8086 模式 就是生成多个虚拟的 8086CPU,以便运行实模式下的 8086 程序。 支持内存保护和隔离,可以同时运行多个程序。 Windows 下在 DOS 窗口运行一个 DOS 应用程序,则该程序运行在虚拟 8086 模式下。
小结
- 8086 结构特征
- 数据总线为 16 位;
- 运算器一次最多可以处理 16 位的数据;
- 寄存器的最大宽度为 16 位;
- 寄存器和运算器之间的通路为 16 位
第三章 汇编语言程序实例及上机操作
3.1 汇编语言的工作环境
3.1.1 汇编语言的系统工作文件
代码到程序三步骤
- 编辑
- 汇编
- 连接
编辑形成 .ASM 文件(代码文件),汇编成为 .OBJ 文件,连接形成 .EXE 文件。
因此需要有四种程序:
- 编辑程序如记事本,EDIT.COM。
- 汇编程序如 MASM.EXE。
- 连接程序如 LINK.EXE
- 调试程序如 DEBUG.EXE
3.2 实例
code segment ;
assume cs:code ; 说明语句,指定CODE段与CS寄存器关联。
start: mov ah,1 ; START:是一个标号,MOV AH,1指令表示把1送AH寄存器,这是因为DOS系统功能的1号功能是键盘输入,所以要把功能号1送AH。
int 21h ; INT 21H 指令即调用DOS系统功能,其1号功能被执行,程序等待键盘输入,从键盘输入一个字符后,程序才继续执行。注意,这个从键盘输入的字符到哪里去了呢?它被放到寄存器AL中,是该字符的ASCII码。如果是字符“A”,则AL中就为41H。
mov dl,al ; 把AL送DL寄存器。
add dl,1 ; DL内容加1。为后面的2号功能调用准备输出的字符。
mov ah,2 ; 调用DOS系统功能的2号功能,显示DL中的字符。
int 21h ; 执行
mov ah,4ch ; 调用DOS系统功能的4CH号功能,4CH号功能是程序结束并返回到操作系统。
int 21h ; 执行
code ends ;
end start ; 说明语句,告诉汇编程序,汇编到此结束,程序的启动地址为标号为START的那条指令(第3行)。解析:
CODE
是段名,SEGMENT
和ENDS
是关键字。第 1 行
CODE SEGMENT
和第 11 行CODE ENDS
这 一对说明语句,定义了代码。第四章 操作数的寻址方式
- 立即寻址方式 MOV AL,6H 指令 目的 数字 源操作数是数字。直接把数字复制进入目的操作数。 只适用于源操作数,类型要和目的操作数相同(同样是字/字节)
- 寄存器寻址 MOV AX,BX 源操作数是寄存器。读出寄存器的值,写入目的操作数。
- 直接寻址方式 MOV AX,DS:[4050H] MOV AX,[4050] 源操作数是逻辑地址。读出地址的存储单元的值,写入目的操作数。
- 寄存器间接寻址方法 MOV AX,[BX] 操作数的地址 EA 在寄存器中。 读入 BX 的值,计算物理地址,再读物理地址的值,写入目的操作数。这里与直接寻址方式的区别在于读了两次,且第一次是寄存器,第二次也是寄存器。 注意,寄存器间接寻址中只允许
- BX(DS) Base 基址寄存器,Data Segment 数据段
- BP(SS) Base Pointer 基址指针寄存器,Stack Segment 堆栈段
- SI(DS) Source Index 源变址寄存器,Data Segment 数据段
- DI(DS) Destination Index 目的变址寄存器,Data Segment 数据段
- 寄存器相对寻址方法 MOV AX,TOP[SI] MOV [BX+2623H],AX 以上第一句,源操作数 有效地址 为 EA = SI + TOP(TOP 取其偏移地址若是寄存器或常量,多是 16 位,与 16 位寄存器匹配) 物理地址 为 DS * 10H + EA 然后读取这个物理地址的存储单元的值 这种方式常用于查表。适合访问一维数组。 注意:默认搭配 DS 段寄存器和 BX、SI、DI。SS 段寄存器和 BP
- 基址变址寻址方式 MOV AX,[BX+DI] 源操作数物理地址为 DS * 10H + BX +DI 取出该地址的存储单元的值。 允许使用的基址寄存器为
- BX(DS) Base 基址寄存器
- BP(SS) Base Pointer 基址指针寄存器
- SI Source Index 源变址寄存器
- DI Destination Index,目的变址寄存器
- 相对基址变址寻址方式 MOV AX,[BX+DI+MASK] 物理地址为 DS * 10H + BX + DI + MASK 注意:默认 DS 为数据端 允许使用的基址寄存器为
- BX(DS) Base 基址寄存器
- BP(SS) Base Pointer 基址指针寄存器
- SI Source Index 源变址寄存器
- DI Destination Index,目的变址寄存器
小结
- 选择寻址方式的两个原则
- 实用
- 有效
- 双操作数指令的提示
- 双操作数的两个操作数长度需要匹配。
- 两个操作数不能同时是内存单元。 因为一条指令需要使用地址线传输地址,如果都是地址,那么一条指令就需要传输两个地址,所以是不现实的。
第五章 常用指令系统
汇编语言的一般格式为:
[ 标号 : ] 指令助记符 [ 操作数 ] [ ; 注释 ]
例如:
START: MOV AX,DATA ; DATA 送AX
上述一般格式内的方括号为可选内容,由此可知,一条指令中只有 指令助记符 是必不可少的。
意义说明:
- 标号 标号是一个符号地址,用来表示指令在内存中的位置。 令使用标号时,应加冒号
:
- 指令助记符 指令助记符表示指令名称,是指令功能的英文缩写。
- 操作数 操作数表示指令要操作的数据或数据所在的地址。 可以是 寄存器、常量、变量,也可以由表达式构成。 80×86 指令一般带有 0 个、1 个或 2 个操作数。 双操作数指令的 第一个 操作数称为 目的操作数
DST
,存放操作结果。第二个 称为 源操作数SRC
。操作数之间用,
分开。 - 注释 注释由分号
;
开始。 注释超过一行则在每行都必须以分号开头。
5.1 数据传送指令
5.1.1 通用数据传送指令
- MOV(move) 传送 格式:
MOV DST,SRC
操作:(DST
)←(SRC
),将源操作数传送到目的操作数。 注意:DST
与SRC
长度必须明确且一致DST
与SRC
不能同为存储器,不允许在两个存储单元之间直接传送数据。这是因为一条指令最多发送一个地址。DST
不能为CS
或IP
,因为CS:IP
指向的是当前要执行的指令。DST
不允许是立即数。
- PUSH(push onto the stack) 进栈 格式:
PUSH SRC
操作:(SP
)←(SP
)-2 ((SP
)+1,(SP
))←(SRC
) 表示将源操作数压入堆栈(目的操作数),目的操作数地址由SS:SP
指定,指令中无需给出。SP
总是指向栈顶,堆栈操作以 字(16 位) 为单位进行操作。 - POP(pop from the stack) 出栈 格式:
POP DST
操作:(DST
)←((SP
)+1,(SP
))(SP
)←(SP
)+2 其中DST
表示目的操作数。将堆栈中源操作数弹出到目的操作数,堆栈中源操作数地址由SS:SP
指定,指令中无需给出。 源操作数弹出后,SP
加 2,下移一个字,指向新的栈顶。 - XCHG(exchange) 交换 格式:
XCHG OPR1,OPR2
操作:(OPR1
)← →(OPR2
) 其中OPR1
、OPR2
为操作数。把 2 个操作数互换位置。XCHG
为双操作数指令,两个操作数均是目的操作数,除了遵循双 操作数指令的规定,也不能用立即数寻址。
5.1.2 累加器专用传送指令
- IN(input)输入 把端口号 PORT 或由 DX 指向的端口的数据输入到累加器,根据端 口号的长度,有长格式和短格式两种形式。
- 长格式:IN AL,PORT(字节) IN AX,PORT(字) 操作:AL ←(PORT)AX ←(PORT) PORT 为端口号,端口号范围为 00~FFH 时,可以使用长格式指令 长格式指令,是指其机器指令长度为 2 个字节(端口号占 1 个字节)
- 短格式:IN AL,DX(字节) IN AX,DX(字) 操作:AL ←((DX))AX ←((DX)) 端口号范围为 0100H~0FFFFH 时,必须使用短格式指令。 短格式指令长度为 1 个字节,因为端口号存放在 DX 寄存 器中。
- OUT(output)输出 把累加器的数据输出到端口 PORT 或由 DX 指向的端口。与输入指 令相同,根据端口号的长度,分为长格式和短格式两种形式。
- 长格式:OUT PORT,AL(字节) OUT PORT,AX(字节) 操作:PORT ← AL PORT ← AX
- 短格式:OUT DX,AL(字节) OUT DX,AX(字节) 操作:(DX)← AL(DX)← AX
- XLAT(translate)换码 格式:XLAT 操作:AL ←(BX+AL) 把 BX+AL 的值作为有效地址,取出其中的一个字节送 AL。
5.1.3 地址传送指令
- LEA(Load Effective Address)有效地址送寄存器 格式:LEA REG,SRC 操作:REG ← SRC 把源操作数的有效地址 EA 送到指定的寄存器。
- LDS(Load DS with Pointer)指针送寄存器和 DS 格式:LDS REG,SRC 操作:REG ←(SRC)DS ←(SRC+2) 把源操作数 SRC 所指向的内存单元中 2 个字送到指定的寄存器 REG 和 DS。
- LES(Load ES with Pointer)指针送寄存器和 ES 格式:LDS REG,SRC 操作:REG ←(SRC)ES ←(SRC+2) 把源操作数 SRC 所指向的内存单元中 2 个字送到指定的寄存器 REG 和 ES。
5.1.4 标志寄存器传送指令
- LAHF(Load AH with Flags)标志送 AH 寄存器
- SAHF(Store AH into Flags)AH 送标志寄存器
- PUSHF(Push Flags)标志入栈
- POPF(Pop Flags)标志出栈
以上 4 条指令的格式相同,只有操作码部分,操作数为固定默认值。
5.2 算术运算指令
5.2.1 类型扩展指令
- CBW 字节扩展成字:convert byte to word
- CWD 字扩展成双字:convert word to double word
这两条指令的格式相同,只有操作码部分,无操作数部分。
操作数默认为累加器,无需在指令中给出。
当执行 CBW 时,默认将 AL 寄存器的内容扩展到 AX 寄存器中,扩展方法为符号扩展。
即如果 AL 的最高位为 1(负数),则 CBW 指令扩展时使 AH=FFH,如果 AL 的最高位为 0(正数),则 CBW 指令扩展时使 AH=00H。
当执行 CWD 时,默认将 AX 寄存器的内容扩展到(DX,AX)中,其中 DX 存放双字中的高位,AX 存放双字中的低位。
如果 AX 的最高位为 1(负数),则 CWD 指令扩展时使 DX=FFFFH,如果 AX 的最高位为 0(正数),则 CWD 指令扩展时使 DX=0000H。
5.2.2 加法指令
- ADD(add)加法 格式:ADD DST,SRC 操作:(DST)←(DST)+(SRC) ADD 指令将源操作数与目的操作数相加,结果存入目的操作数中。 特别需要注意,加法指令执行后会影响标志寄存器中的 CF 和 OF 标志位。
- ADC(add with carry)带进位加法 格式:ADD DST,SRC 操作:(DST)←(DST)+(SRC)+CF 其中上式中的 CF 为运算前 CF 标志位的值。
- INC(increment)加 1 格式:INC OPR 操作:(OPR)←(OPR)+1 该指令不影响 CF 标志位。
5.2.3 减法指令
- SUB(subtract)减法 格式:SUB DST,SRC 操作:(DST)←(DST)-(SRC)
- SBB(subtract with borrow)带借位减法 格式:SBB DST,SRC 操作:(DST)←(DST)-(SRC)-CF
- DEC(decrement)减 1 格式:DEC OPR 操作:(OPR)←(OPR)-1 该指令不影响 CF 标志位。
- NEG(negate)求补 格式:NEG OPR 操作:(OPR)←(OPR)
- CMP(compare)比较 格式:CMP OPR1,OPR2 操作:(OPR1)-(OPR2)
5.2.4 乘法指令
- MUL(unsigned mulutiple)无符号数乘法 格式:MUL SRC 操作:当操作数为字节时,(AX)←(AL)×(SRC) 当操作数为字时,(DX,AX)←(AX)×(SRC)
- IMUL(signed mulutiple)有符号数乘法 格式和操作与 MUL 相同,用来作有符号数乘法。
乘法指令中,目的操作数默认为累加器 AX,不必在指令中写出。
两个相乘的数必须长度相同,根据 SRC 的长度,默认参与运算的是 AL 寄存器的值(即为 AX 寄存器的第八位)或者是 AX 寄存器的值。
SRC 可以是寄存器或变量,但不能是立即数,因为立即数的长度是不明确的。
5.2.5 除法指令
- DIV(unsigned divide)无符号数除法 格式:DIV SRC 操作: SRC 为字节时,(AL)←(AX)/(SRC)的商,(AH)←(AX)/(SRC)的余数。 SRC 为字时,(AX)←(DX,AX)/(SRC)的商,(DX)←(DX,AX)/(SRC)的余数。该指令将参与运算的数据默认为无符号数,则商和余数都是无符号数。
- IDIV(signeddivide)有符号数除法 指令格式和操作与无符号数除法相同,用来作有符号数除法。 最终商的符号应是两个操作数符号的异或,而余数的符号和被除数符号一致。
在除法指令里,目的操作数必须是累加器 AX 和 DX,不必在指令中写出。
被除数长度应为除数长度的两倍,余数放在目的操作数的高位,商放在目的操作数的低位。其中 SRC 不能是立即数。
另外,和作乘法时相同,作除法时需考虑是无符号数还是有符号数,从而选择不同的指令。
由于除法指令的字节操作要求被除数为 16 位,字操作要求被除数为 32 位,因此往往需要用符号扩展指令使得被除数长度比除数长度扩大一倍。
需要注意的是,在进行乘法运算时,不会发生溢出问题,但在使用除法指令时,会产生溢出现象。
当除数是字节类型时,除法指令要求商为 8 位。此时如果被除数的高 8 位绝对值≥除数的绝对值,则商会产生溢出。
当除数是字类型时,除法指令要求商为 16 位。此时如果被除数的高 16 位绝对值≥除数的绝对值,则商会产生溢出。
商出现溢出时,系统转 0 号类型中断处理,提示“divide overflow”,并退出程序,返回到操作系统,程序已经崩溃了。
要想避免出现这种情况,必须在作除法前对溢出作出预判。
5.2.6 BCD 码的十进制调整指令
BCD 码是二进制转十进制比较简单的编码,4 位二进制表示 1 位十进制,其转换表如下。
- DAA(Decimal Adjust for Addition)加法的十进制调整指令 格式:DAA 操作:加法指令中,以 AL 为目的操作数,当加法运算结束后,使用本指令可以把 AL 中的和调整为正确的 BCD 码格式。 即:
- 如果 AL 低 4 位 > 9,或 AF=1,则 AL = AL + 6;
- 如果 AL 高 4 位 > 9,或 CF=1,则 AL = AL + 60H, CF = 1。
- DAS(Decimal Adjust for Subtraction)减法的十进制调整指令 格式:DAS 操作:减法指令中,以 AL 为目的操作数,当减法运算结束后,使用本指令可以把差调整为 BCD 码格式。 即:
- 如果 AL 低 4 位 > 9,或 AF = 1,则 AL = AL – 6,AF = 1;
- 如果 AL 高 4 位 > 9,或 CF = 1,则 AL = AL – 60H, CF = 1。
5.3 逻辑与移位指令
5.3.1 逻辑指令
- AND(and)与 格式:AND DST,SRC 操作:(DST)←(DST)∧(SRC)
- OR(or)或 格式:OR DST,SRC 操作:(DST)←(DST)∨(SRC)
- NOT(not)非 格式:NOT OPR 操作:(OPR)←(OPR)
- XOR(exclusive or)异或 格式:XOR DST,SRC 操作:(DST)←(DST)∀(SRC)
- TEST(test)测试 格式:TEST OPR1,OPR2 操作:(OPR1)∧(OPR2) 说明:TEST 指令的两个操作数相与的结果不保存,只根据结果置标志位。
逻辑运算指令只会对部分标志位产生影响,其中 NOT 指令不影响任何标志位,其他指令将使 CF 位和 OF 位为 0,AF 位无定义,其他位则根据运算结果设置。
逻辑指令除了常规的逻辑运算功能外,通常还可以用来对操作数的某些位进行处理,例如屏蔽某些位(将这些位置 O),或使某些位置 1,或测试某些位等。
- SHL(Shift Logical Left)逻辑左移
- SAL(Shift Arithmetic Left)算术左移
- SHR(Shift Logical Right)逻辑右移
- SAR(Shift Arithmetic Right)算术右移
- ROL(Rotat Left)循环左移
- ROR(Rotat Right)循环右移
- RCL(Rotat Left with Carry)带进位循环左移
- RCR(Rotat Right with Carry)带进位循环右移
移位指令均是双操作数指令,指令的格式相同,以 SHL 为例,
则:
格式:
- SHL OPR,1
- SHL OPR,CL,其中 CL 寄存器的值大于 1。
其中 OPR 为寄存器或内存单元,移位次数可以是 1 或 CL 寄存器,如需移位的次数大于 1,则可以在该移位指令前把移位次数先送 CL 寄存器中。
当执行逻辑或算术左移时,操作结果相同,均是最低位补 0,移出的最高位送 CF 标志位;
当执行逻辑右移时,最高位补 0,移出的最低位送 CF 标志位;
当执行算术右移时,OPR 被认为是有符号数,则最高位补符号位自身,移出的最低位送 CF 标志位;
当执行循环左移时,OPR 整体向左移一位,最高位移出,同时送 CF 标志位和最低位;
当执行循环右移时,OPR 整体向右移一位,最低位移出,同时送 CF 标志位和最高位;
当执行带进位循环左移时,OPR 整体向左移一位,此时最高位移出送 CF 标志位,而 CF 标志位原始的数值送 OPR 最低位;
当执行带进位循环右移时,OPR 整体向右移一位,此时最低位移出送 CF 标志位,而 CF 标志位原始的数值送 OPR 最高位。
5.4 串操作(我赌一波,不考)
- MOVS(Move String)串传送
- MOVSB:以字节为单位传送; 操作: (ES:DI)←(DS:SI),DI±1,SI±1
- MOVSW:以字为单位传送; 操作: (ES:DI)←(DS:SI),DI±2,SI±2
- MOVS DST,SRC:将源串 SRC 传送到目的串 DST 中。
MOVS ES:BYTE PTR[DI],DS:[SI]
目的操作数指出了是字节的传送,如果源串不在数据段,也可加前缀,如ES:[SI]
。上述操作中,当方向标志 DF=0 时 SI、DI 用+;DF=1 时 SI、DI 用-。方向标志 DF 的设置有两条指令:- CLD(clear direction flag)设置正向(向前,使 DF=0,SI 或 DI 自动加)
- STD(set direction flag)设置反向(向后,使 DF=1,SI 或 DI 自动减)
- CMPS(Compare String)串比较
- CMPSB(字节) 操作: (ES:DI)-(DS:SI),DI±1,SI±1
- CMPSW(字) 操作: (ES:DI)-(DS:SI),DI±2,SI±2
- CMPS DST,SRC。
- SCAS(Scan String)串扫描
- SCASB(字节) 操作: AL-(ES:DI),DI±1
- SCASW(字) 操作: AX-(ES:DI),DI±2
- SCAS DST。
- STOS(Store in to String)存入串
- STOSB(字节) 操作: (ES:DI)←AL,DI±1
- STOSW(字) 操作: (ES:DI)←AX,DI±2
- STOS DST
- LODS(Load from String)从串取
- LODSB(字节) 操作: AL←(DS:SI),SI±1
- LODSW(字) 操作: AX←(DS:SI),SI±2
- LODS SRC。
串操作指令用以处理内存中的数据串,但该操作每一次执行处理的只是单个字节或字,因此对于数据串来说,需要重复执行串操作指令才能处理完整个串。
串操作指令的重复有特定的前缀指令配合,下面先介绍前缀指令:
- REP(repeat)重复 前缀 REP 的作用是重复执行串操作指令,直到寄存器 CX=0 为止,而每执行一次串操作指令,会使 CX 的内容自动减 1,因此总的重复次数等于 CX 寄存器的初始值。
- REPE/REPZ(repeat while equal/zero)相等/为零则重复 前缀 REPE 也叫 REPZ,只有当 CX 寄存器的值≠0 并且标志位 ZF=1 时,重复执行串操作指令。 若用以比较两个字符串是否相等,每次的串操作指令把源串中的一个字节和目的串中的一个字节进行比较,如果相等(即 ZF=1),则还需继续执行串操作指令,若不相等或者比较全部串的数据(CX=0),则停止。
- REPNE/REPNZ(repeat while not equal/not zero)不相等/不为零则重复 前缀 REPNE 也叫 REPNZ,只有当 CX 寄存器的值≠0 并且标志位 ZF=0 时,重复执行串操作指令。 若在一个字符串中查找是否存在某一个字符,串操作指令把字符串中的一个字节和要找的这个字符进行比较,如果不相等(即 ZF=0),则还需继续执行串操作指令,直到找到(ZF=1)或者查找完整个串的数据(CX=0),才停止。
串处理指令的特性及用法
5.5 程序转移指令
5.5.1 无条件转移指令与程序的可重新定位
JMP(jmp)
:该指令无条件转移到指令指定的地址去执行程序。- 段内直接转移 格式:JMP NEAR PTR OPR 操作:IP←IP+16 位位移量
- 段内间接转移 格式:JMP WORD PTR OPR 操作:IP←(EA) 有效地址 EA 值由 OPR 的寻址方式确定。它可以使用除立即数 方式以外的任何一种寻址方式。
- 段间直接转移 格式:JMP FAR PTR OPR 操作:IP←OPR 的偏移地址 CS←OPR 所在段的段地址
- 段间间接转移 格式:JMP DWORD PTR OPR 操作:IP←(EA) CS←(EA+2)
待续,敢考就敢寄。
第六章 伪指令与源程序格式
6.1 伪指令
6.1.1 处理机选择
随着处理器的升级,增加了新的指令。
处理机选择伪指令有以下几种:
- ·8086 选择 8086 指令系统
- ·286 选择 80286 指令系统
- ·286P 选择保护方式下的 80286 指令系统
- ·386 选择 80386 指令系统
- ·386P 选择保护方式下的 80386 指令系统
- ·486 选择 80486 指令系统
- ·486P 选择保护方式下的 80486 指令系统
- ·586 选择 Pentium 指令系统
- ·586P 选择保护方式下的 Pentium 指令系统
指令中的点
·
是需要的。这类伪指令一般放在代码段中的第一条指令前即可。如不给出,则汇编程序认为其默认选择是 8086 指令系统。6.1.2 段定义伪指令
- 段定义伪指令汇编程序在把源程序转换为目标程序时,只能自动确定标号和变量(代码段和数据段的符号地址)的偏移地址,程序中对于段地址也要作出说明,段地址一旦说明,该段内的指令、标号和变量都属于这个段。段定义伪指令格式: segment_name SEGMENT
segment_name ENDSsegment_name
由用户确定,大写的为关键字。段定义伪指令两句成对出现,两句之间为其他指令。 - 为了确定用户定义的段和哪个段寄存器相关联,用 ASSUME 伪指令 来实现。 ASSUME 伪指令格式: ASSUME register_name: segment_name …,
register_name: segment_nameregister_name
为段寄存器名,必须是 CS,DS,ES 和 SS。而segment_name
则必须是由段定义伪指令定义的段中的段名。ASSUME 伪指令只是指定把某个段分配给哪一个段寄存器,它并不能把段地址装入段寄存器中,所以在代码段中,还必须把段地址装入相应的段寄存器中。还需要用两条 MOV 指令完成这一操作。但是,代码段不需要这样做,代码段的这一操作是在程序初始化时完成的。
6.1.3 程序开始和结束伪指令
源程序结束的伪操作的格式为:
END [label]
汇编程序将在遇到 END 时结束汇编。其中标号 label 指示程序开始执行的起始地址。
如果是多个程序模块相连接,则只有主程序需要使 用标号,其他子程序模块则只用 END 而不能指定标号。
6.1.4 数据定义与存储器单元分配伪指令
伪指令的一般格式是:
[变量] 操作码 N个操作数 [; 注释]
操作码字段说明所用伪操作的助记符,即伪操作,说明所定义的数据类型。常用的有以下几种。
- DB:伪操作用来定义字节,其后的每个操作数都占有一个字节(8 位)。
- DW:伪操作用来定义字,其后的每个操作数占有一个字(16 位,其低位字节在第一个字节地址中,高位字节在第二个字节地址中,即数据低位在低地址,数据高位在高地址)。
- DD:伪操作用来定义双字,其后的每个操作数占有两个字(32 位)。
- DF:伪操作用来定义 6 个字节的字,其后的每个操作数占有 48 位。
- DQ:伪操作用来定义 4 个字,其后的每个操作数占有 4 个字(64 位),可用来存放双精度浮点数。
- DT:伪操作用来定义 10 个字节,其后的每个操作数占有 10 个字节,为压缩的 BCD 码。
这些伪操作可以把其后跟着的数据存入指定的存储单元,形成初始化数据;或者只分配存储空间而并不确定数值。
6.1.5 类型属性操作符
- WORD PTR ; 字类型
- BYTE PTR ; 字节类型
就是按照某种类型读取,但不会改变变量本身的类型。
对一个 8 位(或 16 位)的变量可以用 16 位(或 8 位)方式访问。
6.1.6 THIS 操作符和 LABEL 伪操作
通过 THIS 操作符或 LABEL 伪操作,将变量定义为不同的访问类型。
- 使用 THIS 操作符: 格式:THIS type
- 使用 LABEL 伪操作: 格式:name LABEL type
type
在这里是BYTE
或者WORD
。6.1.7 表达式赋值伪指令“EQU”和“=”
可以用赋值伪操作给表达式赋予一个常量或名字。其格式如下:
Expression_name EQU Expression
Expression_name=Expression后略
第七章 分支与循环程序设计
略
18 级试卷
-
Android 复习
第一章 Android 基础入门
Android 是基于 Linux 的开源操作系统,最初由 Andy Rubin 开放。
1.1 简介
1.1.1 通信技术
1G 很容易被窃听
2,3,4,5G 技术的本质区别是传输速度不同。
2019 年,5G 商用元年。
1.1.3 体系结构
- 应用程序层应用程序的集合。
- 应用程序框架层提供 API。
- 核心类库系统库和 Android 运行时库(包含了 Dalvik 虚拟机,使得每个 Android 应用程序都能运行在独立的进程中)。
- Linux 内核层底层驱动的集合,如显示驱动,音频驱动,照相机驱动等。
1.1.4 Dalvik 虚拟机
基于寄存器架构。执行特有的
dex
文件实现其功能。功能:
- 对象生命周期管理
- 堆栈管理
- 线程管理
- 安全异常管理
- 垃圾回收
虚拟机编译文件的过程(P4):
ART 模式
启用后,安装 APP 时预编译,并将代码转换成机器语言存储在本地。
提高执行效率。
1.4 Android 程序结构
四大组件在 src/main/AndroidManifest.xml 里注册,这是整个程序的配置文件。 四大组件:
- Activity 活动
- Service 服务
- BroadCastReceive 广播接收器
- ContentProvider 内容提供商
接下来介绍程序工程的结构:
- app 代码和资源
- libs 第三方 jar 包
- src 代码和资源的主目录
- androidTest 调试代码
- main
- java 程序代码
- res 程序资源
- AndroidManifest.xml 整个程序的配置文件,在其中可以配置程序所需的权限和注册程序用到的四大组件。注意这里是申请网络权限和存储权限的地方,不申请就用不了,而且似乎不会报错。
- build.gradle app 构建脚本,包含编译的 SDK 版本,Tools 版本,支持的最低 SDK 版本,支持的目标 SDK 版本。
- build.gradle 安卓程序的构建脚本
- local.properties 指定程序的 SDK 路径。可以通过 sdk.dir 的值指定,一般不需要修改
- settings.gradle 配置子项目
1.5 资源管理与使用
res 目录中。
1.6 调试
输出信息使用 logcat。 v:verbose 详细 d:debug i:info w:warning e:error a:assert 断言 vdiwea
习题
一、填空题
- Dalvik 中的 Dx 工具会把部分 class 文件转换成(dex)文件。
- 如果希望在 XML 布局文件中调用颜色资源,可以使用(@color)调用。
- Android 程序入口的 Activity 是在(AndroidMainifest.xml)文件中注册的。
- Android 中 查看应用程序日志的工具是(LogCat)。
二、判断题
- Dalvik 是 Google 公司设计的用于 Android 平台的虚拟机。 √
- Android 应用程序的主要语言是 Java。√
- Android 系统采用分层架构,分别是应用程序层、应用程序框架层、核心类库和 Linux 内核。√
- 第三代移动通信技术 (3G) 包括 TD-LTE 和 FDD-LTE 两种制式。× 应是 4G。
- Android 程序中,Log.e() 用于输出警告级别的日志信息。× e-error
- 每个 Dalvik 虚拟机实例都是一个独立的进程空间,并且每个进程之间不可以通信。× 可以通信。
三、选择题
- Dalvik 虚拟机是基于(C)的架构。 A. 栈 B. 堆 C. 寄存器 D. 存储器
- Android 项目中的主题和样式资源,通常放在 (C) 目录。 A. res/drawable B. res/layout C. res/values D. assets
- 下列关于 AndroidManifest.xml 文件的说法中,错误的是 (D)。 A. 它是整个程序的配置文件 B. 可以在该文件中配置程序所需的权限 C. 可以在该文件中注册程序用到的组件 D. 该文件可以设置 UI 布局
- Dalvik 虚拟机属于 Android 系统架构中的(C) A. 应用程序层 B. 应用程序框架层 C. 核心类库层 D. Linux 内核层
- Android 中 短信、联系人管理、浏览器等属于 Android 系统架构中的(A) A. 应用程序层 B. 应用程序框架层 C. 核心类库层 D. Linux 内核层
四、简答题
简述 Android 体系结构的层次和特点
见 1.1.3。书 P3。
第二章 Android 常见界面布局
2.1 View 控件
每个界面必须有且只有一个 ViewGroup 容器。
2.2 界面布局编写方式
2.2.1 XML 布局文件 中编写布局
布局文件放在
res/layout
,用标签定义布局,例如<RelativeLayout></RelativeLayout>。
2.2.2 Java 代码中编写布局
P32
2.3 布局通用属性(P32)
android:id
// 标识
android:layout_width
// 布局的宽度
android:layout_height
// 布局的高度
android:background
// 布局背景
android:layout_margin
// 边界距离,外距
android:padding
// 内距2.4 线性布局
<LinearLayout></LinearLayout>
- 排列方式:水平或者竖直排列。 android:orientation=”vertical”
// 控件自上到下
android:orientation=”horizontal”
// 控件从左到右 - 权重 android:layout_weight=”1″
// 设置权重
2.5 相对布局
<RelativeLayout></RelativeLayout>
相对定位指定子控件的位置。
2.6 表格布局
行列方式的布局。
<TableLayout>
<TableRow>
</TableRow>
</TableLayout>2.7 帧布局
<FrameLayout></FrameLayout>
习题
一、填空题
- Android 的常见布局都直接或者间接的继承自(ViewGroup)类。
- Android 中 的 TableLayout 继承自(LinearLayout)。
- 表格布局 TableLayout 通过(TableRow)布局控制表格的行数。
- (RelativeLayout)布局通过相对定位的方式指定子控件的位置。
- 在 R.java 文件中,android:id 属性会自动生成对应的(int)类型的值。
二、判断题
- ViewGroup 是盛放界面控件的容器。√
- 如果在帧布局 FrameLayout 中放入三个所有属性都相同的按钮,那么能够在屏幕上显示的是第 1 个被添加的按钮。× 第三个,因为会被覆盖。
- Android 中的布局文件通常放在 res/layout 文件夹中。√
- TableLayout 继承自 LinearLayout, 因此它完全支持 LinearLayout 所支持的属性。√
- LinearLayout 布局中的 android:layout_weight 属性用于设置布局内控件所占的权重。√
三、选择题
- 下列属性中,用于设置线性布局方向的是(A)。 A. orientation B. gravity C. layout gravity D. padding
- 下列选项中,不属于 Android 布局的是(C)。 A. FrameLayout B. LinearLayout C. Button D. RelativeLayout
- 帧布局 FrameLayout 是将其中的组件放在自己的(A)。 A. 左上角 B. 右上角 C. 左下角 D. 右下角
- 对于 XML 布局文件,android:layout_width 属性的值不可以是(D)。 A. match_parent B. fill_parent C. wrap_content D. match_content
- 下列关于 RelativeLayout 的描述,正确的是(C)。 A. RelativeLayout 表示绝对布局,可以自定义控件的 x、y 的位置 B. RelativeLayout 表示帧布局,可以实现标签切换的功能 C. RelativeLayout 表示相对布局,其中控件的位置都是相对位置 D. RelativeLayout 表示表格布局,需要配合 TableRow 一起使用
第三章 Android 常见界面控件
3.1 简单控件
- Textview 文本框
- EditText 文本输入框
- Button 按钮
- ImageView 图片
- RadioButton 单选按钮(圆按钮)
- CheckBox 多选按钮(方按钮)
- Toast 类 轻量级信息提醒机制。
3.2 列表控件
ListView 是类似微信主界面和淘宝搜索界面的列表。
这种列表需要配合数据适配器 Adapter,常见的 Adapter 如下
- BaseAdapter
- getCount 获取 Item 个数
- getItem(int position) 获取对应位置的 Item 对象
- getItemId(int position) 获取 id
- getView(int position,View convertView,ViewGroup Parent) 获取视图
- SimpleAdapter
- ArrayAdapter
注意学习一下 Adapter 怎么定义。
习题
一、判断题
- Android 的控件样式,每一个 XML 属性都对应一个 Java 方法。√
- 当指定 RadioButton 按钮的 android:checked 属性为 true 时,表示未选中状态。× 当然是选中了。
- AlertDialog 对话框能够直接通过 new 关键字创建对象。× 要通过 Builder 对象。
- Toast 是 Android 系统提供的轻量级信息提醒机制,用于向用户提示即时消息。√
- ListView 列表中的数据是通过 Adapter 加载的。√
二、选择题
- 在 XML 布局中定义了一个 Button, 决定 Button 按钮上显示文字的属性是(B) A. android:value B. android:text C. android:id D. android:textvalue
- 下列选项中,(C)用于设置 TextView 中文字显示的大小。 A. android:textSize=”18″ B. android:size=”18″ C. android:textSize=”18sp” D. android:size=”18sp”
- 使用 EditText 控件时,当文本内容为空时,如果想做一些提示,那么可以使用的属性是(D)。 A. android:text B. android:background C. android:inputType D. android:hint
- 为了让一个 imageView 显示一张图片,可以通过设置的属性是(A)。 A. android:src B. android:background C. android: img D. android:value
- 下列关于 ListView 的说法中,正确的是(C) A. ListView 的条目不能设置点击事件 B. ListView 不设 置 Adapter 也能显示数据内容 C. 当教据超出能显示范围时,ListView 自动具有可滚动的特性 D. 若 ListView 当前能显示 10 条,一共有 100 条数据,则产生了 100 个 View
- CheckBox 被选择的监听事件通常使用(B)方法。 A. setOnClickListener B. setOnCheckedChangeListener C. setOnMenuItemSelectedListener D. setOnCheckedListener
- 当使用 EditText 控件时,能够使文本框设置为多行显示的属性是(A)。 A. android:lines B. android:layout_height C. android:textcolor D. android:textsize
- 下列关于 AlertDialog 的描述,错误的是(A) A. 使用 new 关键字创建 AlertDialog 的实例 B. 对话框的显示需要调用 show() 方法 C. setPositiveButton() 方法是 用来设置确定按钮的 D. setNegativeButton() 方法是用来设置取消按钮的
第四章 程序活动单元 Activity
4.1 Activity 生命周期
5 种生命周期的状态:
- 启动状态 十分短暂
- 运行状态 可见的、有焦点的。运行状态的 Activity 具有高优先级,即使内存不足,系统也会先销毁栈底的 Activity 来确保当前的 Activity 正常运行。
- 暂停状态 可见,但无焦点不可操作,例如主界面出现一个弹窗,则主界面就是暂停状态。
- 停止状态 完全不可见就是停止状态。
- 销毁状态 会被清理出内存。
生命周期方法:
- onCreate() 创建界面的时候初始化的函数,一开始就会调用,可以重写这个方法来做一些初始化工作,例如给控件写数据等
- onStart() 即将可见时调用
- onResume() 获取焦点时调用
- onPause() 暂停,被遮挡时调用
- onStop() 不可见时调用
- onRestart() 停止状态启动
- onDestory() 销毁时调用
4.2 Activity 创建、配置、启动、关闭
创建
编译器左上角 new 就行。
配置
编写类继承 ( extend ) activity 类
启动
public void startActivity (Intent intent)
关闭
public void finish()
4.3 Intent 意图和 IntentFilter 匹配
显式和隐式 Intent
- 显式 intent Intent intent = new Intent(this, SecondActivity.class);
startActivity(intent);那么就用 intent 的属性来确定需要打开的 Activity 了。 - 隐式 intent 主要有三个属性值:
- action 要完成的动作
- data 传递的数据
- category action 属性的额外信息
IntentFilter
隐式 Intent 会使得系统将其和每一个组件的过滤器相匹配,三个属性值都匹配成功,则唤起相应的组件。
匹配规则:
- action 清单文件中设置 一些 action 属性(
<intent-filter></intent-filter>
),只要 action 匹配上了一条就算匹配上。 - data 清单文件中设置 一些 data 属性(
<intent-filter></intent-filter>
),只要 data 匹配上了一条就算匹配上。 - category 清单文件中设置 一些 category 属性(
<intent-filter></intent-filter>
),隐式的 intent 里的 category 必须包含于(小于等于)清单文件里的 category 属性,否则匹配不上。因此 intent 的 category 如果是空则和所有的 IntentFilter 匹配上
4.4 Activity 之间的跳转
4.4.1 数据传递
- 用 Intent 来做数据传递。 putExtra(String name, …);
// 加入一个属性,第一个参数是属性名,第二个是属性值第二个参数的类型是变化的,因为这个函数有很多的重载。但应该都是基本数据类型。不能传递一个对象的引用。另一个 Activity 取出这个属性这样操作。 getIntent().getStringExtra(String name)
// 如果这个属性的值是 int,就把 String 改成 Int。Boolean→Boolean - 用 Bundle 类传递数据 例如 Intent intent = new Intent() ;
Bundle bundle = new Bundle() ;
bundle.putString(“account”, “小米”);
intent.putExtras(bundle);
startActivity(intent);另一个 Activity 取出 Bundle 这样操作。 getIntent() .getExtras() .getString(“account”);
4.4.2 数据回传
三个方法:
- startActivityForResult( Intent intent, int requestCode); 第一个参数是意图对象,第二个是请求码,标识请求来源。这个函数用来开启一个 Activity,当开启的 Activity 被销毁时会返回数据。
- setResult( int resultCode, Intent intent); 用于携带数据回传。
- onActivityResult( int requestCode, int resultCode, Intent data); 用于接收回传的数据。
startActivityForResult( Intent intent, int requestCode); 用于在 MainActivity 开启 SecondActivity。
setResult( int resultCode, Intent intent); 用于 SecondActivity 设置 Result,自身销毁后 Result 会返回到 MainActivity。
onActivityResult( int requestCode, int resultCode, Intent data); 用于对回传的 Result 解析,是在 SecondActivity 销毁后自动调用的。需要重写。
4.5 任务栈和启动模式
任务栈
任务栈是存放 Activity 实例的容器,销毁就弹出栈,用户操作的永远是栈顶。
启动模式
- standard 默认的启动模式,启动一个就创建一个新的实例。
- singleTop 如果启动的是栈顶的任务则复用,而不是创建新的实例。
- singleTask 如果启动的是栈内的任务则复用,而不是创建新的实例。
- singleInstance 启动新的栈来管理新的实例,不管哪个栈调用这个 Activity,整个系统里只有这一个 Activity,例如安卓的系统桌面。
4.6 Fragment (P95)
嵌入 Activity 的 UI 片段。
4.6.2 生命周期
受其所属的 Activity 影响,也和 Activity 类似,有
启动、运行、暂停、停止、销毁
五种状态,但 Activity 暂停,Fragment 也暂停,停止、销毁也是跟随 Activity 的。在 Activity 运行的时候,可以单独对每个 Fragment 操作,如添加(启动状态)删除(销毁状态)等。
习题
一、填空题
- Activity 的启动模式包括 standard, singleTop,singleTask 和(singleintance)。
- 启动一个新的 Activity 并且获取这个 Activity 的返回教据,需要重写(startActivityForResult() )方法。
- 发送隐式 Intent 后,Android 系统会使用(IntentFilter)匹配相应的组件。
- 在清单文件中为 Activity 添加
<intent-filter>
标签时,必须添加的属性名为(action)否则隐式 Intent 无法开启该 Activity. - Activity 的(finish() )方法用于关闭当前的 Activity。
二、判断题
- 如果 Activity 不设置启动模式,则默认为 standard.√
- Fragment 与 Activity 的生命周期方法是一致的。× 方法当然不一样 P80 和 P95
- 如果想要关闭当前的 Activity, 可以调用 Activity 提供的 finish() 方法。√
<intent-filter>
标签中间只能包含一个 action 属性。× 多个- 默认情况下,Activity 的启动方式是 standard。√
三、选择题
- 下列选项中,不属于 Android 四大组件的是(C) A. Service B. Activity C. Handler D. ContentProvider
- 下列关于 Android 中 Activity 管理方式的描述中,正确的是(B) A. Android 以堆的形式管理 Activity B. Android 以栈的形式管理 Activity C. Android 以树的形式管理 Activity D. Android 以链表的形式管理 Activity
- 下列选项中,(B)不是 Activity 生命周期方法。 A. onCreate() B. startActivity() C. onStart() D. onResume()
- 下列方法中,(A)是启动 Activity 的方法。 A. startActivity() B. goToActivity() C. startActivityResult() D. 以上都是
- 下列关于 Intent 的描述中,正确的是(B) A. Intent 不能够实现应用程序间的数据共享 B. Intent 可以实现界面的切换,还可以在不同组件间直接进行数据传递 C. 使用显式 Intent 可以不指定要跳转的目标组件 D. 隐式 Intent 不会明确指出需要激活的目标组件,所以无法实现组件之间的数据跳转
第五章 数据存储
5 种存储方式:
- 文件存储 openFileInput() openFileOutput() 类似 Java。
- SharedPreferences 存储 存储一些简单的配置信息,采用 xml 格式。
- SQLite 数据库存储 支持基本 SQL 语法,一般使用其作为复杂数据的存储引擎。
- ContentProvider 四大组件之一,用于应用程序间的数据交换。可以将自己的数据共享给其它的应用程序使用。
- 网络存储 存储在网络服务器上。
5.2 文件存储(P108)
5.2.1 写入
内部存储
内部存储是存在应用程序中的,文件会被该应用程序私有化,其它应用程序需要申请权限才可以操作。卸载应用程序时,这些文件也会删除。
FileOutputStream fos = openFileOutput( String fileName, int mode);
String word = "Hello World!";
fos.write(word.getBytes());
fos.close();写入(
FileOutputStream
)有四种读写模式,以枚举的方式定义,填入第二个参数。- MODE_PRIVATE只能被当前文件读写。
- MODE_APPEND可以追加。
- MODE_WORLD_READABLE该文件可以被其它程序读。
- MODE_WORLD_WRITEABLE可以被其它程序写。
外部存储
外部存储是存入外部的设备(SD卡或内嵌的存储卡)的。永久性的存储方式。可以被其它应用程序共享,可能会被浏览、修改、删除,是不安全的存储方式。
需要使用
Environment.getExternalStorageState()
方法确认外部设备是否可用。String state = Environment.getExternalStorageState();
// 获取外存状态
if(state.equals(Environment.MEDIA_MOUNTED)) {
//状态判断
File SDPath = Environment.getExternalStorageDirectory();
// 获取 SD 卡路径
File file = new File( SDPath, "data.txt");
// 创建文件对象
FileOutputStream fos = new FileOutputStream(file);
// 打开文件流
fos.write( data.getBytes());
// 写入
fos.close();
// 关闭流
}5.2.2 读取
内部存储
String content = "";
// 创建 存储的字符串
FileInputStream fis = openFileInput( "data.txt");
// 打开文件输入流
byte[] buffer = new byte[fis.available()];
// 创建缓冲区
fis.read(buffer);
// 读入缓冲区
content = new String( buffer);
// 读入字符串
fis.close();
//关闭流外部存储
String state = Environment.getExternalStorageState();
// 获取外存状态
if(state.equals(Environment.MEDIA_MOUNTED)) {
//状态判断
File SDPath = Environment.getExternalStorageDirectory();
// 获取 SD 卡路径
File file = new File( SDPath, "data.txt");
// 创建对象文件
FileInputStream fis = new FileInputStream(file);
// 打开流
BufferedReader br = new BufferedReader( new InputStreamReader( fis));
// 创建字符输入缓冲流对象
String data = br.readLine();
// 读取数据
br.close();
fis.close();
}5.3 SharedPreferences 存储(P115)
持久化存储。存一些配置,类似账号密码。采用 xml 格式。
5.4 SQLite 数据库存储(P118)
可以存储大量数据。
习题
一、判断题
- SQLite 是 Android 自带的一个轻量级的数据库,支持基本 SQL 语法。√
- Android 中的文件存储方式,分为内部存储方式和外部存储方式。√
- 使用 openFileOutput() 方式打开应用程序的输出流时,只需指定文件名。× 还有读写模式。这是内部存储的输出流,外存用
new FileOutputStream(file)
。Input 只要文件名(从文件里面读到程序里面当然不用什么追加什么的) - 当 Android SDK 版本低于 23 时,应用程序想要操作 SD 卡数据,必须在清单文件中添加权限。√
- SQLiteDatabase 类的 update() 方法用于删除数据库表中的数据。× update 当然是更新
- SQLite 数据库的事 务操作满足原子性、一致性、隔离性和持续性。√
二、选择题
- 下列关于 SharedPreferences 存取文件的描述中,错误的是(C)。 A. 属于移动存储解决方式 B. SharedPreferences 处理的就是 key-value 对 C. 读取 xml 的路径是 /sdcard/shared_prefs D. 文本的保存格式是 xml
- 下列选项中,不属于 getSharedPreferences 方法的文件操作模式参数是(B)。 A. Context.MODE_PRIVATE B. Context.MODE_PUBLIC C. Context.MODE_WORLD_ READABLE D. Context.MODE_WORLD_WRITEABLE
- 下列方法中,(B)方法是 shardPreferences 获取其编辑器的方法。 A. getEdit() B. edit() C. setEdit() D. getAll()
- Android 对教据库的表进行查询操作时,会使用 SQLietDatabase 中的(C)方法。 A. insert() B. execSQL() C. query() D. updata()
- 下列关于 SQLite 数据库的描述中,错误的是(C) A. SqliteOpenHelper 有创建数据库和更新数据库版本的的功能 B. SqliteDatabase 类是用来操作数据库的 C. 每次调用 SqliteDatabase 的 getWritableDatabase 方法时,都会执行 SqliteOpenHelper 的 onCreate() 方法 D. 当数据库版本发生变化时,会调用 SqliteOpenHelper 的 onUpgrade() 方法更新数据库
- 下列初始化 SharedPreferences 的内代码中,正确的是(D) A. SharedPreferences sp = new SharedPreferences(); B. SharedPreferences sp = SharedPreferences.getDefault(); C. SharedPreferences sp = SharedPreferences.Factory(); D. SharedPreferences sp = getSharedPreferences();
第六章 内容提供者和内容观察者
6.1 内容提供者
A 程序通过 ContentProvider 来暴露数据,B 程序通过 ContentResolve 操作 A 程序暴露的数据。A 程序会将操作结果返回给 ContentResolver,然后 ContentResolver 再将操作结果返回给 B。
两个重要的部分
- 数据模型 使用基于数据库模型的 简单表格,其中每个数据的唯一标识是
_ID
,数据类型是 int。查询字段数据用 getInt() /getString() /getLong() 等 - Uri ContentResolver 的增删查改方法以 Uri 的形式对外提供数据。 Uri 的三部分:
- scheme
content://
表示操作的数据被 ContentProvider 控制,不会被修改。 - authority 表示 ContentProvider 设置的唯一标识。主要用来区分程序,其一般是表示程序包名。
- path 表示资源或数据。可以动态修改。
content://
cn.itcast.mycontentprovider/
person
。 - scheme
6.4 内容观察者
实时监听 ContentProvider 共享的数据是否变化。
观察指定的 Uri 代表的数据的变化。
变化时触发 ContentObserver 的 onChange() 方法。
详细解释 P138
习题
一、判断题
- Uri 主要由三部分组成,分别是 scheme,authority 和 path。√
- 内容观察 ContentObserver 用于观察指定 URI 代表的数据的变化。√
- 内容提供者主要功能是实现跨程序共享数据的功能。√
- Android 中通过内容解析者查询短信数据库的内容时不需要加入读短信的权限。× 危险权限一共九个:
- 位置
- 日历
- 照相机
- 联系人
- 存储卡
- 传感器
- 麦克风
- 电话
- 短信 这九个需要动态申请。也就是说不仅需要在清单中注册,还需要在代码里面申请!
- Android 系统的 UriMatcher 类用于匹配 Uri。√
二、选择题
- 如果一个应用程序想要访问另外一个应用程序的数据库,那么需要通过(C)实现。 A. BroadcastReceiver B. Activity C. ContentProvider D. AIDL
- 下列方法中,(B)能够得到 ContentResolver 的实例对象。 A. new ContentResolver() B. getContentResolver() C. newInstance() D. ContentUris.newInstance()
- 自定义内容观察者时,需要继承的类是(B)。 A. BaseObserver B. ContentObserver C. BasicObserver D. DefalutObserver
- 对查询手机系统短信时,内容提供者对应的 Uri 为(C)。 A. Contacts.Photos.CONTENT_URI B. Contacts.People.CONTENT_URI C. content://sms/ D. Media.EXTERNAL_CONTENT_URI
- 下列关于 ContentProvider 的描述,错误的是(D)。 A. ContentProvider 是一个抽象类,只有继承后才能使用 B. ContentProvider 只有在 AndroidManifest.xml 文件中注册后才能运行 C. ContentProvider 为其他应用程序提供了统一的访问数据库的方式 D. 以上说法都不对
第七章 广播机制
7.1 广播机制的概述
广播机制:每个程序可以根据自己的兴趣来注册广播,广播可能是系统发送的也可能是其他应用发送的。
消息订阅者(广播订阅者)注册广播接收者(Binder 机制),广播发送者发送广播(Binder 机制),处理中心接收,根据消息发送者的要求,在已注册列表内寻找合适的消息订阅者,寻找依据是(IntentFilter/Permission)。然后由处理中心发送广播到消息订阅者。
广播接收者默认重写类的构造函数和 onReceive() 方法。
Android 8.0 后,必须用动态注册才可以接收广播,静态接收无效。
- 无序广播 异步执行,所有监听这个广播的广播接收者都会收到,但接收/执行顺序不确定。效率高,但无法拦截。
- 有序广播 按照广播接收者声明的优先级别依次接收,只有一个广播接收者能接收到。在这个广播接收者中的逻辑执行完,再继续传递。效率较低,有接收先后顺序,可以被拦截(终止)。优先级相同,先注册的先接收。
setPriority(int priority)
函数,参数值越大,优先级越高。 - 指定广播 有序广播的一种,保证某个接收者一定会接收到这个广播。
习题
一、填空题
- (BroadcastReceiver)用来监听来自系统或者应用程序的广播。
- 广播接收者的注册方式有两种,分别是(动态注册)和(静态注册)。
二、判断题
- Broadcast 表示广播,它是一种运用在应用程序之间传递消息的机制。√
- 在清单文件注册广播接收者时,可在
<intent-filer>
标签中使用 priority 属性设置优先级别,属性值越大优先级越高。√ - 有序广播的广播效率比无序广播更高。× 当然是无序的高,因为有序的需要按顺序接收和执行。
- 动态注册的广播接收者的生命周期依赖于注册广播的组件。√
- Android 中广播接收者必须在清单文件里面注册。× 动态注册不需要在清单里注册。但注意危险权限注册,既要在清单注册也要代码里面动态注册。
三、选择题
- 关于广播类型的说法,错误的是(BC)。(多选) A. Android 中的广播类型分有序广 播和无序广播 B. 无序广播是按照一定的优先级进行接收 C. 无序广播可以被拦截,可以被修改数据 D. 有序广播按照一定的优先级进行发送
- 广播作为 Android 组件间的通信方式,使用的场景有(ABCD)。(多选) A. 在同一个 APP 内部的同一组件内进行消息通信 B. 不同 APP 的组件之间进行消息通信 C. 在同一个 APP 内部的不同组件之间进行消息通信(单个进程) D. 在同一个 APP 具有多个进程的不同组件之间进行消息通信
第八章 服务
8.1 服务概述
四大组件之一,关于四大组件见 1.4。
在后台长时间执行操作并且不提供用户界面的应用程序。
一般由 Activity 启动,但不依赖于 Activity,有自己的生命周期。
应用场景:
- 后台运行 后台长时间运行而不提供界面,系统必须回收内存资源的时候,会被销毁,否则一直在后台运行。
- 跨应用访问 服务被启动时,即使用户切换到其它应用程序,服务也会后台继续运行
例如多媒体后台播放是服务,后台记录地理位置也是服务。
注意,服务并不是运行在子线程中,而是在主线程中,只是没有界面而已。它的耗时操作会开启子线程来处理,否则会出现 ANR(程序无响应)异常。
8.3 服务的生命周期
与启动方式有关,有两种启动方式。
startService()
方法(其他组件调用stopService()
停止,服务自身调用stopSelf()
) 依次调用onCreate()
onStartCommand()
onDestory()
在后台长时间运行,启动服务的组件与服务之间没有关联。即使组件被销毁,服务也会继续运行。bindService()
方法(unbindService()
解绑) 依次调用onCreate()
onBind()
onUnbind()
onDestory()
服务与组件绑定,允许组件与服务交互,组件退出/调用unbindService()
方法,服务就会销毁(当所有组件都解绑的时候,这个服务就会被销毁)。 多个组件可以绑定一个服务。第一个组件绑定是会回调onCreate()
生命周期方法,后续的绑定只会调用onBind()
方法,返回 IBinder 给客户端。
8.5 服务的通信
通过调用
bindService()
方法开启服务后,服务与绑定服务的组件之间可以通信,通过组件可以控制服务操作服务。通信可以先用
startService()
来开启服务,再bind
绑定组件来通信,也可以直接以bindService()
来开启服务。服务通信方式有两种
- 本地服务通信 应用程序内部通信。
Service 类
的onBind()
方法会返回一个IBinder 对象
,传递给ServiceConnection 类
的onServiceConnected( ComponentName Name, IBinder service)
方法,然后绑定服务的组件就通过IBinder 对象
与Service
通信。 - 远程服务通信 应用程序之间的通信。通过
AIDL(一种接口定义语言)
实现。由于每个应用程序都有自己的进程,因此远程服务通信,是不同进程之间的通信。注意进程与线程的区别,线程是更小的概念,前面提到后台服务在主线程内,耗时操作在子线程内。而主线程和子线程都在进程内。
习题
一、填空题
- 如果想要停止
bindService()
方法启动的服务,需要调用(onUnbind()
)方法。 - Android 系统的服务的通信方式分为(本地通信)和(远程通信)。
- 远程服务通过(AIDL)实现服务的通信。
二、判断题
- Service 服务是运行在子线程中的。× 是主线程,但耗时操作放到子线程,否则出现程序无响应。
- 不管使用哪种方式启动 Service, 它的生命周期都是一样的。× 生命周期的方法都不一样,生命周期也不一样
- 使用服务的通信方式进行通信时,必须保证服务是以绑定的方式开启的,否则无法通信。√
- 一个组件只能绑定一个服务。× 多个组件可以绑定一个服务,一个组件可以绑定多个服务(据说)
- 远程服务和本地服务都运行在同一个进程中。× 远程服务是不同进程之间的数据通信,因此是多进程。
三、选择题
- 如果通过 bindService 方式开启服务,那么服务的生命周期是(C)。 A. onCreate() →onstart() →onBind() →onDestroy() B. onCreate() →onBind() →onDestroy() C. onCreate() →onBind() →onUnBind() →onDestroy() D. onCreate() →onStart() →onBind() →onUnBind() →onDestroy()
- 下列关于 Service 服务的描述中,错误的是(D) A. Service 是没有用户可见的界面,不能与用户交互 B. Service 可以通过 Context.startService() 来启动 C. Service 可以通过 Context.bindService() 来启动 D. Service 无须在清单文件中进行配置
- 下列关于 Service 的方法描述,错误的是(D)。 A. onCreate() 表示第一次创建服务时执行的方法 B. 调用 startService() 方法启动服务时执行的方法是 onStartCommand() C. 调用 bindService() 方法启动服务时执行的方法是 onBind() D. 调用 startService() 方法断开服务绑定时执行的方法是 onUnbind()
-
Device Association Service占用CPU 蓝牙导致卡顿 更换网卡后游戏卡顿 Win10/Win11打不开5G热点 蓝牙设备删除失败
最近碰到一系列的问题,在此做个总结。
首先是Win10/Win11打不开5GHz频段热点。显示 所选频段不可用。这是政策原因,导致Intel网卡不能打开热点,不清楚是驱动层面还是系统层面的设置。
解决方法是,让电脑先连接5GHz的热点,例如手机开热点,电脑连接。如果手机恰好开到了高信道,那电脑就可以开5G热点。
这样太麻烦了,我选择抛弃Intel网卡。
对了,网上会说部分Intel型号网卡受限,这个,本人不可能测试全部的网卡,反正在自己的电脑上测试了AX210,这是现在最新款Intel通用网卡(其它可能有主板限制),仍然不能打开。一劳永逸的办法半糖原创。
然后我换了个AMD/联发科网卡,MT7922/RZ616,花了99大洋。
这个网卡在本地机器上是不免驱的,需要下载专门的驱动(不确定大家的机器是否免驱,因为官方淘宝店说是免驱的)。
驱动下载
这是驱动下载的网址。
实测这个网卡能开5GHz热点。据说中国只有Intel受限。半糖原创。然后发现新的问题,系统的Device Association Service占用很大,我这边游戏本,基本没开什么东西,居然让这个东西拉满了CPU,甚至半个小时不带停的。我甚至怀疑是中毒了。
不是中毒,搜索一番后发现,这个进程占用大,可能是系统在自动清理缓存,也可能是其它问题。
它的作用是和新的有线/无线设备连接。半糖原创。
然后发现是蓝牙问题,但除了关闭蓝牙别无办法。关闭蓝牙以后我的电脑风扇几乎就静音了,打开就呼呼转。
这边顺便提一句,换网卡,网卡上除了无线网络设备,还有蓝牙设备。所以是WLAN和蓝牙一起换了。
其实拉满了CPU也就略卡,还能忍忍。
最后发现问题,因为我的蓝牙设备删除失败。
删除失败的解决方法:进入设备管理器,手动卸载相关的蓝牙设备。
而解决卡顿和Device Association Service服务占用的方法也是这样。半糖原创。在设备管理器里面点菜单栏的
查看
–显示隐藏的设备
,然后把蓝牙里面半透明的设备全部卸载,就可以解决卡顿的问题。其原因是旧的驱动程序干扰了新的驱动程序。导致功能异常。
最后我还是推荐换RZ616网卡的,体验上还行,对比Intel,似乎感知不强,没有所谓的AMD不如Intel的感知。也支持5GHz或者6GHz。半糖原创。这边一次把所有的问题都写一起了,没有分开写文章,是因为,这几个问题刚好串联在一起了。因此一次性写完。半糖原创。
-
数据库复习
第一章 绪论
1.1 数据库系统概述
1.1.1 四个概念
- 数据(Data)描述事物的符号记录 是 数据。数据含义 是 数据语义。
- 数据库(DB,DataBase)长期储存在计算机内,有组织的,可共享的大量数据的集合。特点:
- 按一定的数据模型组织、描述和储存。
- 较小冗余度。
- 较高数据独立性和易扩展性。
- 可共享。
- 数据库管理系统(DBMS,DataBase Management System)用户与操作系统间的一层数据管理软件,是计算机的基础软件。功能:
- 数据定义
- 数据组织、存储、管理
- 数据操纵
- 数据库事务管理和运行管理
- 数据库建立和维护
- 数据库系统存储、管理、处理和维护数据的系统。
1.1.2 历程
- 人工管理阶段
- 文件系统阶段
- 数据库系统阶段
1.1.3 数据库系统的特点
- 数据结构化
- 数据共享性高、冗余度低、易扩充
- 数据独立性高
- 数据 由 数据管理系统 统一管理控制
1.2 数据模型
对数据特征的抽象。
是数据库系统的核心和基础
根据模型应用目的可以将模型分为两大类:
- 概念模型
- 逻辑模型和物理模型
1.2.1 概念模型
基本概念:
- 实体客观存在并可相互区别的事物称为 实体 。一条数据本身是一个实体。
- 属性实体具有的某一特性称为 属性。
- 码唯一标识实体的属性集称为 码。
- 实体型实体名和属性名集合来抽象刻画实体,称为 实体型。如 学生(姓名,学号,性别,出生日期)是一个实体型。
- 实体集同一类型的实体集合。
- 联系不同实体集之间的联系。有一对一,一对多,多对多。
E-R 方法:实体-联系方法。
1.2.2 数据模型的组成要素
- 数据结构数据库的组成对象以及对象之间的联系。
- 数据操作操作及操作规则。对各种对象的实例允许执行的操作的集合。
- 数据的完整性约束条件一组完整性规则。
1.2.3 常用数据模型
- 层次模型
- 网状模型
- 关系模型
- 面向对象数据模型
- 对象关系数据模型
- 半结构化数据模型
基本层次联系 是指两个记录以及它们间的一对多(包括一对一)的联系。
1.2.4 数据模型详解
层次模型
特点:
- 有且只有一个结点没有双亲节点,称为根节点
- 根以外其它结点有且只有一个双亲节点
//类似树,只有一个父节点但可以很多个子节点。
优点:
- 数据结构简单清晰
- 查询效率高
- 良好的完整性支持
缺点:
- 非层次性的不适合用层次模型表示
- 具有多个父节点的关系,使用层次模型比较复杂
- 查询子女结点必须通过双亲节点
- 层次命令趋于程序化
网状模型
特点:
- 允许一个以上的节点无双亲
- 一个结点可以有多个双亲
优点:
- 更直接描述现实世界
- 性能良好,存取效率高
缺点:
- 结构复杂
- 嵌入高级语言后,用户不易掌握
- 必须了解系统结构的细节
关系模型
数据结构
- 关系:一张表
- 元组:一行
- 属性:一列
- 码:唯一标识
- 域:属性的取值范围
- 分量:元组的一个属性值
- 关系模式:对关系的描述,理解成表头,但表示成关系名(属性1,属性2,属性3,……)
关系模型要求关系规范化,不允许表中有表。
术语对比
关系术语 一般表格的术语 关系名 表名 关系模式 表头 关系 一张表 元组 行 属性 列 属性名 列名 属性值 列值 分量 一条记录中的一个列值 非规范关系 表中有表 优点:
- 建立在严格的数学概念上
- 数据结构简单、清晰,用户易懂、易用
- 存取路径对用户透明
1.3 数据库系统的三级模式结构
外模式、模式、内模式 三个抽象级别。
- 模式也称 逻辑模式 ,是数据库中全体数据的逻辑结构和特征的描述,是所有用户的公共数据视图。
- 外模式也称 子模式 或 用户模式 ,它是数据库用户能够看见和使用的局部数据的逻辑结构和特征的描述,是数据库用户的数据视图,是与某一应用有关的数据的逻辑表示。
- 内模式也称 存储模式 ,一个数据库只有一个内模式,它是数据物理结构和存储方式的描述。是数据在数据库内部的组织方式。
1.4 数据库系统的二级映像功能与数据独立性
两层映像:
- 外模式 / 模式映像一个模式(数据的全局逻辑结构)可以有多个外模式(数据的局部逻辑结构)。注:应用程序是根据数据的外模式编写的。数据与程序有逻辑独立性。模式改变时,外模式可以不变。
- 模式 / 内模式映像当存储结构改变时,模式和应用程序都可以不改变。数据与程序具有物理独立性。
第二章 关系数据库
2.1 关系数据结构及形式化定义
2.1.1 关系
- 域属性的取值范围。定义:域是一组具有相同数据类型的值的集合。域允许的不同取值个数称为 基数。
- 笛卡尔积就是把多个域的全部组合遍历。例如 老师域×学生域 得到 全部(老师,学生)。
- 关系笛卡尔积代表全部的可能性,但现实中往往并不是全部可能性都真实存在。因此笛卡尔积的子集称为关系。若某一属性组的值能唯一地标识一个元组,而其子集不能,则称该属性组为 候选码 。多个候选码,选一个为 主码 。候选码的各种属性称为 主属性 。不包含在各种候选码中的属性称为 非主属性 或 非码属性 。候选码有时候只有一个属性,有时候可能包含全部属性,即全部属性才能唯一地标识一个元组。后者称为 全码 。三种类型:
- 基本关系(基本表,基表)
- 查询表
- 视图表
- 列是同质的,每个分量来自同一个域。
- 不同的列可以出于同一个域。如 老师域和学生域 合成 人域。但属性名(列名)需要区分开。
- 列的顺序无所谓。
- 行的顺序无所谓
- 任意两个元组的候选码不同。(候选码是唯一标识)
- 分量必须取原子值。即每个分量都是一个不可分的数据项。(基本)
2.2 关系的完整性
实体完整性、参照完整性、用户定义的完整性。
- 实体完整性主属性不能取空值。
- 参照完整性外码对于关系 R 中每个元组,外码要么取空,要么取外码所在表的主码(唯一标识)。
- 用户定义的完整性例如分数在0~100之间。
2.3 关系代数
传统集合运算:
- 并 R ∪ S
- 差 R – S 属于R但不属于S
- 交 R ∩ S
- 笛卡尔积 ×
关系运算
- 选择 Sigma σF(R)选择满足条件的元组。
- 投影 派 ΠA(R)关系 R 的投影是从 R 中选择出若干属性列,组成新的关系。
- 连接 $$
R \Join S
$$
从两个 关系 的笛卡尔积中选取满足条件的元组。 - 除运算 R ÷ S 包含所有在 R 不在 S 的属性和值。
2.4 关系演算
ALPHA 语言
GET 检索操作
- GET W(SC.Cno) //条件为空
- GET W(5)(Student.Sno,Student.Sage):Student.Sdept=‘IS’ DOWN Student.Sage //取五个 IS 系学生的学号年龄,年龄降序排列。
PUT 插入操作
HOLD 取指令,类似于指针指向一个关系
UPDATE 修改操作,不允许修改主码
DELETE
DROP
基本格式
操作语句 工作空间名(数量)(表达式):操作条件
RANGE A B 把 A 重命名为 B 。
函数名 功能 COUNT 对元组计数 TOTAL 求总和 MAX 求最大值 MIN 求最小值 AVG 求平均值 QBE 语言
Query By Example。
在表中填条件。
操作符
操作符 意义 P. Print 打印 U. Update 更新 I. Insert 插入 D. Delete 删除 第三章 SQL
3.1 概述
特点:
- 综合统一
- 高度非过程化
- 面向集合的操作方式
- 同一种语法结构提供多种使用方式
- 语言简洁,易学易用
数据库中只存放视图定义,不存放视图数据。
3.2 SQL 语句
操作关键字:
关键字 作用 CREATE 创建 DROP 删除表,索引 ALTER TABLE 修改 UPDATE 更新 DELETE 删除数据 ALTER TABLE 适用于表和索引的修改,可以ADD列,DROP列,ALTER列
名词关键字:
关键字 意义 SCHEMA 模式 TABLE 表 INDEX 索引 数据类型:(挖坑,下辈子填)
SELECT 语句中可选的条件:
关键字 作用 DISTINCT 去除重复 ALL 不去除重复 通配符:
- % 任意长度
- _ 一位长度
WHERE 子句常用查询条件:
3.3 索引(INDEX)
索引存在可以加快查询速度。
3.4 视图(VIEW)
CREATIVE VIEW name (列名1,列名2…) AS SELECT [WITH CHECK OPTION];
第四章 数据库安全性
4.1 安全性概述
安全性:保护数据库以防止不合法的使用所造成的数据泄露、更改或破坏。
不安全因素:
- 非授权用户对数据库恶意存取破坏
- 数据库的重要或敏感的数据被泄露
- 安全环境的脆弱性
4.2 安全性控制
常用方法和技术:
- 用户标识和鉴别
- 存取控制
- 视图机制
- 审计
- 数据加密
- 用户身份鉴别
- 静态口令
- 动态口令
- 生物特征
- 智能卡
- 存取控制
- 定义用户权限
- 合法权限检查
- 自主存取控制两个要素:
- 数据库对象
- 操作类型
- 授权
- 授予 GRANTGRANT 操作如SELECT ON TABLE name TO username;
- 收回 REVOKEREVOKE 操作如SELECT ON TABLE name TO username;
- 强制存取控制方法强制存取控制是对数据本身进行密集标记。无论数据如何复制,标记和数据是一个不可分的整体。只有符合密级标记要求的用户才可以操纵数据,从而提供了更高级别的安全性。数据库所管理的实体被分为主体和客体。客体受主体操控。可以理解成,主体是操作者,客体是资料。敏感度标记分配给主体与客体的实例值。主体:许可证级别。客体:密级。敏感度标记:
- 绝密
- 机密
- 可信
- 公开
- 主体许可证级别高于等于客体密级则可读
- 主体许可证级别低于等于客体密级则可写
第五章 数据库完整性
指 正确性和相容性。
5.1 实体完整性
主码不能取空值。
定义实体完整性(设置主码)
- 列级定义(单属性)
- 表级定义(单属性或多属性)
检查实体完整性
- 检查主码值是否唯一
- 检查主码的各个属性是否为空
5.2 参照完整性
外码对于关系 R 中每个元组,外码要么取空,要么取外码所在表的主码(唯一标识)。
定义参照完整性(设置外码)
表级定义。
FOREIGN KEY (属性名) REFERENCES 表名(属性名)
检查参照完整性
5.3 用户定义的完整性
如列值非空,列值唯一,列值是否满足一个条件表达式 CHECK(列名 条件)。
- 列级定义
- 元组上定义
第六章 关系数据理论
6.1 函数依赖
X→Y则函数依赖。
通过X可以推出Y。
6.2 码
候选码
主码
主属性:包含在任何一个候选码中的属性。
其它叫非主属性。
码:候选码和主码统一简称。
6.3 范式
关系数据库中的关系是要满足一定要求的,满足不同程度要求的为不同范式。
最低要求 1NF。第一范式中,进一步要求为 2NF。
规范化:一个低一级范式的关系模式,通过模式分解可以转换为若干个高一级范式的关系模式的集合。
6.4 2NF
若R∈1NF,且每一个非主属性完全函数依赖于任何一个候选码,则R∈2NF。
6.5 3NF
没有传递依赖关系的存在称为3NF。
定义:设R<U,F>不存在 码X,属性组Y,非主属性Z,使得
$$
X\to Y,Y\to Z,Y\not{\to } X
$$成立,则称 R<U,F>∈3NF。
第七章 数据库设计
7.1 基本步骤
- 需求分析
- 概念结构设计
- 逻辑结构设计
- 物理结构设计
- 数据库实施
- 数据库运行和维护
7.2 E-R 图
E-R 图是概念结构设计中的概念。
E-R 模型
两个实体型之间的联系:
- 一对一联系(1:1)
- 一对多联系(1:n)
- 多对多联系(m:n)
多个实体型之间的联系:
一对一,一对多,多对多。
单个实体型内的联系:
一对一,一对多,多对多。
E-R 图
实体型:矩形
属性:椭圆
联系:菱形
第十章 数据库恢复技术
10.1 事务
定义
是用户定义的一个数据库操作序列。
这一系列操作,要不全都做,要不全都不做。是不可分割的。
特性
ACID 特性:
- 原子性(不可分割)
- 一致性(使数据库中的数据不发生逻辑错误)
- 隔离性(并发的各个事务之间不能相互干扰)
- 持续性(永久性,事务提交后,其改变或者效果应该是永久性的)
干扰因素:
- 不同事务交叉执行
- 事务执行时被强行停止
第十一章 并发控制
11.1 概述
事务是并发控制的基本单位。
并发操作带来的不一致性
- 丢失修改
- 不可重复读
- 读 “脏” 数据
主要原因:并发操作破坏了事务的隔离性。
并发控制机制就是要用正确的方式调度并发操作,使一个用户事务的执行不受其他事务的干扰。
并发控制主要技术:
- 封锁
- 时间戳
- 乐观控制法
- 多版本并发控制
11.2 可串行化调度
定义
多个事务的并发执行是正确的,当且仅当其结果与按某一次序串行执行这些事务时的结果相同。
可串行性
是并发事务正确调度的准则。
11.3 两段锁协议(2PL)
事务两个阶段:
- 获得封锁,扩展阶段:在对任何数据进行读、写操作之前,首先要申请并获得对该数据的封锁。
- 释放封锁,收缩阶段:在释放一个封锁之后,事物不再申请和获得任何其他封锁。
11.4 封锁的粒度
封锁对象的大小。
封锁对象可以是逻辑单元,也可以是物理单元。
封锁粒度与系统的并发度和并发控制的开销密切相关。
多种封锁粒度,供不同的事务选择。称为多粒度封锁。
-
Web 课程设计
本文参考1 WebSocket之仿QQWeb即时聊天系统(上)
本文参考2 WebSocket之仿QQWeb即时聊天系统(下)
本文参考3 Node.js 教程_菜鸟
本文参考4 socket.io 概述
摇骰子摇到了1,所以选择了仿QQWeb即时聊天系统
功能要求
- 实现
Web
的点对点即时的文本消息聊天功能。 - 实现
Web
的表情的发送、接收和显示功能。 - 实现
Web
的图片的发送、接收和显示功能。 - 实现本地消息的存储,在离线的时候也能加载和查看历史消息;
- 要求使用
WebSocket
;
功能分析
就是能发 文本、表情、图片。点对点。
还使用了
WebSocket
。WebSocket
?WebSocket
WebSocket
是独立的、创建在 TCP 上的协议。Websocket
通过HTTP/1.1 协议的101状态码进行握手。为了创建
Websocket
连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(handshaking)。WebSocket
的出现,使得浏览器具备了实时双向通信的能力。WebSocket
可以在浏览器里使用- 支持双向通信
- 使用很简单
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在
WebSocket API
中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。现在,很多网站为了实现推送技术,所用的技术都是
Ajax
轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP
请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。HTML5
定义的WebSocket
协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。浏览器通过
JavaScript
向服务器发出建立WebSocket
连接的请求,连接建立以后,客户端和服务器端就可以通过TCP
连接直接交换数据。当你获取 Web Socket 连接后,你可以通过
send()
方法来向服务器发送数据,并通过onmessage
事件来接收服务器返回的数据。WebSocket
是一个很棒的概念,我单独拿出来写。WebSocket
创建 对象
以下
API
用于创建WebSocket
对象。var Socket = new WebSocket(url, [protocol] );
以上代码中的第一个参数
url
, 指定连接的URL
。第二个参数protocol
是可选的,指定了可接受的子协议。
属性
以下是
WebSocket
对象的属性。假定我们使用了以上代码创建了Socket
对象:属性 描述 Socket.readyState 只读属性 readyState 表示连接状态,可以是以下值:0 – 表示连接尚未建立。1 – 表示连接已建立,可以进行通信。2 – 表示连接正在进行关闭。3 – 表示连接已经关闭或者连接不能打开。 Socket.bufferedAmount 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。
WebSocket 事件
以下是 WebSocket 对象的相关事件。假定我们使用了以上代码创建了 Socket 对象:
事件 事件处理程序 描述 open Socket.onopen 连接建立时触发 message Socket.onmessage 客户端接收服务端数据时触发 error Socket.onerror 通信发生错误时触发 close Socket.onclose 连接关闭时触发
WebSocket 方法
以下是
WebSocket
对象的相关方法。假定我们使用了以上代码创建了Socket
对象:方法 描述 Socket.send() 使用连接发送数据 Socket.close() 关闭连接
为了建立一个
WebSocket
连接,客户端浏览器首先要向服务器发起一个HTTP
请求,这个请求和通常的HTTP
请求不同,包含了一些附加头信息,其中附加头信息"Upgrade: WebSocket"
表明这是一个申请协议升级的HTTP
请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的WebSocket
连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。一些准备工作
根据大佬RongLin02的博客做一些准备工作。
首先是对于
WebSocket
的了解。以上已经介绍了。然后是安装Node.js,你也可以进入 官网 下载。学习页面。
NPM
的安装,在上一步极有可能已经附带完成。socket.io
是一个
WebSocket
库,用起来很方便。库所以只需要引入而不需要下载。
在项目根目录 右键–
在集成终端中打开
,执行以下代码:
npm install jquery npm install socketio npm install express
会生成一个
node_modules
文件夹用来存放安装好的模块,一些配置信息会生成在package-lock.json
和package.json
文件中。使用方法
在中文文档里面发现第一个文档 socket.io 快速入门教程——聊天应用 就和我们的任务很像。
按照其流程一步一步来。
常用方法
socket.emit('A',value);
这个是发送方法,A是名字,叫什么名字都行,
value
就是要发送的值,可以是服务器给客户端发送,也可以是客户端给服务器发送,类型也可以是一个对象。socket.on('A',function(data){ console.log(data); });
有发送就有接受,服务器或者客户端会监听,如果有终端通过 名字 A 发送,就会获取到发送的数据,并且把
value
存到data
中,所以说上边的那个data
的值就是emit
的value
。emit
和on
一对一。运行方法
根目录下 右键-
终端运行
输入node index.js
或 编译器左边
项目目录
,右键
–打开于
–终端
。在编译器下方的窗口输入指令。REPL 命令
ctrl + c
– 退出当前终端。ctrl + c 按下两次
– 退出 Node REPL。ctrl + d
– 退出 Node REPL.向上/向下 键
– 查看输入的历史命令tab 键
– 列出当前命令
学习过程
1. 服务器端的编写
创建服务器
使用
http.createServer()
方法创建服务器,并使用 listen 方法绑定端口3000
。 函数通过request
,response
参数来接收和响应数据。在项目根目录 新建一个
index.js
文件来创建应用。var app = require('express')(); var http = require('http').Server(app); app.get('/', function(req, res){ console.log("主页 POST 请求"); res.send('<h1>Hello world</h1>'); }); var server = app.listen(3000, function () { var host = server.address().address var port = server.address().port console.log("访问地址为 http://%s:%s", host, port) })
这段代码作用如下:
Express
初始化app
作为HTTP
服务器的回调函数 (见第 2 行)。- 定义了一个路由
/
来处理首页访问。在终端输出"主页 POST 请求"
,给客户端返回'<h1>Hello world</h1>'
。 - 使 http 服务器监听
端口 3000
。也就是说这个代码只对端口3000
有效。
路由/其实是域名后面的东西。如果访问 localhost:3000/,因为 域名后面只有“/”,所以匹配了这个路由。
运行node index.js
2. Node.js
模块
引入模块
在
Node.js
中,引入一个模块非常简单。创建
hello.js
文件,代码如下://hello.js exports.world = function() { console.log('Hello World'); } //world模块就暴露了,调用见下
创建一个
main.js
文件并引入hello 模块
,代码如下://main.js var hello = require('./hello');//引入 hello.world();//调用
以上实例中,代码
require('./hello')
引入了当前目录下的hello.js
文件(./ 为当前目录,node.js 默认后缀为 js)。Node.js
提供了exports
和require
两个对象。exports
是模块公开的接口。require
用于从外部获取一个模块的接口,即所获取模块的exports
对象。有时候我们只是想把一个对象封装到模块中,格式如下:
module.exports = function() { // ... }
例如:
//hello.js function Hello() { var name; this.setName = function(thyName) { name = thyName; }; this.sayHello = function() { console.log('Hello ' + name); }; }; module.exports = Hello; //暴露接口
这样就可以直接获得这个对象了:
//main.js var Hello = require('./hello'); hello = new Hello(); hello.setName('BYVoid'); hello.sayHello();
Node.js
中自带了一个叫做http
的模块,我们在我们的代码中请求它并把返回值赋给一个本地变量。这把我们的本地变量变成了一个拥有所有
http
模块所提供的公共方法的对象。
模块的加载
在路径 Y 下执行 require(X) 语句执行顺序:
1. 如果 X 是内置模块 a. 返回内置模块 b. 停止执行 2. 如果 X 以 '/' 开头 a. 设置 Y 为文件根路径 3. 如果 X 以 './' 或 '/' or '../' 开头 a. LOAD_AS_FILE(Y + X) b. LOAD_AS_DIRECTORY(Y + X) 4. LOAD_NODE_MODULES(X, dirname(Y)) 5. 抛出异常 "not found" LOAD_AS_FILE(X) //作为文件加载 1. 如果 X 是一个文件, 将 X 作为 JavaScript 文本载入并停止执行。 2. 如果 X.js 是一个文件, 将 X.js 作为 JavaScript 文本载入并停止执行。 3. 如果 X.json 是一个文件, 解析 X.json 为 JavaScript 对象并停止执行。 4. 如果 X.node 是一个文件, 将 X.node 作为二进制插件载入并停止执行。 LOAD_INDEX(X) //加载索引 1. 如果 X/index.js 是一个文件, 将 X/index.js 作为 JavaScript 文本载入并停止执行。 2. 如果 X/index.json 是一个文件, 解析 X/index.json 为 JavaScript 对象并停止执行。 3. 如果 X/index.node 是一个文件, 将 X/index.node 作为二进制插件载入并停止执行。 LOAD_AS_DIRECTORY(X) //作为目录加载 1. 如果 X/package.json 是一个文件, a. 解析 X/package.json, 并查找 "main" 字段。 b. let M = X + (json main 字段) c. LOAD_AS_FILE(M) d. LOAD_INDEX(M) 2. LOAD_INDEX(X) LOAD_NODE_MODULES(X, START) //加载节点模块 1. let DIRS=NODE_MODULES_PATHS(START) 2. for each DIR in DIRS: a. LOAD_AS_FILE(DIR/X) b. LOAD_AS_DIRECTORY(DIR/X) NODE_MODULES_PATHS(START) //节点模块路径 1. let PARTS = path split(START) 2. let I = count of PARTS - 1 3. let DIRS = [] 4. while I >= 0, a. if PARTS[I] = "node_modules" CONTINUE b. DIR = path join(PARTS[0 .. I] + "node_modules") c. DIRS = DIRS + DIR d. let I = I - 1 5. return DIRS
回调函数
回调函数在完成任务后就会被调用,
Node
使用了大量的回调函数,Node
所有API
都支持回调函数。
函数
一个函数可以作为另一个函数的参数。我们可以先定义一个函数,然后传递,也可以在传递参数的地方直接定义函数。
举例来说,你可以这样做:
function say(word) { console.log(word); } function execute(someFunction, value) { someFunction(value); } execute(say, "Hello");
匿名函数
我们可以把一个函数作为变量传递。但是我们不一定要绕这个”先定义,再传递”的圈子,我们可以直接在另一个函数的括号中定义和传递这个函数:
function execute(someFunction, value) { someFunction(value); } execute(function(word){ console.log(word) }, "Hello");
我们在 execute 接受第一个参数的地方直接定义了我们准备传递给 execute 的函数。
用这种方式,我们甚至不用给这个函数起名字,这也是为什么它被叫做匿名函数 。
全局对象
__filename
__filename 表示当前正在执行的脚本的文件名。它将输出文件所在位置的绝对路径,且和命令行参数所指定的文件名不一定相同。 如果在模块中,返回的值是模块文件的路径。
__dirname
__dirname 表示当前执行脚本所在的目录。
文件系统
Node 导入文件系统模块(fs)语法如下所示:
var fs = require("fs")
Node.js Web 模块
使用 Node 创建 Web 服务器
Node.js 提供了 http 模块,http 模块主要用于搭建 HTTP 服务端和客户端,使用 HTTP 服务器或客户端功能必须调用 http 模块,代码如下:var http = require('http');
Node.js Express 框架
Express 是一个简洁而灵活的 node.js Web应用框架, 提供了一系列强大特性帮助你创建各种 Web 应用,和丰富的 HTTP 工具。
使用 Express 可以快速地搭建一个完整功能的网站。
Express 框架核心特性:
- 可以设置中间件来响应 HTTP 请求。
- 定义了路由表用于执行不同的 HTTP 请求动作。
- 可以通过向模板传递参数来动态渲染 HTML 页面。
请求和响应
Express 应用使用回调函数的参数: request 和 response 对象来处理请求和响应的数据。
app.get('/', function (req, res) { // -- })
request 和 response 对象的具体介绍:
Request 对象 – request 对象表示 HTTP 请求,包含了请求查询字符串,参数,内容,HTTP 头部等属性。
常见属性有:
- req.app:当callback为外部文件时,用req.app访问express的实例
- req.baseUrl:获取路由当前安装的URL路径
- req.body / req.cookies:获得「请求主体」/ Cookies
- req.fresh / req.stale:判断请求是否还「新鲜」
- req.hostname / req.ip:获取主机名和IP地址
- req.originalUrl:获取原始请求URL
- req.params:获取路由的parameters
- req.path:获取请求路径
- req.protocol:获取协议类型
- req.query:获取URL的查询参数串
- req.route:获取当前匹配的路由
- req.subdomains:获取子域名
- req.accepts():检查可接受的请求的文档类型
- req.acceptsCharsets / req.acceptsEncodings / req.acceptsLanguages:返回指定字符集的第一个可接受字符编码
- req.get():获取指定的HTTP请求头
- req.is():判断请求头Content-Type的MIME类型
Response 对象 – response 对象表示 HTTP 响应,即在接收到请求时向客户端发送的 HTTP 响应数据。
常见属性有:
- res.app:同req.app一样
- res.append():追加指定HTTP头
- res.set()在res.append()后将重置之前设置的头
- res.cookie(name,value [,option]):设置Cookie
- opition: domain / expires / httpOnly / maxAge / path / secure / signed
- res.clearCookie():清除Cookie
- res.download():传送指定路径的文件
- res.get():返回指定的HTTP头
- res.json():传送JSON响应
- res.jsonp():传送JSONP响应
- res.location():只设置响应的Location HTTP头,不设置状态码或者close response
- res.redirect():设置响应的Location HTTP头,并且设置状态码302
- res.render(view,[locals],callback):渲染一个view,同时向callback传递渲染后的字符串,如果在渲染过程中有错误发生next(err)将会被自动调用。callback将会被传入一个可能发生的错误以及渲染后的页面,这样就不会自动输出了。
- res.send():传送HTTP响应
- res.sendFile(path [,options] [,fn]):传送指定路径的文件 -会自动根据文件extension设定Content-Type
- res.set():设置HTTP头,传入object可以一次设置多个头
- res.status():设置HTTP状态码
- res.type():设置Content-Type的MIME类型
app.use
app.use(path,callback)
app.use(path,callback)
中的callback既可以是router对象又可以是函数。app.get(path,callback)
中的callback只能是函数。当一个路由有好多个子路由时用
app.use(path,router)
例子:
http://localhost:3000/home/one
http://localhost:3000/home/two
http://localhost:3000/home/three
路由/home后面有三个子路由紧紧跟随,分别是/one,/two,/three,如果使用
app.get()
,则要不断的重复,很麻烦,也不利用区分。我们可以创建一个
router.js
专门用来一个路由匹配多个子路由。var express = require('express') var router = express.Router() router.get("/",(req,res)=>{ res.send("/") }) router.get("/one",(req,res)=>{ res.send("one") }) router.get("/two",(req,res)=>{ res.send("second") }) router.get("/three",(req,res)=>{ res.send("three") }) module.exports = router; //module.exports把router暴露出去了。
在
app.js
中导入router.js
var express = require('express') var router = require("./router") var app = express() app.use('/home',router) //router路由对象中的路由都会匹配到"/home"路由后面 app.get('/about', function (req, res) { console.log(req.query) res.send('你好,我是 Express!') }) app.listen(3000, function () { console.log('app is running at port 3000.') })
app.use(express.static(‘public’));
为了提供对静态资源文件(图片,css,js文件)的服务,请使用Express内置的中间函数
express.static
。传递一个包含静态资源的目录给
express.static
中间件用于立即开始提供文件。 比如用以下代码来提供public目录下的图片、css文件和js文件:app.use(express.static('public'));
如果前台想请求后台public目录下images/08.jpg静态的图片资源
通过: http://localhost:3000/images/08.jpg通过多次使用
express.static
中间件来添加多个静态资源目录:app.use(express.static('public')); app.use(express.static('file'));
Express将会按照你设置静态资源目录的顺序来查看静态资源文件。
为了给静态资源文件创建一个虚拟的文件前缀(文件系统中不存在),可以使用express.static函数指定一个虚拟的静态目录,如下:
app.use('/static', express.static('public'));
现在你可以使用‘/static’作为前缀来加载public文件夹下的文件了。
比如: http:// localhost:3000/static/image/kitten.jpg
get/post
get
“读取“一个资源。比如
Get
到一个html
文件。反复读取不应该对访问的数据有副作用。post
例如,在页面里<form> 标签会定义一个表单。点击其中的submit元素会发出一个POST请求让服务器做一件事。这件事往往是有副作用的,不幂等的。
post是一种提交。
4. 客户端的编写
在上面的这个代码里面,我们对访问 localhost:3000 采用 res.send() 方法,让其返回一个网页。这样显然对于前端并不友好。
替代的方法是新建一个
index.html
文件作为服务器响应。现在我们用
sendFile
来重构之前的回调:app.get('/', function(req, res){ res.sendFile(__dirname + '/index.html'); });
__dirname指的是当前文件所在文件夹的绝对路径。
index.html
内容如下:<!doctype html> <html> <head> <title>Socket.IO chat</title> <!--网页名称--> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font: 13px Helvetica, Arial; } form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; } form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; } form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; } #messages { list-style-type: none; margin: 0; padding: 0; } #messages li { padding: 5px 10px; } #messages li:nth-child(odd) { background: #eee; } </style> </head> <body> <ul id="messages"></ul> <form action=""> <input id="m" autocomplete="off" /><button>Send</button> </form> </body> </html>
- margin 属性为“外边距”,指的是围绕在元素边框的空白区域;
- Padding 属性为“内边距”,定义元素边框与元素内容之间的空间。
- box-sizing 是设置盒子模型用的,他其实是设置容器最终的尺寸的计算方法,盒子的计算方法如下: width(宽度) + padding(内边距) + border(边框) = 元素实际宽度
height(高度) + padding(内边距) + border(边框) = 元素实际高度 上面的计算方法是按照box-sizing:content-box来说的,根据这种设置办法是,先规定了显示区域的大小,在加上边框和内边距就是元素最终显示的大小 还有另外一种模式,就是box-sizing:border-box,他的使用方式就是,先规定了最终显示的区域大小,实际内容展示区域是根据最终区域大小减去边框和内边距,自适应比较强,有时候比较方便页面布局一些。 - font 规定文本的字体、字体尺寸、字体颜色。
日后再补。
5. 集成 Socket.IO
Socket.IO 由两部分组成:
- 一个服务端用于集成 (或挂载) 到 Node.JS HTTP 服务器:
socket.io
- 一个加载到浏览器中的客户端:
socket.io-client
开发环境下,
socket.io
会自动提供客户端。Socket.IO
的核心理念就是允许发送、接收任意事件和任意数据。任意能被编码为 JSON 的对象都可以用于传输。6.
jQuery
$
就是jQuery
的别称。而
jQuery
就是jQuery
库提供的一个函数.(好像也不仅仅只是函数, 因为还有$.ajax(options)
这样的使用,等同jQuery.ajax(options))
。这个函数的作用是根据 () 里的参数进行查找和选择html文档中的元素, 函数作用之一就是GetElementByID的代替,但()内不仅可以是ID,还可以是各类选择器。
比如:
$(document)
就是 选取整个文档对象。7. HTML

- <!DOCTYPE html> 声明为 HTML5 文档
- <html> 元素是 HTML 页面的根元素
- <head> 元素包含了文档的元(meta)数据,如 <meta charset=“utf-8”> 定义网页编码格式为 utf-8。
- <title> 元素描述了文档的标题
- <body> 元素包含了可见的页面内容
- <h1> 元素定义一个大标题
- <p> 元素定义一个段落
学习过程中的代码
前期的学习到这里结束。
接下来放一段到此为止的代码。
服务端:
const { data } = require('jquery'); const app = require('express')(); const http = require('http').Server(app); const io = require('socket.io')(http); app.get('/', function(req, res){ res.sendFile(__dirname + '/index.html'); console.log("主页 get 请求"); }); io.on('connection', function(socket){ console.log("一位用户连接"); socket.on('chat message', function(msg){ console.log('message: ' + msg); io.emit('chat message', msg); }); socket.on('disconnect', function(){ console.log('用户关闭连接'); }); }); http.listen(3000, function(){ console.log('http://127.0.0.1:3000'); });
客户端
<!doctype html> <html> <head> <title>Socket.IO chat</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font: 13px Helvetica, Arial; } form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; } form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; } form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; } #messages { list-style-type: none; margin: 0; padding: 0; } #messages li { padding: 5px 10px; } #messages li:nth-child(odd) { background: #eee; } </style> </head> <body> <ul id="messages"></ul> <form action=""> <input id="m" autocomplete="off" /><button>Send</button> </form> </body> <script src="/socket.io/socket.io.js"></script> <script src="https://code.jquery.com/jquery-1.11.1.js" rel="external nofollow" ></script> <script> $(function () { var socket = io(); $('form').submit(function(){ socket.emit('chat message', $('#m').val()); $('#m').val(''); return false; }); socket.on('chat message', function(msg){ $('#messages').append($('<li>').text(msg)); }); }); </script> </html>
文件结构
接下来正式开始。我将在上面代码的基础上修改。
开始
本地数据库
建立数据库
数据库建表
index.js
文件添加依赖及定义,现在定义部分如下const { data } = require('jquery'); const express=require('express'); const app = require('express')(); const http = require('http').Server(app); const io = require('socket.io')(http); const mysql = require('mysql'); const url = require('url'); const fs = require('fs'); const { Console } = require('console'); const { query } = require('express'); const PORT = 3000;
后端代码
const { data } = require('jquery'); const express=require('express'); const app = require('express')(); const http = require('http').Server(app); const io = require('socket.io')(http); const mysql = require('mysql'); const url = require('url'); const fs = require('fs'); const { Console } = require('console'); const { query } = require('express'); const Front=__dirname +"/Front/"; const PORT = 3000; const _cilents=[]; //在线的名称 //数据库连接 const connection = mysql.createConnection({ host: 'localhost', user: 'root', password: 'mhqdcqxs', database: 'ChatProject' }); //从数据库中查询 function select_user(data,callback){ //连接数据库开始查询 let sql = 'SELECT * FROM user where ac = \''+data.username+'\';'; connection.query(sql,function (err, result) { if(err){ console.log('[SELECT ERROR] - ',err.message); callback(null) //如果查不到就返回null } //用回调函数表示执行完了。回调函数是哪个?取决于传参。 //查到了就返回结果 callback(result); }); } //插入数据库 function insert_user(data){ let sql ='INSERT INTO user VALUES (\''+data.username+'\',\''+data.password+'\');'; connection.query(sql,(err,result)=>{ if(err){ console.log('[INSERT ERROR] - ',err.message); return; }; }); } app.get('/', function(req, res){ res.sendFile(Front + 'index.html'); console.log("主页 get 请求"); }); io.on('connection', function(socket){ socket.on('login',function(data){ //寻找是否在线 var t_cilent = _cilents.find(item => item == data.username); if(t_cilent){ socket.emit('loginFail','该用户已经登录'); return ; } //从数据库中寻找 select_user(data,result=>{ if(result.length){ if(result[0].pw != data.password){ socket.emit('loginFail','密码错误!'); return ; } } else{ insert_user(data); } //记录已经登录的用户 _cilents.push(data.username); socket.emit('loginSuccess',_cilents); //全局广播,来了新用户 io.emit('updateUser', _cilents); socket.username = data.username; }) }); // 用户断开连接的功能 socket.on('disconnect', () => { // 把当前用户的信息删除 // 找到断开连接的用户的下标 const idx =_cilents.findIndex(item => item == socket.username) // 删除函数,第一个参数是下标,第二个是距离,也就是删几个元素 _cilents.splice(idx, 1); // 告诉所有人,用户发生更新 io.emit('updateUser', _cilents) }); socket.on('sendMessage',data=>{ var toSocket = null //找到接收者在io.sockets.sockets中的位置。 for (const key in io.sockets.sockets) { if (io.sockets.sockets[key].username == data.toName) { toSocket = key break } } if (toSocket) { // 发送给指定用户 socket.to(toSocket).emit('receiveMessage', { msg:data.msg, fromName:data.name, time : data.time, name:data.toName }) } //将数据存入文件中 let url = './history/'+data.name+'To'+data.toName+'.txt'; fs.appendFile(url,'\n'+JSON.stringify(data), err => {if (err) {console.log(err)}}); if(data.toName != data.name){ let t_url = './history/'+data.toName+'To'+data.name+'.txt'; fs.appendFile(t_url,'\n'+JSON.stringify(data), err => {if (err) {console.log(err)}}); } }); socket.on('sendImage',data=>{ var toSocket = null //console.log(io.sockets.sockets) for (const key in io.sockets.sockets) { if (io.sockets.sockets[key].username == data.toName) { toSocket = key break } } //console.log(data); if (toSocket) { // 发送给指定用户 socket.to(toSocket).emit('receiveImage', { image:data.image, fromName:data.name, time : data.time, name:data.toName }) } //将数据存入文件中 let url = './history/'+data.name+'To'+data.toName+'.txt'; fs.appendFile(url,'\n'+JSON.stringify(data), err => {if (err) {console.log(err)}}); if(data.toName != data.name){ let t_url = './history/'+data.toName+'To'+data.name+'.txt'; fs.appendFile(t_url,'\n'+JSON.stringify(data), err => {if (err) {console.log(err)}}); } }) }); http.listen(PORT, function(){ console.log('http://127.0.0.1:'+PORT+'/'); });
后面没有了,开发时间太紧没有继续记录,但以上作为一个前置步骤已经比较完整。
- 实现