java基础知识

gzm keep patient


java和编译型语言区别

此处输入图片的描述
编译型语言直接编译变为可执行程序exe,然后就可以执行了。但是java需要先编译为.class字節碼,在由解釋器進行解釋執行。可以說java是介於編譯型語言和解釋型語言之間的一鍾語言了。所以網上一直有種說法:最好不要使用編譯型語言和解釋型語言的劃分方法來劃分java,因爲它既有編譯,也有解釋。

image_1cer6l3dg1ma768a1egjb8d7671g.png-2.5kB


各种类型变量存储位置

局部变量 -> 栈
动态变量 -> 堆
全局变量 静态变量 -> 静态存储区
函数代码 -> 代码区
常量 -> 常量区


java支持的变量类型

类中的变量类型

  • 类变量 独立于方法之外的变量 static修饰 每个类共享
  • 实例变量 独立于方法之外 但是没有static修饰 每个实例都有自己的一个这个变量的版本 实例变量具有默认值。数值型变量的默认值是0,布尔型变量的默认值是false,引用类型变量的默认值是null 变量的值可以在声明时指定,也可以在构造方法中指定
  • 局部变量 类方法中的变量 访问修饰符可以修饰实例变量

局部变量

  • 声明在方法、构造方法或者语句块中
  • 访问修饰符不能用于局部变
  • 局部变量是没有默认值 所有被声明之后需要初始化之后才可以使用

final修饰变量

final 变量能被显式地初始化并且只能初始化一次。被声明为 final 的对象的引用不能指向不同的对象。但是 final 对象里的数据可以被改变。也就是说 final 对象的引用不能改变,但是里面的值可以改变

final修饰类中的方法
类中的 final 方法可以被子类继承,但是不能被子类修改。

final修饰类
final修饰的类不能被继承 没有类能继承final类的任何特性


abstract修饰符

abstract抽象类是不能用来实例化对象的 声明抽象类的唯一目的就是将来对该类进行扩充

如果一个类中具有抽象方法 那么这个类一定要声明微抽象类 抽象方法的的定义由子类提供

任何继承抽象类的子类必须实现父类的所有抽象方法,除非该子类也是抽象类。

abstract抽象类例子
Test.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class Mon_class {
public abstract void m();
}

class Son_class extends Mon_class {
@Override
public void m() {
System.out.println("this is a m func");
}
}

public class Test {
public static void main(String []args) {
Son_class test_class = new Son_class();
test_class.m();
}
}

可以使用非抽象子类的构造函数创建一个抽象父类的引用对象 并且可以调用具体定义在非抽象子类中的重写方法
但是注意非抽象类的构造函数并不可以用来创建一个实例


synchronized 修饰符

synchronized 关键字声明的方法同一时间只能被一个线程访问。synchronized 修饰符可以应用于四个访问修饰符。


transient 修饰符

序列化的对象包含被 transient修饰的实例变量时,java虚拟机(JVM)跳过该特定的变量。
该修饰符包含在定义变量的语句中,用来预处理类和变量的数据类型。


volatile 修饰符

volatile 修饰的成员变量在每次被线程访问时,都强制从共享内存中重新读取该成员变量的值。而且,当成员变量发生变化时,会强制线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。

一个 volatile 对象引用可能是 null。


extends和implements

  1. 在类的声明中,通过关键字extends来创建一个类的子类。一个类通过关键字implements声明自己使用一个或者多个接口。
    extends 是继承某个类, 继承之后可以使用父类的方法, 也可以重写父类的方法; implements 是实现多个接口, 接口的方法一般为空的, 必须重写才能使用
    2.extends是继承父类,只要那个类不是声明为final或者那个类定义为abstract的就能继承,JAVA中不支持多重继承,但是可以用接口 来实现,这样就要用到implements,继承只能继承一个类,但implements可以实现多个接口,用逗号分开就行了

instanceof运算符

1
2
3
4
5
6
public class Test {
public static void main(String []args) {
String name = "gzm";
System.out.println(name instanceof String);
}
}

装箱和拆箱

在java中的内置数据类型对应的包装类都是继承一个叫做Number的抽象类的,关系图如下
image_1cetpi31q1ldo12bi1f1l1i841aru9.png-43.3kB

  • 当内置数据类型被当作对象使用的时候 编译器会吧内置类型装箱
  • 反过来 编译器也会一个包装类拆箱

比如下面是整型包装类和内置类型之间的装箱和拆箱之间的关系
image_1cetputrjfl1t58lp7o0g1a8m.png-3kB

例子

1
2
3
4
5
6
7
8
9
public class Test {
public static void main(String []args) {
//装箱
Integer num = 5;
//拆箱
num += 10;
System.out.println("num " + num);
}
}


Charactor

char这个内置数据类型对应的包装类是Charactor


String和char array

String和char array的相互转换以及遍历
String转为char array

1
2
String t_str = "abcd";
char [] c_arr = t_str.toCharArray();

char array转为String

1
String new_str = new String(c_arr);

foreach只可以用来迭代可迭代对象 String并不是可迭代对象 只有char array是可迭代对象

1
2
3
4
5
6
7
8
for (char c: c_arr
) {
System.out.println(c);
}
for (char c: new char[]{'1', '2', '3'}
) {
System.out.println(c);
}

String类是不可该改变的 即使具有concat那些字符串连接方法 但是那也是新建了一个新的String对象 如果需要改变字符串 那么就需要使用StringBuffer和StringBuilder对象了

  • String 不可修改 速度最慢
  • StringBuffer 可修改 速度中等 线程安全
  • StringBuilder 可修改 速度最快 非线程安全

