Java
基本知识
-
基本数据类型
// 整数 byte 1 short 2 int 4 long 8 // 浮点数 float 4 double 8 // 逻辑 boolean 1 // 字符 char 2, Unicode
- Override, 覆盖/重写, 返回值和形参都不能改变
- Overload, 重载
栈和堆
- 基本数据类型的变量, 在栈里面, 复制变量的时候, 复制的是值
引用类型的变量, 在堆, 变量只是引用, 类似于指针, 只能指向特定对象, 不能乱指, 复制变量的时候, 复制的是引用
引用类型: 数组, 类, 接口
抽象和接口
-
抽象类跟接口的区别
抽象类, 是声明, 抽象类不能instantiate(实例化), 也就是不能制造对象。抽象方法只声明, 不实现。具体的实现由继承它的子类来实现。
public abstract class Shape{ // 抽象的方法, 注意没有大括号, ()后面直接分号结尾 // 有abstract方法的类必须是abstract public abstract void draw(); }
接口, 让其他类能够有接口的方法
public interface Cell{ // 不需要显示写abstract void draw(Graphics g, int x, int y, int size); } public class Fox extends Animal implements Cell{ @Override public void draw(...){ // ... } public void run() { } }
抽象类可以有非抽象的方法(具体实现), 接口是纯抽象类, 只有抽象方法
一个子类只能存在一个父类, 但是可以存在多个接口。
MVC 开发模型
- 数据模型、表现、控制, 三者分离
M, Model, 模型, 保存和维护数据, 还可以 通知 View 进行界面的更新.
V, View, 表现, 从 Model 获得数据, 并以此画出表现(界面). View 是被动的, 只有在 Model 通知 View 之后, View才会去Model取数据, 并画出来.
C, Control, 控制, 得到用户输入, 以此调整数据. C 和 V 并不直接交互. C只能更新Model中的数据, 也就是说, 用户在界面上所作的操作, 不会直接修改界面上的显示. 而是先去修改后台的数据, 再由Model通知View, 再整体重画.
多线程
- 创建线程的方式, 继承 Thread 类, 实现 Runnable 接口
start(), run(), stop(), destroy()
新建 --- 就绪 --- 运行 --- 阻塞 --- 运行 --- 终止
错误和异常
-
分类
ArrayIndexOutOfBoundsException OpenFileException AllocateMemoryException LoadFileException OutOfMemoryError
-
处理方式
try{ } catch(ArrayIndexOutOfBoundsException e){ }
GC, 垃圾回收机制
-
Wikipedia, GC)
某个对象在未来的程序运行中, 将不会被访问, 就可以回收它的内存
- 回收方法,策略
引用计数收集器, 当有其他数据与其相关时则加一, 反之相关解除时减一, 最后计数为 0 的对象可以回收
跟踪收集器, 定期对若干根储存对象开始遍历, 对与其相关的对象进行标记, 最后, 没有被标记的对象就可以回收
Java HashMap 实现
- 邻接链表,也就是hash值对应了一个数组的下标,然后数组的元素又是一个链表
- 负载因子超过 0.75 后, 数组会 resize() 到原来的 2 倍
-
Java HashMap工作原理及实现
C
存储区
- 栈 stack
局部变量, 函数参数
- 全局/静态 存储区
全局变量, 静态全局变量, 静态局部变量
若没有手动初始化, 则会自动初始化为0
- 堆 heap
new --- delete / malloc --- free
- 常量存储区
存放字符串常量和const修饰的全局变量
c的源文件经过了预处理, 编译, 汇编, 链接, 每一步的作用是什么。
- 预处理Pre Pocess:包含头文件, 条件编译, 宏替换
- 编译:检查语法错误. 检查无误后, 将代码翻译成汇编语言
- 汇编:将汇编语言转化成二进制代码表示的目标文件. 每一个源文件(.c)产生一个目标文件(.obj)
- 链接:与库函数进行链接, 形成可执行文件
- 整体过程
预处理 --- 编译 --- 汇编 --- 链接 --- 装载 --- 运行
malloc, 分配内存, 是在
运行
这一阶段。即在 heap 上动态分配 -
gcc编译程序的四个阶段(预处理-编译-汇编-链接)
-
C++ 预处理、编译、汇编、链接
-
C语言编译过程详解
指针
-
指向二维数组的指针
int a[4][2]; int (*p) [2]; p = a; p[3][1];
[]
的优先级高于*
, 所以要加括号 -
二级指针
void getMemory(char **p){ *p = (char *)malloc(100); } int main(){ char *str = NULL; getMemory(&str); strcpy(str, "Thurs"); strcat(str+2, " day"); printf("%s\n", str); return 0; }
首先
str
被分配了100字节的空间, 然后在 str 开头粘贴了Thurs
, 接着, 由于strcat
总是先找到末尾再粘贴, 所以day
不会覆盖urs
补码
-
正数与原码和反码相同。对于负数, 以
-1
为例1)先写出原码, `1000 0001` 2)符号位不变, 其余各位取反, `1111 1110` 3)最后 +1, `1111 1111`
-
数据类型在内存中的表达形式
整数:二进制数(补码), 可以直接在加法器里面做加法 浮点数:要进行编码
结构体
-
结构体内定义指针
typedef struct{ int val; struct Node *lchild, *rchild; }Node; Node root = {1, NULL, NULL};
struct node{ int val; struct Node *lchild, *rchild; }; struct node root = {1, NULL, NULL};
const
-
const
在*
之前, 意思是对i
只读/* const 在 * 之前, 意思是 对 i 只读 ** 可以通过 *p 读取i的值, 但不能通过 *p 修改i的值 ** 另外, 指针作为函数参数时, 这样写 (const int * p) */ int i = 1; const int * p1 = &i; int const * p2 = &i;
-
const 在 * 的后面, 意思是 指针 不能指向其他地址
/* const 在 * 的后面, 意思是 指针 不能指向其他地址 ** p3 被绑定到了 j, 就无法再指向其他地址 ** 可以通过p3来读写j的值 */ int j = 2; int * const p3 = &j; *p3; // ok *p3 = 3; // ok int k = 123; p3 = &k; // error
malloc
-
用法
int *a = (int *)malloc( 10*sizeof(int) )
man malloc
可以看到参数要求是(size_t size)
, 所以实参是(10*sizeof(int))
, 表示申请到了10*4=40
个字节的空间, 同样的也可以40*sizeof(char)
, 或者直接写40
可以看到返回类型是void *
, 这也就解释了为什么要在前面加上(int *)
, 由此划分空间, 来区分类型
sizeof()
-
sizeof()
是静态的, 程序编译完成后已经定下来, 直接填进去了, 并不会真的去做计算int a = 1; printf("%d\n", sizeof(a++)); // 4 printf("%d\n", a); // 1, sizeof()是静态的, 程序编译完成后已经定下来, 直接填进去了, 并不会真的去做a的++ printf("%d\n", sizeof(a + 2.0));// 8, 已经转换成了double printf("%d\n", sizeof(double)); // 8 return 0;
define 和 const 的区别
- 编译器处理方式不同
define
宏是在预处理阶段展开,const
常量是编译运行阶段使用. - 类型和安全检查不同
define宏没有类型, 不做任何类型检查, 仅仅是展开. const常量有具体的类型, 在编译阶段会执行类型检查.
- 存储方式不同
define宏仅仅是展开, 有多少地方使用, 就展开多少次, 不会分配内存. const常量会在内存中分配(堆或栈).
其他
- 全局 static, 初始化为 0, 作用域是文件内部, 放在全局/静态 存储区, 生命周期从程序开始到程序结束
- 局部 static, 局部作用域
- extern 全局 (extern可省略)
- 宏 --- 字符替换
- main可以作为变量名
- 浮点数 --- 不可以用
==
作为判断条件
C++
虚函数
- 函数名前面加了
virtual
的函数叫做虚函数 -
用于
多态
(即动态绑定
,覆盖Override
, 运行的时候根据实例对象来调用方法)class Animal { public: void run(){ cout << "That animal is running\n"; } virtual void eat(){ cout << "That animal is eating\n"; } }; class Dog : public Animal{ public: void run(){ cout << "The dog is running\n"; } void eat(){ cout << "The dog is eating\n"; } }; int main(){ // 父类 `Animal` 类型的 `指针` 可以管理子类对象 // 如果方法名前面有 virtual, 则可以动态绑定 Animal * animal = new Dog(); animal->run(); // That animal is running, 隐藏子类方法 animal->eat(); // The dog is eating, 子类方法覆盖父类, 多态 // 注意指针才可以, 普通变量形式仍然会隐藏子类方法 Animal ani = Dog(); ani.eat(); // That animal is eating, 隐藏子类方法 return 0; }
父类方法被声明为
virtual
后, 直接/间接子类中的这个方法都会变成virtual
, 也即子类中的virtual
可以省略, 但最好还是写上去
纯虚函数
-
纯虚函数只有声明, 没有实现, 相当于接口规范。含有纯虚拟函数的类称为抽象类, 它不能生成对象(不能放在等号右边), 但可以声明指向实现该抽象类的具体类的指针或引用。
class Flyable { public: virtual void fly() = 0; // 纯虚函数, 只有声明, 后接 = 0 virtual void run() = 0; }; class Bird : public Flyable{ public: void fly(){ cout << "Bird can fly\n"; // 子类只有实现纯虚函数才能实例化 } }; class Superman : public Flyable{ public: void fly(){ cout << "Superman can fly\n"; } void run(){ cout << "Superman is running\n"; } }; int main(){ Flyable * f1 = new Bird(); f1->fly(); // 出错, 因为没有实现所有的纯虚函数, 所以仍然是抽象类, 无法实例化 Flyable f2 = new Superman(); f2->fly(); // Superman can fly return 0; }
定义纯虚函数的目的在于, 使派生类仅仅只是继承函数的接口。纯虚函数的意义, 让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作, 但类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者, “你必须提供一个纯虚函数的实现, 但我不知道你会怎样实现它”。
new 和 malloc 区别
- 返回类型安全性
new
操作符内存分配成功时, 返回的是对象类型的指针, 类型严格与对象匹配, 无须进行类型转换, 故new
是符合类型安全性的操作符。而malloc
内存分配成功则是返回void *
, 需要通过强制类型转换将void *
指针转换成我们需要的类型。类型安全很大程度上可以等价于内存安全, 类型安全的代码不会试图方法自己没被授权的内存区域。关于
C++
的类型安全性可说的又有很多了 -
细说new与malloc的10点区别
-
C++ 自由存储区是否等价于堆?
STL
- vector 扩容的时候增加 50% 的容量, Java 的 ArrayList 也是如此
- cpp string 字符串拼接,重载了
+
运算符,重新申请len1 + len2
的内存空间,然后把s1, s2
复制进去
前端
JavaScript
-
选择器
// 按ID查找 document.getElementById('xxx'); // 按tagname查找 document.getElementsByTagName('xxx'); // classname document.getElementsByClassName('xxx');
-
jQuery, 选择器是
jQuery
的核心// DOM 操作 var ele = document.getElementById('id1'); var divlist = document.getElementsByTagName('div'); // jQuery 的写法 // 返回一个 jQuery 对象, 即若 id1 存在, 那么返回 [<div id="id1">...</div>] // 若不存在, 则返回 [] var ele = $('#id1'); // jQuery 对象和 dom 对象相互转化 var eledom = ele.get(0); ele = $(eledom); // 按 class 查找 var a = $('.red.green'); // 多个 class 时中间没有空格 // 按属性查找 var email = $('[name=email]'); // 找出<??? name="email"> var passwordInput = $('[type=password]'); // 找出<??? type="password"> var icons = $('[name^=icon]'); // 找出所有name属性值以icon开头的DOM var names = $('[name$=with]'); // 找出所有name属性值以with结尾的DOM // 组合查找 var emailInput = $('input[name=email]'); // 找到所有 input 中的 name="email" 的属性 // 不会找出 <div name="email"> // 多项选择器(不会重复选择) $('p,div'); // 把<p>和<div>都选出来 $('p.red,p.green'); // 把<p class="red">和<p class="green">都选出来
-
正则表达式
`\d` 数字 `\w` 字母或数字 `.` 至少 1 个任意字符 `*` 至少 0 个任意字符 `\d{3}` 匹配 3 个字符 `[0-9a-zA-Z\_]` 匹配一个数字, 字母或者下划线 var re1 = /^\d{11}$/; re1.test('12345678'); // false
-
AJAX, Asynchronous JavaScript and XML, 意思就是用 JavaScript 执行异步网络请求
AJAX请求是异步执行的, 也就是说, 要通过回调函数获得响应
function success(text) { var textarea = document.getElementById('test-response-text'); textarea.value = text; } function fail(code) { var textarea = document.getElementById('test-response-text'); textarea.value = 'Error code: ' + code; } var request = new XMLHttpRequest(); // 新建XMLHttpRequest对象 request.onreadystatechange = function () { // 状态发生变化时, 函数被回调 if (request.readyState === 4) { // 成功完成 // 判断响应结果: if (request.status === 200) { // 成功, 通过responseText拿到响应的文本: return success(request.responseText); } else { // 失败, 根据响应码判断失败原因: return fail(request.status); } } else { // HTTP请求还在继续... } } // 发送请求: request.open('GET', '/api/categories'); request.send();
Python
- 如何管理内存, 垃圾回收
引用计数, del对象
多进程与多线程
-
Unix/Linux
推荐多进程, 提供了一个fork()
系统调用(System call
)pid = fork()
, 调用一次返回两次, 子进程中返回的pid == 0
, 父进程中返回的是子进程的pid > 0
,ppid = getppid()
可以得到父进程的pid
import os # 得到当前进程的 pid print( os.getpid() ) pid = os.fork() if pid == 0: print('This is child process %s, parent is %s' % (os.getpid(), os.getppid())) else: print('This is parent process %s, child is %s' % (os.getpid(), pid))
结果
17420 This is parent process 17420, child is 17421 This is child process 17421, parent is 17420
-
Windows
没有fork()
,Python
多进程需要使用multiprocessing
from multiprocessing import Process import os def proc(name): print('child process %s, pid = %s' % (name, os.getpid())) p = Process( target=proc, args=('test',) ) print('child process start') p.start() p.join() print('child process end')
结果
child process start child process test, pid = 17491 child process end
创建子进程时, 只需要传入待执行函数
target
和函数的参数, 创建一个Process
实例, 用start()
方法启动join()
方法可以等待子进程结束后再继续往下运行, 通常用于进程间的同步。 -
多线程. 进程是由若干线程组成的, 一个进程至少有一个线程, 线程是操作系统直接支持的基本执行单元.
启动一个线程就是把一个函数传入并创建
Thread
实例, 然后调用start()
开始执行import time, threading def loop(): print('%s is running' % threading.current_thread.name) print( '%s is running' % threading.current_thread().name ) t = threading.Thread(target=func, name='myThread') t.start() t.join() print( '%s ended' % threading.current_thread().name )
- 区别: 多进程中, 同一个变量, 各自有一份拷贝存在于每个进程中, 互不影响, 而多线程中, 所有全局变量都由所有线程共享, 所以, 任何一个变量都可以被任何一个线程修改, 因此, 线程之间共享数据最大的危险在于多个线程同时改一个变量, 把内容给改乱了。
多线程共享的内容: 虚拟地址空间,只读代码块,读、写数据, 堆(全局, 静态), 打开的文件集合
-
锁
balance = 0 def change_balance(n): global balance balance += n balance -= n lock = threading.lock() def run_thread(n): for i in range(10000): # 用之前先获得 balance 等全局变量的独占权 lock.acquire() try: change_balance(n) finally: # 用完后释放锁 lock.release()
Java Python 区别
- Java 静态类型语言, 编译的时候就确定了数据类型了, 编写代码的时候要明确变量的数据类型
Python 动态类型语言, 运行期间才检查数据类型
- 语法不一样, Java 用大括号划分代码块, Python 用缩进划分代码块
Java 分号结尾, Python 不需要
数据结构
链表
-
结构定义
struct Node{ public: int val; Node * next; Node(int x) : val(x), next(NULL) {} };
-
插到第 n 个结点前面, n 从 0 开始
Node * insertBefore(Node * head, int n, int val){ if ( head == NULL || n < 0 ) { return head; } Node dummyhead(0); dummyhead.next = head; Node * pre = &dummyhead; Node * now = head; while ( pre != NULL && now != NULL && n > 0 ) { pre = pre->next; now = now->next; n--; } Node * node = new Node(val); pre->next = node; node->next = now; return dummyhead.next; }
-
插到第 n 个结点后面, n 从 0 开始
Node * insertAfter(head, n, val) { return insertBefore(head, n+1, val); }
-
头插法造链表
int main() { int n = 3; Node * head = NULL; while ( n > 0 ) { int val; cin >> val; head = insertBefore(head, 0, val); n--; } return 0; }
-
头插法逆转链表
// 头插法逆转链表 Node * reverse_list(Node * head) { if ( head == NULL || head->next == NULL ) { return head; } Node dummyhead(0); dummyhead.next = head; // now 是准备插入 head 之前的结点 Node * now = head->next; head->next = NULL; while ( now != NULL ) { Node * tmp = now->next; now->next = dummyhead.next; dummyhead.next = now; now = tmp; } return dummyhead.next; } // head = reverse_list(head);
-
尾插法造链表
// 尾插法 void insertTail(Node * &head, Node * &tail, int val) { if ( head == NULL ) { head = new Node(val); tail = head; return ; } else { tail->next = new Node(val); tail = tail->next; return ; } } int main() { int n = 3; while ( n > 0 ) { int val; cin >> val; insertTail(head, tail, val); n--; } return 0; }
-
删除第 n 个结点, n 从 0 开始
Node * deleteAt(Node * head, int n){ if( head == NULL || n < 0 ){ return head; } cout << "delete\n"; Node dummyhead(0); dummyhead.next = head; Node * pre = &dummyhead; Node * now = head; while ( pre != NULL && now != NULL && n > 0 ) { pre = pre->next; now = now->next; n--; } if ( now != NULL ) { pre->next = now->next; delete now; } return dummyhead.next; }
红黑树
- 为什么 map 用红黑树不用 AVL
功能、性能、空间开销的折中结果。
AVL更平衡,结构上更加直观,时间效能针对读取而言更高;维护稍慢,空间开销较大。
红黑树,读取略逊于AVL,维护强于AVL,空间开销与AVL类似,内容极多时略优于AVL,维护优于AVL。
如果插入一个node引起了树的不平衡,AVL和RB-Tree都是最多只需要2次旋转操作,即两者都是O(1);但是在删除node引起树的不平衡时,最坏情况下,AVL需要维护从被删node到root这条路径上所有node的平衡性,因此需要旋转的量级O(logN),而RB-Tree最多只需3次旋转,只需要O(1)的复杂度。
其次,AVL的结构相较RB-Tree来说更为平衡,在插入和删除node更容易引起Tree的unbalance,因此在大量数据需要插入或者删除时,AVL需要rebalance的频率会更高。因此,RB-Tree在需要大量插入和删除node的场景下,效率更高。自然,由于AVL高度平衡,因此AVL的search效率更高。
map的实现只是折衷了两者在search、insert以及delete下的效率。总体来说,RB-tree的统计性能是高于AVL的。
-
红黑树是 BST,且有如下规则
1) 结点是红色或黑色 2) 根是黑色 3) 叶子是黑色结点(叶子是 NULL) 4) 红结点必有两个黑色孩子,也即,从根到叶子路径上不能有连续的红结点 5) 任一结点向下到叶子的所有路径都包含相同数目的黑色节点(黑高度相同)
- 笔记
LCA
-
递归查找LCA
// LeetCode 235, 236 都适用 TreeNode * lowestCommonAncestor(TreeNode * root, TreeNode * p, TreeNode * q){ if( root == NULL || root == p || root == q ){ return root; } TreeNode * left = lowestCommonAncestor(root->left, p, q); TreeNode * right = lowestCommonAncestor(root->right, p, q); if( left != NULL && right != NULL ){ return root; } else{ return left==NULL ? right : left; } }
AVL
- 笔记
算法
排序
- 几个考点: 复杂度, 稳定性, 实现
-
冒泡, 最好 O(n), 最坏 O(n^2)
// 升序 void bubble_sort(int a[], int len){ for(int i=0; i < len; i++ ){ for( int j = len - 1; j > i; j-- ){ if( a[j] < a[j-1] ){ swap(a[j], a[j-1]); } } } } int main(){ const int len = 8; int a[len]={4, 2, 5, 1, 3, 2, 5, 1}; bubble_sort(a, len); for( int i = 0; i < len; i++){ cout << a[i] << '\t'; } return 0; }
-
快排, 最坏 O(n^2), 平均 O(nlgn)
int a[MAXSIZE] = {4, 2, 6, 3, 1, 5, 7}; int partQuickSort(int left, int right){ int i = left, j = right; int key = a[i]; while( i < j ){ while( i < j && a[j] > key ){ j--; } if( i < j ){ // a[j] < key, 换到左边 a[i++] = a[j]; } while( i < j && a[i] < key ){ i++; } if( i < j ){ // a[i] > key, 换到右边 a[j--] = a[i]; } } int mid = i; a[mid] = key; return mid; } /* 以a[left]为中轴key, 把所有小于key的都放到它的左边, 大的放到右边。 中轴的位置就固定了。 再对左边这一块和右边这一块, 做同样的处理。 */ void quickSort( int left, int right ){ if(left < right){ int mid = partQuickSort(left, right); quickSort(left, mid - 1); quickSort(mid + 1, right); } }
-
插入, 最好 O(n), 最坏 O(n^2)
// 升序 void insert_sort(int a[], int len){ for(int i = 1; i < len; i++ ){ int j = i; int key = a[j]; // 把 key 插到正确的位置 while( j > 0 && a[j-1] > key ){ a[j] = a[j-1]; j--; } a[j]=key; } }
- 归并, O(n^2)
查找
-
二分, 序列必须有序
// 递归 int bsearch(int a[], int left, int right, int key){ if( left <= right ){ int mid = (left + right) / 2; if( key < a[mid] ){ return bsearch(a, left, mid - 1, key); } else if( key > a[mid] ){ return bsearch(a, mid + 1, right, key); } else{ return mid; } } return -1; } // 非递归 int bsearch(int a[], int left, int right, int key){ while( left <= right ){ int mid = (left + right) / 2; if( key < a[mid] ){ right = mid - 1; } else if( key > a[mid] ){ left = mid + 1; } else{ return mid; } } return -1; }
字符串匹配
- KMP
- BM
动态规划
- 最大连续子列和
全排列
- next_permutation
其他
-
最大公因数 gcd, 最小公倍数 lcm
// PAT_B_1062, 1034, PAT_A_1081 // 最大公因数, the Greatest Common Divisor int get_gcd(int a, int b){ if( b == 0 ){ return a; } else{ return get_gcd(b, a % b); } } // 最小公倍数, the Lowest Common Multiple int get_lcm(int a, int b){ int gcd = get_gcd(a, b); if( gcd != 0 ){ return a * b / gcd; } else{ return 0; } }
- 素数
- Fib
数据库
-
数据库事务(Transaction)的 ACID 特性
原子性(Atomicity), 一致性(Consistency), 隔离型(Isolation), 持久性(Durability)
- 原子性(A)是指事务中的操作不可拆分, 只允许全部执行或者全部不执行
- 一致性(C)指事务的执行不能破坏数据库的一致性, 一致性也称为完整性。一个事务在执行后, 数据库必须从一个一致性状态转变为另一个一致性状态
- 隔离性(I)指并发的事务相互隔离, 不能互相干扰
- 持久性(D)指事务一旦提交, 对数据的状态变更应该被永久保存
-
数据库事务隔离级别有4个
# 由低到高依次为 Read uncommitted, 读到了未提交的事物, 只是 add 还没有 commit Read committed, 读到了上一次的commit, 也就是说还没有更新 最新的commit Repeatable read, 保证读取最新的 commit, 为此, 读取的时候不允许提交 Serializable, 要求有很高的实时同步性 # 这四个级别可以逐个解决脏读 、不可重复读 、幻读 这几类问题
- 锁(并发控制的手段)
- 关系数据模型的三个组成部分
数据结构, 对数据的操作, 完整性约束
- 参考链接
SQL
- CRUD
Create, Read, Update, and Delete
-
过程
1.首先连接到数据库 conn = sqlite.connect('test.db') 2.建立游标 cursor cursor = conn.cursor() 3.建立表 create table user (id int, name varchar(20), score int ) 4.insert into 插入数据, 注意占位符是 ? insert into user (id, name) values(1, "Alice", 88) insert into user (id, name) values(2, "Bob", 89) insert into user (id, name) values(3, "Cindy", 88) 5.select * from user where 查找数据 select * from user where id between 1 and 3 order by score asc 6.cursor.close() 7.conn.commit() 8.conn.close()
-
column, 列, 字段
select * from user select col1, col2 from user
-
下面以这个表格
customers
为例, 展示 SQL 的语法id name age city postalcode country 1 Alfreds 25 Berlin 12209 Germany 2 Ana 15 Mexico 05021 Mexico 3 Antonio 20 Mexico 05023 Mexico 4 Thomas 30 London WA11DP UK 5 Berglunds 35 Lulea S-958-22 Sweden -
where 条件选择
select * from customers where country='Germany' and city='Berlin'
-
order by 排序
select * from customers order by country
-
插入
insert into customers (name, age, city, postalcode, country) values ('Bart', 10, 'LA', '4006', 'USA')
-
更新
update customers set name = 'Smith', city = 'Paris' where id = 5
-
删除
delete from customers where name = 'Berglunds'
-
limit/top 不返回全部结果, 只返回有限数量的记录
# SQL select top 3 from customers # MySQL select * from customers limit 3
-
最大最小值
select min(age) from customers
-
统计 count, distinct
# 统计有多少个不同的国家 select count(distinct country) from customers
-
平均值
select avg(age) from customers
-
求和
select sum(age) from customers
-
通配符
# SQL select * from customers where name like 'B%' # sqlite select * from customers where name like 'B*'
-
选择范围
# 离散 select * from customers where country in ('Germany', 'France') # 连续 select * from customers where age between 10 and 20
-
连接表格
inner join, (A ∩ B) left join, (A ∩ B) U A right join, (A ∩ B) U B full outer join, (A U B)
- 参考资料
计算机网络
HTTP 状态码
-
2xx: Successful responses
- 200 OK
- 200 OK
-
3xx: Redirection messages
- 301 Moved Permanently, 永久转移
This response code means that the URI of the requested resource has been changed permanently. Probably, the new URI would be given in the response.
- 307 Internal Redicrection, 浏览器自动重定向, HSTS 会用到
- 301 Moved Permanently, 永久转移
-
4xx: Client error responses
- 400 Bad Request, 非法请求
This response means that server could not understand the request due to invalid syntax.
- 403 Forbidden, 未认证, 权限不够
The client does not have access rights to the content, i.e. they are unauthorized, so server is rejecting to give proper response. Unlike 401, the client's identity is known to the server.
- 404 Not Found, 没有相应的资源, 路径不对
The server can not find requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 to hide the existence of a resource from an unauthorized client. This response code is probably the most famous one due to its frequent occurence on the web.
- 405 Method Not Allowed, 非法的请求方式
The request method is known by the server but has been disabled and cannot be used. For example, an API may forbid DELETE-ing a resource. The two mandatory methods, GET and HEAD, must never be disabled and should not return this error code.
- 400 Bad Request, 非法请求
-
5xx: Server error responses
- 500 Internal Server Error, 服务器内部错误
The server has encountered a situation it doesn't know how to handle.
- 501 Not Implemented, 请求方法未实现
The request method is not supported by the server and cannot be handled. The only methods that servers are required to support (and therefore that must not return this code) are GET and HEAD.
- 500 Internal Server Error, 服务器内部错误
- 参考资料
https://developer.mozilla.org...
GET 和 POST 区别
- 幂等
GET为请求数据, 没有副作用, 每次请求的效果都相同。
POST为修改数据, 相对GET来讲没有那么安全(因为要修改数据)。
- 安全性
GET 通过 URL 传输数据, 容易在日志记录中留下私密数据, 隐蔽性没有POST好。
- 长度限制。
一般通过GET方式传输数据, 由于Web Server和浏览器限制, URL长度限制传输的数据有限(根据Web Server配置和浏览器不同, 一般为2kB~8kB)
而POST通常可以忽略限制(实际上根据Web Server配置也还是有限制的, 一般是2G)
http://stackoverflow.com/ques...
TCP 连接
- 三次握手
-
过程
- 服务器 B 处于 LISTEN 状态, 监听端口, 等待请求
- 客户端 A 的 TCP 进程首先创建 TCB (传输控制块, 存储了每一个连接中的重要信息, srcPort, dstPort, srcIP, destIP, seq, ack)。 然后向 B 发出连接请求报文段(传输层是段, 网络层是包, 链路层是帧)。 此时 【 SYN=1, seq=x 】, 不携带数据, 但是消耗一个序号。接着进入 SYN-SENT状态。
- B 收到连接请求后, 如果同意连接, 则向A发送确认(ACK), 此时 【 SYN=1, ACK=1, ack=x+1, seq=y 】, ACK是确认标志, ack是确认号。这个报文段不带数据, 但是消耗一个序号。服务器进入 SYN-RCVD 状态
- A 收到 B 的确认后, 还要向 B 确认自己收到了。此时 【 ACK=1, ack=y+1, seq=x+1 】, 这个ACK报文段可以带数据。接着 A 进入ESTABLISHED 状态。
- B 收到 A 的确认后, 也进入 ESTABLISHED 状态。
<br>
- 为什么三次握手, 而不是两次?也就是说,
B
在第一次收到A
的SYN
之后, 没有立刻进入ESTABLISHED
, 而是等到A
再来一次ACK
才建立连接, 为什么要这样设计?答:防止已经失效的连接请求(比如在某个网络滞留了很久), 突然又传到了
B
, 从而产生错误, 造成B
的资源浪费.考虑这样一种情况:
A
的第一次SYN
被拦截了。然后A
又发了一次SYN
,B
收到, 并且完成后续的握手 --- 数据传输 --- 挥手
。此时
A
的第一次SYN
又被放了出来, 并传到B
, 如果只需要2
次握手, 那么此时B
就会直接进入ESTABLISHED
而这个时候
A
都已经关机了, 不会发数据过来了,B
就在ESTABLISHED
中苦苦等待A
, 浪费很多资源。如果是3次握手, 那么
B
只会进入SYN-RCVD
状态, 一段时间后 (比ESTABLISHED
要短, 而且不会占用那么多资源), 会自动取消连接 - SYN Flood
SYN Flood
是一种广为人知的DoS
(拒绝服务攻击) , 是DDoS
(分布式拒绝服务攻击) 的方式之一, 这是一种利用TCP
协议缺陷, 发送大量伪造的TCP
连接请求, 从而使得被攻击方资源耗尽 (CPU
满负荷或内存不足) 的攻击方式。假设一个用户向服务器发送了
SYN
报文后突然死机或掉线, 那么服务器在发出SYN+ACK
应答报文后是无法收到客户端的ACK
报文的 (第三次握手无法完成) ,这种情况下服务器端一般会重试 (再次发送
SYN+ACK
给客户端) 并等待一段时间后丢弃这个未完成的连接, 这段时间的长度我们称为SYN Timeout
, 一般来说这个时间是分钟的数量级 (大约为30秒 - 2分钟).一个用户出现异常导致服务器的一个线程等待1分钟并不是什么很大的问题, 但是, 如果有一个恶意的攻击者大量模拟伪造这种情况, 服务器端将会维护一个非常大的半连接列表, 从而消耗非常多的资源
数以万计的半连接, 即使是简单的保存并遍历也会消耗非常多的
CPU
时间和内存, 何况还要不断对这个列表中的IP
进行SYN+ACK
的重试实际上如果服务器的
TCP/IP
栈不够强大, 最后的结果往往是堆栈溢出崩溃,即使服务器端的系统足够强大, 服务器端也将忙于处理攻击者伪造的
TCP
连接请求而无暇理睬客户的正常请求 (毕竟客户端的正常请求比率非常之小) , 此时从正常客户的角度看来, 服务器失去响应, 无法对正常客户进行服务这就是
SYN Flood
攻击防御措施: 缩短
SYN Timeout
, 增加IP
黑名单, 使用CDN
TCP 断开
- 断开, 4次挥手 (4-way handshake, connection termination)
- MSL, Maximum Segment Lifetime, 最长报文段寿命
A
发出的最后一个ACK
可能会丢失. 而此时B
还在等待这个ACK
, 如果B
一直没有收到, 则会给A
发一个FIN + ACK
, 通知A
刚才那个没收到, 从A
发出ACK
到B
发出FIN + ACK
需要2MSL
, 如果2MSL
时间里面A
都没有收到B
的回复, 就认为自己的ACK
已经送达B
, 所以A
可以安心关闭了而且,
2MSL
后, 可以使本次连接所产生的所有报文段都从网络上消失 (TTL, Time To Live
), 以后建立新的连接, 就不会和旧的连接混淆
趋势科技
安卓
- 网络请求具体过程, 步骤, 代码
- 用了异步吗, 为什么(网络慢的时候不会卡界面)
- MainActivity, 点击, 进入新的 Activity 这中间发生了什么, 怎么传递的消息?
Intent
Python
- 为什么选 Flask, 什么叫做轻?其他框架有看过吗?
- 为什么要做这个网站?
- 把 vue 和 echarts 放到 html-body 前面
网络和安全
- https 有什么作用, https具体连接过程是怎么样的
交互图
-
http://www.ruanyifeng.com/blo...
1). Client 给出协议版本号、一个的随机数(Client random),以及客户端支持的加密方法。 2). Server 确认双方使用的加密方法,并给出数字证书,以及一个服务器生成的随机数 3). Client 确认数字证书有效,然后生成一个新的随机数(Premaster secret), 并使用数字证书中的公钥,加密 Premaster secret, 发给 Server 4). Server 使用自己的私钥,获取爱丽丝发来的随机数 Premaster secret 5). Client 和 Server 根据约定的加密方法,使用前面的三个随机数,生成"对话密钥"(session key),用来加密接下来的整个对话过程。 生成对话密钥一共需要三个随机数。 握手之后的对话使用"对话密钥"加密(对称加密),服务器的公钥和私钥只用于加密和解密"对话密钥"(非对称加密),无其他作用。 服务器公钥放在服务器的数字证书之中。
- 用 let's encrypt 的 https 证书需要注意哪些事情?有什么限制吗?为什么设置 60 天的有效期, 过期了要重新延长?证书有效期是什么概念?有效期过了会导致什么问题?
最重要的原因在于吊销。当网站的私钥丢失时,网站应该向证书颁发机构(CA)申请将他们的证书加入到证书吊销列表(CRL)里。当用户访问https站点时,浏览器会自动向CA请求吊销列表,如果用户访问的站点提供的证书在CRL里,浏览器就不信任这个证书,因为攻击者可能拥有同样的证书。所以如果证书永久有效,随着越来越多的私钥丢失,吊销列表也越来越大(因为只有加进去的,没有剔出去的),这既给CA增加流量压力,也会增加浏览器的流量。而一旦有效期只有几年,那么CA就可以将那些已经过期了的证书从CRL里剔除,因为反正浏览器也不信任过期证书。
- 输入 URL 到页面展示, 中间发生了什么?授权解析服务器
https://segmentfault.com/a/11...
- 拿到端口号后, 谁跟谁之间建立了什么连接?
- 有哪些 request 的方法?GET, POST, PUT, DELETE 还有吗?RESTful API?
- GET 和 POST 有什么区别?
- GET对URL的限制是哪里限制的?(Server 和 浏览器 都有限制)
- HTTP headers的作用, 比如 Connection: keep-alive 是干什么用的?重用是什么概念, 是谁的重用?
客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭, 如果客户端再次访问这个服务器上的网页, 会继续使用这一条已经建立的连接
- HSTS?
JavaScript, 前端
-
javascript, setTimeout() 的用法
console.log(1) for( let i = 0; i < 5; i++ ){ setTimeout(function(){console.log(2);}, 1000); } console.log(3);
输出是什么(1 3 2 2 2 2 2)
异步, 不会等
https://www.liaoxuefeng.com/w...
- 前端怎么向后端发起请求
axios, jQuery, AJAX 是怎么写的
建议
- 基础要扎实
体系结构, 操作系统, 网络, 语言, 数据结构算法, 框架, 应用
- 集中, 专攻, 而且要体现在简历上, 让面试官知道问什么
如果要做 Java 就专攻 Java, SSM/SSH 实践好, 理解好
- Python 如果要做, 也要深入, 不要皮毛
其他
- 牛客网面试经历 (视频面试)
- 项目经历
- 自我介绍(中英文)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。