StringBuffer基本使用
(注意在insert和replace这些函数中 start end的范围是跟python是相同 都是包含start但是没有包含end)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {
public static void main (String []args)
{
StringBuffer test_buffer = new StringBuffer("lala");
test_buffer.append("heihei");
System.out.println(test_buffer);
test_buffer.reverse();
System.out.println(test_buffer);
test_buffer.delete(test_buffer.length() - 3, test_buffer.length());
System.out.println(test_buffer);
test_buffer.replace(1, 2, "heihei");
System.out.println(test_buffer);
}
}


StringBuilder

如果在StringBilder的构造函数中没有传入一个长度参数,那么这个构造函数将会为这个StringBuilder分配默认的16个字符的空间


什么是内存抖动Memory Churn
内存抖动是因为大量对象被创建又在段时间内马上被释放 内存页被频繁更换 导致整个系统效率急剧下降 这个现象称为内存抖动 一般都是因为内存分配算法不好 内存太小造成的
其实内存抖动就是系统颠簸


正则表达式

基本使用

1
2
3
String content = "i am haha";
String patern = ".*haha";
System.out.println(Pattern.matches(patern, content));

常用的正则表达

  • .表示任意一个字符
  • \s+表示若干个空格(应该是1个或者多个)
  • \d+表示一个或者多个数字
  • \.表示.
  • ()?表示问好前面中的括号的内容是可选的
  • {n} n为非负数 表示前面的字符刚好重复n次
  • {n,} n为非负数 表示前面的字符至少重复n次
  • {n,m} n为非负数 表示前面的字符出现次数至少n次 之多m次
  • x|y 匹配x或者y
  • [xyz]匹配xyz中任一字符
  • [^xyz] 跟上面的相反 匹配不是xyz的字符
  • [a-z] 字符范围 匹配a-z之间的字符
  • [^a-z]反向范围
  • \b匹配一个字符边界(字符和空格间的位置) 例如,”er\b”匹配”never”中的”er”,但不匹配”verb”中的”er”。
  • \B匹配一个非字符边界 “er\B”匹配”verb”中的”er”,但不匹配”never”中的”er”。

java可变参数

从jdk1.5开始 java支持传递同类型的可变参数 这个跟声明参数为数组类型的不同的地方在于可以传入多个同类型的参数

声明方式

typeName... parameterName

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Test {
public static void testPrint(int... arr) {
if (arr.length == 0) {
System.out.println("no parameter has been transfer");
}
for (int i: arr
) {
System.out.println(i);
}
}
public static void main(String []args) {
testPrint(1, 2, 3, 4);
testPrint(new int[] {3, 2, 1});
}
}


收尾机制finalize

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

class Dog extends Object {
int id;
Dog(int _id) {
id = _id;
System.out.printf("id %d dog is create\n", id);
}
protected void finalize() throws java.lang.Throwable {
super.finalize();
System.out.printf("id %d dog is dispose\n", id);
}
}

public class Test {
public static void main(String []args) {
Dog d1 = new Dog(1);
Dog d2 = new Dog(2);
//Dog d3 = new Dog(3);
//d1 = null;
//System.gc();
d1 = d2 = null;
System.gc();
}
}


java io

从控制台中输入一个字符

1
2
3
4
5
6
7
8
9
10
11
12
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Test {
public static void main(String []args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int r = br.read();
char c = (char) r;
System.out.println(c);
}
}

在控制台中输入一个字符串 也是使用Bufferreader这个对象进行读取 但是是使用readline这个函数进行读取

1
2
3
4
5
6
7
8
9
10
11
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Test {
public static void main(String []args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String r = br.readLine();
System.out.println(r);
}
}

可以解决中文输入输出出现乱码都解决方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.io.*;

public class Test{
public static void main(String[] args) throws IOException {

File f = new File("a.txt");
FileOutputStream fop = new FileOutputStream(f);
// 构建FileOutputStream对象,文件不存在会自动新建

OutputStreamWriter writer = new OutputStreamWriter(fop, "UTF-8");
// 构建OutputStreamWriter对象,参数可以指定编码,默认为操作系统默认编码,windows上是gbk

writer.append("中文输入");
// 写入到缓冲区

writer.append("\r\n");
//换行

writer.append("English");
// 刷新缓存冲,写入到文件,如果下面已经没有写入的内容了,直接close也会写入

writer.close();
//关闭写入流,同时会把缓冲区内容写入文件,所以上面的注释掉

fop.close();
// 关闭输出流,释放系统资源

FileInputStream fip = new FileInputStream(f);
// 构建FileInputStream对象

InputStreamReader reader = new InputStreamReader(fip, "UTF-8");
// 构建InputStreamReader对象,编码与写入相同

StringBuffer sb = new StringBuffer();
while (reader.ready()) {
sb.append((char) reader.read());
// 转成char加到StringBuffer对象中
}
System.out.println(sb.toString());
reader.close();
// 关闭读取流

fip.close();
// 关闭输入流,释放系统资源

}
}


Scanner

Scanner是java5的新特征 可以通过Scanner类来获取输入的字符串或者数字 字符

下面是获取输入字符串的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.io.*;
import java.util.Scanner;

public class Test {
public static void main(String []args) throws IOException {
Scanner s = new Scanner(System.in);
if (s.hasNext()) {
String r = s.next();
System.out.println(r);
}

Scanner s1 = new Scanner(System.in);
if (s1.hasNextLine()) {
String r = s1.nextLine();
System.out.println(r);
}
}
}
`

next和nextLine的区别
next

  1. 一定要读取到有效字符后才可以结束输入。
  2. 对输入有效字符之前遇到的空白,next()方法会自动将其去掉。
  3. 只有输入有效字符后才将其后面输入的空白作为分隔符或者结束符。
  4. next() 不能得到带有空格的字符串。

nextLine

  1. 以Enter为结束符,也就是说 nextLine()方法返回的是输入回车之前的所有字符。
  2. 可以获得空白。

异常

image_1cf08i4nf1ifd1qtm12hfuoa1lu9.png-24.4kB


继承和接口

java的继承是单继承 就是说一个子类只可以有一个父类 同时java是支持多重继承的 就是说c可以继承b b继承a 这就是多重继承

但是java也是可以通过interface来实现多重继承的
例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
interface A{
public void eat();
}

interface B{
public void sleep();
}

class A_test implements A, B {
@Override
public void eat() {
System.out.println("this is eat func");
}

@Override
public void sleep() {
System.out.println("this is sleep func");
}
}


public class Test {
public static void main(String []args) {
A_test test_a = new A_test();
test_a.eat();
test_a.sleep();
}
}

值得一提的是 接口也可以初始化实例 并且这些接口初始化出来的实例也可以调用它们的方法 即使这些方法的定义在implements了它们的class中


构造函数

java的子类是不能继承夫类的构造函数的 但是当父类的构造函数含有参数时候 必须在子类的构造函数中使用super关键字调用父类构造函数 并且传入参数
但是当父类的构造函数没有参数 子类不是必须super调用父类构造函数的 因为系统会自动调用父类的无参构造函数


继承中的引用对象

可以使用

  • 子类的构造函数构造父类的引用对象
  • 不可以使用父类的构造函数构造子类的引用对象

如果使用的是子类的构造函数来构造父类的引用对象 那么这个对象调用的函数版本是子类中重写的版本


override和overload

重写

  • 重写是在子类和父类之间的
  • 重写的参数列表和返回类型必须相同
  • 声明微final的方法不可以被重写
  • 构造方法不可以被重写
  • static方法不可以被重写 但是可以被重新声明

子类和父类都具有相同的static的的同名 同参数列表 同返回类型的函数 但是这不是重写 static函数不可以被重写 这是重新声明

static函数被重新声明的时候
无论父类的引用对象是被父类构造函数还是子类构造函数构造的 它调用的版本都是父类的static函数版本

子类的引用对象调用的版本是自己重新声明的static版本

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.awt.*;

class Animal {
public void move() {
System.out.println("animal is move");
}
public static void testStatic() {
System.out.println("this is animal static func");
}
}

class Dog extends Animal {
@Override
public void move() {
System.out.println("Dog is move");
}
public static void testStatic() {
System.out.println("this is a Dog static funcss");
}
}

public class Test {
public static void main(String[] args) {
Animal a = new Animal();
Animal b = new Dog();
a.testStatic();
b.testStatic();
Dog c = new Dog();
c.testStatic();
}
}

输出
image_1cf49j0l51m6jpbfaqp4h6f219.png-8.3kB

重载

  • 在通一个class中
  • 返回类型 函数名 参数列表相同

多态

多态是建立在上面的方法重写之上的 被重写的方法称为虚方法

多态的编译和运行

使用子类引用子类的对象

  • 在编译的时候编译器会在子类中找到相应的方法 然后执行的时候JVM会调用子类的这个方法

使用父类引用子类的对象

  • 在编译时 编译器会使用父类的虚方法进行验证 但是在运行时 JVM调用的是子类的对应的虚方法

java的所有方法都按照这种方式来实现 因此重写方法能在运行时调用 不敢编译的时候源代码中引用变量是什么数据类型

多态的实现方式

  • 重写
  • 接口

我一直以为重写是实现多态的唯一方式 但是java里面接口也是实现多态的一种方式 比如上面的多态内容中 我不是发现使用implements了接口的类构造函数类来创建一个interface引用对象 并且可以调用实现方法在这个class中的方法 所以这也是实现多态的一种方式

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.awt.*;

interface A {
public void move();
}

class Dog implements A {
@Override
public void move() {
System.out.println("this is move func in Dog");
}
}

public class Test {
public static void main(String[] args) {
A testA = new Dog();
testA.move();

}
}

注意 interface没有构造函数 因为根本不能用以实例化一个对象


接口

interface特性

  • 每个接口的方法都隐士指定为public abstract 其他修饰符都会报错
  • 接口中可以有变量到 都会被隐士指定为public static final 使用private修饰编译会报错
  • 接口的方法不能在接口中实现 只能由实现接口的类来实现
  • 接口可以继承 这个跟类的继承相似 但是类只支持多重继承 不支持多继承 接口的继承支持多继承也支持多重继承

标记接口

最常用的继承接口是没有包含任何方法的接口和属性的 这个叫做标记接口 主要是表明它的类属于一个特定的类型

目的

  • 建立一个公共的父接口 正如EventListener接口,这是由几十个其他接口扩展的Java API,你可以使用一个标记接口来建立一组接口的父接口。例如:当一个接口继承了EventListener接口,Java虚拟机(JVM)就知道该接口将要被用于一个事件的代理方案
  • 向一个类添加数据类型 这种情况是标记接口最初的目的,实现标记接口的类不需要定义任何接口方法(因为标记接口根本就没有方法),但是该类通过多态性变成一个接口类型

同一个包内的类命名是不可以相同的 但是不同包内的命名是可以相同的

包例子:
结构如下

  • animal
    • Animal.java(Animal interface)
    • MammalInt.java(MammalInt class implements Animal)
  • Test.java(import animal.MammalInt)

Animal.java

1
2
3
4
5
package animal;

interface Animal {
public void eat();
}

MammalInt.jav

1
2
3
4
5
6
7
8
9
10
11
12
package animal;

public class MammalInt implements Animal {
@Override
public void eat() {
System.out.println("this is is animal eat func");
}
public static void main(String[] args) {
MammalInt testM = new MammalInt();
testM.eat();
}
}

Test.java

1
2
3
4
5
6
7
8
9
10
11
import animal.MammalInt;



public class Test {
public static void main(String[] args) {
MammalInt testM = new MammalInt();
testM.eat();
System.out.println("this is test java file");
}
}

运行Test.java
image_1cf4lkjqb6vtuc0f5ja2nc0kt.png-7.7kB

python包调用问题

前言

以前其实一直不是很清楚包调用的具体情况,觉得可以调用就可以了,但是这次系分大作业上我想把结构设计得更好看一点,这时候包调用就出现了一些问题


嵌套调用

嵌套调用没什么特别的,就是在包中调用这个包里面的一个包,有结构如下

  • out_module
    • __init__.py
    • out.py
    • in_module
      • __init__.py
      • in.py
  • test.py

在out_module这个包中的out.py中调用in_module的in.py只需要在out.py中

1
from out_module.in_module import in


平行调用

平行调用就是两个包在路径上市并列的,从一个包中调用另一个包中的模块或者函数,如下就是在m1.py中调用m2.py或者m2.py中的函数或者变量

  • module1
    • __init__.py
    • m1.py
  • module2
    • __init__.py
    • m2.py

谷歌搜到的方法是往sys.path中添加被调用包处在的路径,类似如下

1
2
import sys
sys.path.append("G:\\大三下\\test\\test_module")

这样可以解决问题,但是代码看起来不是很好看,可移植性也不高,毕竟如果需要部署的话也要修改这个路径值。所以我给出了一个稍微好一点的几角方法

1
2
3
4
5
6
import sys
import pathlib
BASE_DIR = pathlib.Path(__file__).parent.parent
models_path = BASE_DIR / "module2"
sys.path.append(str(models_path))
import m2

注意pathlib.Path(__file__).parent.parent返回值是一个WindowsPath对象实例,如果是往sys.path中直接添加是没有效果的,所以要用str转为字符串。

使用异步ORM aiomysql.sa

前言

因为系统分析与设计大作业的web框架我选择了使用aiohttp这个异步框架,所以ORM的选择上要做出改变。sqlalchemy是不支持异步IO的,所以用sqlalchemy搭配aiohttp并不可行,最终我选择了整合了sqlalchemy的aiomysql.sa,这是一个支持mysql的异步ORM。

相关的学习链接
官方文档
用法参考

从官方文档上我们可以看到

image_1ced39bc8n731hlgijuectil9.png-12.2kB

aiomysql的api接口跟aiopg是非常类似的,可以在学习中参考一下aiopg,同时aiomysql.sa是整合了sqlalchemy的,也可以参考sqlalchemy的文档。为什么我要这么说呢,因为aiomysql.sa官方提供的文档描述相当有限,在学习过程中必须参考少之又少的资料,并且这些资料可能因为版本问题还不一定是对的。


基本用法

python的异步io很多都是基于python的协程实现的,asyncio就是,同时aiomysql.sa也是底层由协程实现,所以我们是使用aiomysql.sa过程中,都需要定义各种操作为协程

1.初始化引擎

初始化引擎跟mysql.connector很类似,但是返回一个engine实例
image_1ced3mktqg6t1hr35mg1p1qed0m.png-28.8kB
例子

1
2
3
async def init_engine():
engine = await aiomysql.sa.create_engine(user = config["user"], db = config["database"], host = config["host"], password = config["password"])
return engine

2.引擎获取与事务
这部分我看了下文档和github上的用法参考,感觉都不怎么正确,可能因为版本问题,自己总结了如下

1
2
3
4
async with engine.acquire() as conn:
trans = await conn.begin()
await conn.execute(food.insert().values(name = "test_food"))
await trans.commit()

引擎资源的获取需要engine.acquire()来获取,返回值为一个SAconnection实例,可以进行execute(query, *multiparams, **params)操作。

最好在with语句里面使用acquire,这样可以自动释放engine资源。

aiomysql.sa支持事务,它无论是insert delete还是select都需要事务的提交,没错你没看错,select它都必须你提交事务,不然会报错

1
Failed to release a connection with transaction started at 'aiomysql'

同时这个报错有点意思,如果你没有主动提交事务,它这个报错一定会出现的,但是如果你之前的增删查着操作出现了错误,它会导致整个事务失败,最终这个事务没办法成功提交,也会报这个错误。也就是说如果出现这个错误,并一定就是你事务没有主动提交,而是有可能你的增删查着操作出现了错误导致事务的失败。

事务的获取和提交,事务的获取可以通过SAconnection.begin()获取这个事务

1
trans = await conn.begin()

事务提交

1
await trans.commit()

嵌套事务
文档中这样描述嵌套事务

Nested calls to begin() on the same SAConnection will return new Transaction objects that represent an emulated transaction within the scope of the enclosing transaction

在同一个SAconnection中嵌套调用begin的话,会在外层事务中间返回一个模仿的事务对象

嵌套事务的特点

Calls to Transaction.commit() only have an effect when invoked via the outermost Transaction object, though the Transaction.rollback() method of any of the Transaction objects will roll back the transaction

只有当最外层的事务被调用,那么事务才会被提交,例子如下

1
2
3
4
trans = yield from conn.begin()   # outermost transaction
trans2 = yield from conn.begin() # "inner"
yield from trans2.commit() # does nothing
yield from trans.commit() # actually commits

最后说一数据结构的声明,因为在sqlalchemy中我们一般比较习惯使用class User(Base)这样的方式来定义我们的数据库表结构,因为这符合我们面向对象编程的习惯,同时定义了这些class之后我们可以在稍后的增删查着操作中使用这些class。但是在aiomysql.sa中这样做并不好。官方文档中这样解释
image_1ced511agkr510uc104oevv1qr013.png-59.7kB

就是说Question.query.filter_by(question_text=’Why’).first()或者session.query(TableName).all()之类的查询并不支持异步,所以把表结构定义为class并没有什么帮助,正确的做法是定义为Table对象。

关于class和Table对象的关系我之前一篇博客有说过,就是创建一个class就会自动生成一个同名的Table对象,并且使用一个Mapper对象将这两个对象映射在一起。

所以我们在aiomysql.sa中定义我们的数据库表结构大多形如下面

1
2
3
4
5
6
7
8
9
10
11
12
13
food = sa.Table(
"food",
meta,
sa.Column("id", sa.Integer, primary_key = True),
sa.Column("name", sa.String(50), unique = True, nullable = False),
sa.Column("picture", sa.String(50)),
sa.Column("price", sa.Integer, nullable = False),
sa.Column("description", sa.String(50)),
sa.Column("rating", sa.Float),
sa.Column("amount", sa.Integer, nullable = False),
sa.Column("likes", sa.Integer, default = 0),
sa.Column("tag_id", sa.Integer, sa.ForeignKey("tag.id"), nullable = False)
)

基本用法基本就是上面所说的了,具体可以参考一下我github上系统分析与设计的web后台

aiohttp基本使用

前言

这次系统分析与设计大作业我负责web后端开发工作,本来又想使用flask框架的,但是想想flask框架都有点审美疲劳了,而且flask框架是阻塞式框架,效率未免有点不高。所以这次我使用了异步web框架aiohttp,简单记录一下基本使用,至于框架原理之类后面再深入研究。

官方文档


hello world

简单hello world代码

1
2
3
4
5
6
7
8
9
from aiohttp import web

if __name__ == "__main__":
app = web.Application()
async def hello(request):
return web.Response(text = "hello")

app.router.add_get("/", hello)
web.run_app(app, port = 5000)

在路由中添加视图函数
1.get

url例子: /

1
app.router.add_get("/", hello)

url例子: /api/order/3

1
app.router.add_get("/api/order/{id}", reservation_view.get_order)

使用以下获取参数id

1
id = int(request.match_info['id'])

url例子: /test?uuid=12
使用以下获取url参数

1
uuid = request.rel_url.query['uuid']

2.post

1
app.router.add_post("/api/order", reservation_view.create_order)

使用以下获取表单data

1
data = await request.post()


问题

在这次系分的后端开发过程中,遇到了一个我以前也遇到过的问题,但是我忘记了以前我是怎么解决的了。问题就是,当post的表单中有一个value值为一个list,那么服务端这边接受会这样?

上面的问题就是服务端这边接受了这个字典之后,会发现这个list的value值已经变形了,具体情况如下:
发送表单

1
2
3
4
5
6
7
8
9
10
11
import requests

if __name__ == "__main__":
session = requests.Session()
data = {
"table": 1,
"list": [{'id': '1', 'name': 'pork', 'count': 1, 'price': 20}, {'id': '2', 'name': 'fish', 'count': 1, 'price': 23}]
}
print("data", data)
r = session.post("http://127.0.0.1:5000/api/order", data = data)
print("r", r)

服务端接收为,很显然变形了

1
<MultiDictProxy('table': '1', 'list': 'id', 'list': 'name', 'list': 'count', 'list': 'price', 'list': 'id', 'list': 'name', 'list': 'count', 'list': 'price')>

然后我想到的解决方法就是将list这个value转为string类型再post过来,很显然或者解决方法很简单,问题就在于怎么在服务端这边从新将这个string解释为list对象,并且回复里面的字典。谷歌之后发现这样使用python的ast模块解决这个问题非常的方便

发送将list类型的value转为list之后的字典

1
2
3
4
data = {
"table": 1,
"list": "[{'id': '1', 'name': 'pork', 'count': 1, 'price': 20}, {'id': '2', 'name': 'fish', 'count': 1, 'price': 23}]"
}

服务端这边接受和解析

1
2
3
data = await request.post()
table_num = data["table"]
food_list = ast.literal_eval(data["list"])

这样就完美解决这个问题了,可能还有其他不修改list类型更好的方法,但是我觉得就不深究了。

自制简易图片云盘

github地址


需求

因为我经常在markdown书写过程中需要获取我一些图片或者截图的在线url,但是有些图片云盘需要收费或者要自己手动上传不方便。自己上传github固然可以,但是还是不够方便,所以写了个小脚本。


demo

demo


需要

  1. python
  2. git
  3. python依赖PIL

用法

  1. 一开始需要把配图或者截图保存到本地(本地的git仓库中的raw文件夹),不需要修改文件名。

文件结构


  1. 点击运行 run.cmd 文件

run.cmd

可以看见后面会输出这张图片的url,最后会提示你确认退出,其实这个确认退出只是为了造成阻塞,不然这个cmd文件运行完就自动关闭窗口不留时间给你复制url了。


代码

只用了一点点很简单的py

  1. 把保存在raw文件夹里面的图片复制到mature文件夹里面,并重命名(其实重命名不是必要的),最后删除raw里面的原图片,最终返回这张图片的新名字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    def deliver_image():
    raw = os.listdir("raw")
    if raw == []:
    return False
    f = open("num.txt", "rb")
    num_str = f.read().decode("utf-8")
    num = int(num_str)
    f.close()
    old_name = "raw/" + raw[0]
    image_to_add = Image.open(old_name)
    new_name = str(num + 1) + ".png"
    image_to_add.save("mature/" + new_name)
    f = open("num.txt", "wb")
    f.write(str(num + 1).encode("utf-8"))
    f.close()
    os.remove("raw/" + raw[0])
    return new_name
  2. 使用subprocess产生三个子进程去调用git命令,使这个本地仓库的修改被push到github上的仓库中,最后返回这张图片在github上的地址

1
2
3
4
def push_to_github():
subprocess.call("git add .", shell = True)
subprocess.call("git commit -m 新增一张图片", shell = True)
subprocess.call("git push origin master", shell = True)
  1. 运行

    py中运行

    1
    2
    3
    4
    5
    6
    7
    if __name__ == "__main__":
    pic_name = deliver_image()
    if pic_name != False:
    push_to_github()
    print("图片的url:")
    print("https://raw.githubusercontent.com/gzm1997/gallery/master/mature/" + pic_name)
    stop = input("确认退出:")

cmd中

1
python add.py

sqlalchemy一些常见概念

metadata

官方文档解释

The MetaData is a registry which includes the ability to emit a limited set of schema generation commands to the database. As our SQLite database does not actually have a users table present, we can use MetaData to issue CREATE TABLE statements to the database for all tables that don’t yet exist. Below, we call the MetaData.create_all() method, passing in our Engine as a source of database connectivity

metadata是一个注册器,里面包含一些数据库的模式生成命令,可以调用metadata发起create table命令来创建这个表

image_1cdjqt91j1hpr34cos1euqodr9.png-5.9kB


mapper

sqlalchemy中创建一个继承declarative_base的类的本质是,创建一个class的同时生成一个同名的Table对象,并且使用一个mapper对象将两个对象映射在一起。

1
2
3
4
5
6
7
8
9
class MyClass(Base):
__tablename__ = 'my_table'
id = Column(Integer, primary_key=True)
type = Column(String(50))
alt = Column("some_alt", Integer)

__mapper_args__ = {
'polymorphic_on' : type
}

上述代码跟下面效果是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
my_table = Table("my_table", metadata,
Column('id', Integer, primary_key=True),
Column('type', String(50)),
Column("some_alt", Integer)
)

class MyClass(object):
pass

mapper(MyClass, my_table,
polymorphic_on=my_table.c.type,
properties={
'alt':my_table.c.some_alt
})


对象状态转换

image_1cdjs8hm616i15k3vss1r6u17ju3j.png-14.2kB

值得一提的是,当class被add到session中,但是没有commit,此时这个class并没有被写入数据库,但是此时进行query查询这个class是可以被查询出来的,因为当执行query时,执行了flush操作,调用了sql语句,这个class对应的row被生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from sqlalchemy import Column, String, Integer, ForeignKey, create_engine
from sqlalchemy.orm import sessionmaker, relationship, backref
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class User(Base):
__tablename__ = "user"
id = Column(Integer, primary_key = True)
name = Column(String(20))

engine = create_engine('mysql+mysqlconnector://root:Gzm20125@localhost:3306/test')
DBSession = sessionmaker(bind = engine)

Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
# 创建session对象:

session = DBSession()

u = User(id = 1, name = "gzm")
session.add(u)
print(session.query(User).filter_by(id = 1).first())

输出
image_1cdjsg49k1qk49j6192o1vmd1vdp40.png-13.5kB

软件生命周期

前言

这几天我都在深深地反思,发现自己的确是一个很功利的人,从框架的工具化就可以看出来。越是患得患失就越容易失去。我觉得我这段时间可以多写点代码,并且这不能很以往一样,一个劲地写,应该从工程上开始考量所有问题。


软件生命周期6大阶段

  1. 可行性分析和计划阶段
    • 确定软件开发的总体目标,给出功能,性能,可靠性,接口等方面的要求,进行可行性分析
    • 估计可利用的资源(硬件,软件,人力等),成本,效益,开发进度,进行投资收益分析,制定开发计划
    • 提交可行性分析报告,开发计划等文档
  2. 需求分析阶段
    • 分析用户提出的要求,给出需求详细定义,确定软件系统的各项功能,性能需求和设计约束,确定对文档的编制的要求
    • 提交软件需求说明,软件规格说明,数据要求说明等文档和初步用户手册
  3. 设计阶段
    • 概要设计:将各项需求转换为软件的体系结构。软件的每一组成部分都是意义明确的模块,每个模块和某些需求相对应
    • 详细设计:对没一个模块要完成的工作进行具体的描述,提供源编程编写的直接依据
    • 提交结构设计说明,详细设计说明和测试计划初稿等文稿
  4. 实现阶段
    • 实现源码编码,编译,和排错调试,得到没有语法错误的程序清单。程序结构良好,清晰易读,且与设计想一致
    • 编写进度日报,周报,和月报
    • 提交用户手册,操作手册等面向用户的文档的编写工作
    • 编写测试计划
  5. 测试阶段
    • 全面测试目标软件系统,并检查审阅已编制的文档,提交测试分析报告。逐项评价所生产的程序、文档以及开发工作本身,提交项目开发总结报告
    • 在整个开发过程中 (即前五个阶段中),开发集体需要按月编写
      开发进度月报。
  6. 运行与维护阶段
    • 软件提交给用户后,在运行使用中得到持续维护,根据用户新
      提出的需求进行必要而且可能的扩充、删改、更新和升级。
    • 软件维护包括改正性维护 (发现错误)、适应性维护 (适应运行
      环境变化) 和完善性维护 (增强功能)。

常见软件生命周期模型

软件开发过程一般应该采用某种类型的软件生命周期模型,按
照一定的开发方法,使用相应的工具系统实现。

常见模型

  • 瀑布模型 (Waterfall Model)
  • V-W 模型 (V Model and W Model)
  • 快速应用开发模型 (RAD Model)
  • 原型模型 (Prototype Model)
  • 增量/演化/迭代模型 (Incremental Model)
  • 螺旋模型 (Spiral Model)
  • 喷泉模型 (Fountain Model, Object-Oriented Model)
  • 基于构件的开发模型 (CBSD Model)
  • Rational 统一过程模型 (RUP Model)
  • *敏捷开发模型与极限编程 (Agile Modeling and XP)

瀑布模型

image_1c8ias8gck9c5rmro9cihae89.png-96.4kB

特征

  • 每一阶段工作对象来自于上一阶段的输出
  • 根据本阶段的活动规程执行相应的任务
  • 本阶段产生相关软件工件,作为下一阶段活动的输入
  • 对本阶段的活动情况进行评审

优点

  • 降低软件开发的复杂程度,提高软件开发过程的透明性,提高软件开发过程的可管理性
  • 推迟软件实现,强调在软件实现前必须进行分析和设计工作
  • 以项目的阶段评审和文档控制为手段有效地对整个开发过程进行指导,保证了阶段之间的正确衔接,能够及时发现并纠正开发过程中存在的缺陷,使产品达到预期的质量要求

缺点

  • 强调过程活动的线性顺序
  • 缺乏灵活性,特别是无法解决软件需求不明确或不准确的问题
  • 风险控制能力较弱
  • 瀑布模型中的软件活动是文档驱动的,当阶段之间规定过多的文档时,会极大地增加系统的工作量
  • 管理人员如果仅仅以文档的完成情况来评估项目完成进度,往往会产生错误的结论

sysu教务选课系统登陆

准备:
python3
安装tesseract,本人提供的tesseract百度云下载,亲测版本4比较多坑,这是版本3,已经够用的了
链接: http://pan.baidu.com/s/1boHp2Jt 密码: 6trj


  • 下载本项目到本地
  • 进入主目录,输入以下命令安装相关依赖:
pip install -r requirements.txt
  • 在如下59和64行输入你的账号和密码:
    输入账号密码

  • 输入一下命令运行代码:

    python login.py

运行之后打印不少内容:
第一个是一个byte,内容就是下载下来的验证码:
验证码
第二个是登陆后的页面:
登陆之后
第三个是我的选课结果:
选课结果


最后可以发现本地生成了一个after_login.html的html文件,打开之后发现你的选课结果被下载下来了:
选课结果

功能

  • icode模块负责图片去噪和识别验证码
  • entryption模块负责加密密码
  • login模块负责登陆和抓取一些页面

上传和展示pdf以及markdown


demo

查看地址

pdf版本readme地址


前言

这个是开学前跟别人赌气写下的一个很小的web app,功能是上传pdf文件,然后获得这个文件在线浏览的url,需求很简单。之前为了方便只是把上传到服务器的文件存在flask static文件夹下,会有不便于管理等等问题。这个周末做了如下改进:使用mysql进行存储文件,进行模块的细化。

郭柱明 2018/3/25


可行性分析

从需求上而言,这个web app仅仅有上传,展示两个功能。
我一个人可以进行开发,但是在人力物力上已经绰绰有余。
在app的服务器部署上,我有一个digital ocean vps可以用来部署,所以部署上也没有太大问题。


需求分析

  1. 上传pdf文件
  2. 获得在线浏览此pdf文件的url
  3. 打开这个url可以进行文件的预览

设计

###大概设计###

struction.png-12.1kB

整个结构呈现cs结构,server我使用nginx,server使用wsgi接口调用flask app

###详细设计###

  • 包-pdf(整个web app打包为一个包)
    • db.py模块(定义一个包含数据库链接的类以及初始化数据库的函数)
    • f.py模块(定义File类)

编码

编码情况详细见github


测试与运行

pdf.gif-631.7kB


部署

部署有个问题:无法从事先设置的环境变量中加载出mysql的账号和密码,在服务器上调试没问题,但是一旦部署上去就会在这部分出问题,无奈下只能在代码使用mysql的账号密码,有一定的安全风险。


后期改进

增加了上传和预览markdown文件的功能,具有改进为博客系统的潜质

暗网爬虫

暗网蜘蛛,本项目github地址


shadow_spider


暗网

暗网(英语:Darknet或Dark Web)通称只能用特殊软件、特殊授权、或对电脑做特殊设置才能连上的网络,使用一般的浏览器和搜索引擎找不到暗网的内容


在python上进入暗网


在python中进入暗网需要配置tor代理,因为本人使用的win10,所以主要讲在win10下配置tor代理


下载准备:(除了下载给出的下载链接,本人此项目github也提供相关资源下载 下载链接

  1. 首先下载tor expert bundle
    官方下载链接
    本人提供的百度云链接,密码: fsp8
    下载解压

  2. 下载到的tor expert bundle没有图形界面,使用起来不方便,因此需要下载Vidalia配合使用
    本人提供的百度云链接,密码: kknf

  3. cow下载,百度云连接,密码: c4kx。
    由于tor使用的协议是socks5,由于需要python的requestsselenium两个模块进行get, post请求。但是经过本人尝试,requests对socks5协议代理支持是很不良好的,使用requests加上socks5协议代理去get类似于 https://api.ipify.org/?format=json 这样查询ip的url是没有问题的,但是去get其他url或者暗网的url马上会出现莫名其妙的错误。所以需要使用cow把tor的socks5协议转为http协议


配置


我们要配置上面下载到的工具,使得我们的代理路线如下:
net
1,解压下载下来的tor expert bundle

2,开启你的vpn,本人使用的green vpn,socks5协议,监听1080端口

3,安装vidalia,安装后如图所示:

vidalia

然后以管理员身份运行Start Vidalia.exe

vidalia run

点击设定,在常规那里选中vidalia启动时运行tor软件,并在下面的路径中找到解压tor expert bundle后得到的tor.exe

setting1

点击网络,选中我使用代理服务器连接网络,由于本人使用的green vpn是监听本地的1080端口,所以地址写为:127.0.0.1,端口写为:1080(貌似shadowsocks等vpn也是监听本地1080端口)

setting2

点击高级,在tor配置文件下选中vidalia安装后得到的Data/Tor/torrc文件,在数据目录那里选中Data文件夹(貌似我是多余地自己另外创建了一个Data文件夹给它,但是并不影响,有一个Data文件夹给vidalia存储数据就可以了)

setting3

点击确认

点击启动tor

run

稍等片刻,显示连接tor网络成功!

connect

因为需要在requests模块上使用tor代理而requests模块对socks5支持非常不良好,所以我们需要配置cow把socks5转为http
打开解压后的cow目录:

cow

修改rc.txt内容为如下:

rc

点击运行cow.exe或者点击cow-hide.exe后台运行


在python中使用tor代理


再次重申上面配置好的代理结构:

net

能否正常使用tor代理一个很好的检验方法就是能否进入暗网,这是一个收集了很多暗网url的网站:https://onionlinks.org/

我给出一个暗网url:http://hcutffpecnc44vef.onion/
这是一个暗网的金融网站,普通的上网方式是打不开的,作为一个大哥哥,真心建议18-的小朋友还是不要去看暗网一些其他内容,为了你的身心健康。那下面的测试就是在python中进入这个暗网url为检验标准。

cash machine

在requests中进入暗网
新建一个test.py,输入以下内容:

import requests
proxies = {'http': 'http://127.0.0.1:7777', 'https': 'http://127.0.0.1:7777'}
s = requests.Session()
r = s.get("http://hcutffpecnc44vef.onion/", proxies = proxies)
print(r.text)
f = open("onion.html", "wb")
f.write(r.content)
f.close()

运行,发现可以打印这个暗网网页的内容出来了:

run test

打开test.py同一目录下,生成了一个onion.html的文件,点开,发现把内容也下载下来了:

get

在python的selenium模块的模拟浏览器phantomjs中进入暗网
selenium有三种,chrome,firefox,phantomjs,前两种有图形界面,第三种没有图形界面,所以我选中phantomjs

phantomjs下载:
本人github上的下载下载链接
本人百度云下载链接,密码: pj81

这次我们选择另一个暗网url:http://zqktlwi4fecvo6ri.onion/wiki/index.php/Main_Page
这是一个暗网wiki,上面有很多分类好的暗网url,各位要谨慎点开那些url,因为不少内容不健康的。
下载phantomjs解压,新建一个test2.py文件(下面的executable_path是上面下载解压phantomjs得到的phantomjs.exe运行文件的路径)

from selenium import webdriver

executable_path = "C:/Users/Administrator/Downloads/phantomjs-2.1.1-windows/phantomjs-2.1.1-windows/bin/phantomjs.exe"
service_args = ['--proxy=127.0.0.1:7777', '--proxy-type=http']

driver = webdriver.PhantomJS(executable_path = executable_path, service_args = service_args)

driver.get("http://zqktlwi4fecvo6ri.onion/wiki/index.php/Main_Page")

print(driver.page_source)

f = open("onion.html", "wb")
f.write(driver.page_source.encode("utf-8"))
f.close()

结果也可行:

run test2
wiki


本人的shadow_spider项目(未完待续)


项目github地址
本项目目的在于最大程度还原暗网上网页的内容,这就意味着需要:

注意要点:

  • 下载html
  • 在下载html之前必须加载javascript。因为javascript里面会有ajax,如果把javascript下载下来,而不是加载完javascript再下载html,那么下载到本地ajax向tor网络(暗网)中的服务器发送网络请求,这是行不通的。所以必须加载完javascript再下载html(网上有人说暗网上的网页都没有javascript,但是经本人尝试,暗网上的网页是有javascript的)。而python里加载javascript一个很好的工具就是上面的selenium
  • 下载css和图片等静态资源,需要使用requests模块,不能使用selenium。selenium下载图片和css等资源是很蛋疼的。因为这些模拟浏览器要下载资源就必须做到模仿我们在真的浏览器上那样右键另存为等操作,而这时候会弹出下载确认的弹窗,而selenium中的chrome和firefox是需要手动点击代码运行过程中弹出来的确认保存弹窗的,而phantomjs没有图形界面,所以就根本下载不图片和css等资源。

代码细节不详细说了,在我的github中下载到本地,输入:

pip install -r requirements.txt

安装需要的响应模块

运行main.py文件,输入一个暗网url:

run main

下载到本地的结果:

result

下面是一些暗网网页真实模样和我还原的样子:
真实1:

real1

还原1:

imitate1

真实2:

real2

还原2:

imitate2

此项目shadow_spider未完待续,等到暑假回考虑添加服务端。