CharlesXiao‘s Blog

  • 首页

  • 归档

  • 分类

  • 标签

  • 关于

  • 搜索

浅谈MVC、MVP、MVVM三种框架模式

发表于 2015-08-29 | 更新于 2016-07-07 | 字数统计787字 | 阅读时长2分钟 | 分类于 框架模式

MVC模式是一种非常经典的复合型软件架构模式,它把用户界面交互分拆到不同的三种角色中,应用程序被分成三个核心部件:Model(模型)、View(视图)、Controller(控制器), 它们各自处理自己的任务,这样就可以把业务逻辑和视图显示解耦,提高了应用程序的灵活性和复用性。MVC模式不管是在web开发还是Android开发中都被广泛应用,最典型的MVC应用就是JSP + servlet + javabean的模式,例如SpringMVC开发框架。

  • Model模型:模型持有所有的数据、状态和程序逻辑,一个模型可以有不同的多个视图表现形式,复用性高。例如数据库记录以及相应增删查改操作;Android中对数据库的操作、网络请求、格式转换、文件下载、大数据处理等的耗时操作都在Model里面处理

  • View视图:视图是用户看到并与之交互的页面,通常直接从模型中取得它需要显示的状态与数据,一个视图往往有一个相应的控制器,理论上也可以同不同的模型相关联。例如html、xml页面;Android中主要是layout布局xml文件,webview等

  • Controller控制器:位于视图和模型中间,负责接受用户的输入,将输入进行解析并反馈给模型,例如Android开发中的Activity

  • MVC模式图解析:Event(事件)导致Controller改变Model或View,或者同时改变两者。只要Controller改变了Model的数据或者属性,所有依赖的View都会自动更新。类似的,只要Controller改变了View,View会从潜在的Model中获取数据来刷新自己。

  

 android中mvc的具体体现如下:
1)视图层(view):一般采用xml文件进行界面的描述,使用的时候可以非常方便的引入,当然,如何你对android了解的比较的多了话,就一定 可以想到在android中也可以使用javascript+html等的方式作为view层,当然这里需要进行java和javascript之间的通 信,幸运的是,android提供了它们之间非常方便的通信实现。

 2)控制层(controller):android的控制层的重 任通常落在了众多的acitvity的肩上,这句话也就暗含了不要在acitivity中写代码,要通过activity交割model业务逻辑层处理, 这样做的另外一个原因是android中的acitivity的响应时间是5s,如果耗时的操作放在这里,程序就很容易被回收掉。

 3)模型层(model):对数据库的操作、对网络等的操作都应该在model里面处理,当然对业务计算等操作也是必须放在的该层的。

MVP模式
View不再与model交互,所有的交互通过presenter实现,双方之间的交互都是双向的,view层作为被动视图非常薄,没有任何的业务逻辑,业务逻辑都在presenter里边;Presenter与View的交互是通过接口来进行的

MVP模式在Android开发中的应用

Android开发adb无法识别MX手机和部分平板设备的解决方法

发表于 2015-08-16 | 更新于 2016-02-18 | 字数统计238字 | 阅读时长1分钟 | 分类于 Android开发

mac安装adb命令

brew install android-platform-tools

adb无法识别设备

在Android开发过程中,当我们把移动设备连接到计算机进行真机测试时经常会出现部分设备无法被计算机识别的情况,比如魅族系列手机和部分国产android平板电脑,这种异常情况在mac系统和win系统中都会出现,在此讲述一下解决步骤:

1. 将移动设备连接到计算机,确保连接无误

2. 打开terminal窗口,在当前用户home目录下输入system_profiler SPUSBDataType;可以看到该计算机连接的所有usb设备信息

3. 记录你希望计算机识别的usb设备的Vendor ID参数,例如我的mx3手机Vendor ID参数为0x18d1

4. 在terminal中输入 echo "0x18d1" >> ~/.android/adb_usb.ini即可(该命令会在adb_usb.ini文件中添加一行0x18d1,使得该usb设备可以被计算机识别)

Python基础知识实例讲解

发表于 2015-08-01 | 更新于 2015-08-19 | 字数统计1.4k字 | 阅读时长6分钟 | 分类于 Python开发

Python作为一种面向对象、解释型计算机程序设计语言,语法简洁而清晰,具有丰富和强大的类库,在网站开发,数据处理,网络编程,图形处理,网络爬虫、机器学习、科学计算、自动化测试、系统运维等方面应用广泛。也正是由于Python开发简洁、优雅、开发效率高,它能够在很多地方替代java、C++等语言从而提高程序猿编程效率。作为程序猿,掌握这样一门便捷的编程语言来作为工具显得十分必要。本文主要参考了“极客学院”相关Python入门视频教程,对Python基础入门知识进行了整合,采用代码实例展现的方式,语法知识全部体现在代码中,简洁明了易于理解;本文主要包括三个Python module,分别是hellopython.py、mylib.py、stackqueue.py,涵盖了Python数据类型,常量变量,判断循环,函数,类和对象,模块,数据结构等知识(Python开发编辑器为PyCharm)

hellopython.py

该模块为主模块,负责实现大部分Python基础语法及其测试,从该模块我们看到几乎所有的Python基础句法的使用方法

代码:

# coding=UTF-8
__author__ = 'xiaoyong'

from math import *
from stackqueue import *
from mylib import Hi
from collections import Counter
from itertools import combinations

import mylib
import sys

class HelloPy:
    def helloPy(self, param):
        '''文档字符串:该函数用于测试变量定义,判断语句

        循环语句'''
        # 定义变量
        global globalVar
        globalVar = 10
        var1 = 10
        var2 = 20
        var3 = var1 + var2
        print(var3)

        # 判断语句
        if param >= 60:
            print("{0} > 60".format(param))
        elif param > 0:
            print("{0} > 0".format(param))
        else:
            print("{0} < 60 && 0 > {1}").format(param, "param")

        # 循环语句
        for i in range(0, 10, 2):
            print(i)

        var4 = 0
        while var4 < 3:
            print(var4)
            var4 += 1
        else:
            print("else var4={0}".format(var4))


# 形参设置默认初始值
def fuc1(a, b=1, c=2):
    print(a);print(b);print(c)


def fuc2(a, b=1):
    print(a)
    return b


def fuc3():
    return (1, 2, 3)

# Python常用tips 
def fuc_python_tips():
    print('\nPython常用tips: ')
    # 交换变量
    var1 = 0
    var2 = 1
    var1, var2 = var2, var1
    print var1, var2
    # if语句在行内
    print('hello') if 1 != 1 else 'world'
    print('hello' + str(var1))
    # 数字技巧
    print 5.0 // 2, 5.0 / 2, 2 ** 5, .3 / .1, .3 // .1
    var3 = 2
    if 1 < var3 < 3:
        print(var3)
    # 列表迭代
    list1 = ['a', 'b', 'c']
    list2 = ['d', 'e', 'f']
    for a, b in zip(list1, list2):
        print a + " vs " + b
    for index, a in enumerate(list1):
        print index, a
    list3 = [0] * 3
    list3.append(2)
    print(list3)
    # 列表-->字符串
    print(','.join(list1))
    # 获取列表子集
    x = [1, 2, 3, 4, 5, 6]
    # 前3个
    print x[:3]
    # 中间4个
    print x[1:5]
    # 最后3个
    print x[-3:]
    # 奇数项
    print x[::2]
    # 偶数项
    print x[1::2]
    # 统计单个字母数目
    print Counter('hello')
    # 列出所有组合方式
    for t in combinations(x, 2):
        print(t)

# python标识符首字符只能是下划线或者字母,其他部分可以是下划线、字母、数字;标识符对大小写敏感

print(sys.version);
print ("模块名:{0}").format(__name__)
print(pi)
help(HelloPy.helloPy)
# 列出模块定义的标识符,标识符有函数、类和变量
print dir(sys)
print(HelloPy.helloPy.__doc__)

# 引用mylib.py模块调用函数
h = mylib.Hello("mylib")
h.sayHello()

h1 = Hi("mylib")
h1.sayHi()

h2 = HelloPy()
h2.helloPy(15)
# 全局变量
print(globalVar)

fuc1(b=2, a=3)
var1 = fuc2(2, 3)
print(var1)
var2 = fuc3()
print(var2)
m1, m2, m3 = fuc3()
print("{0} {1} {2}".format(m1, m2, m3))

fuc_python_tips()

# 引号
str1 = 'It is a "dog"'
str2 = "It's a dog"
str3 = '''she
I
you'''
print("{0}  {1}".format(str1, str2))

# 字符串重复输出
print(str3) * 3

# 转义符
print('It\'s a dog\nyou are not')
print"women\
doushihaohaizi"

# 自然字符串
print(r"It's a dog \n you are not")

# 子字符串,索引和切片两种方式
print(str1[1]);print(str1[0:3]);print(str1[:3]);print(str1[3:]);

# set,求两个set的& | - 重复元素
print("\n集合 列表 元组 字典测试:")
var3 = set('aaabbc')
# 集合中不能有重复元素
newVar = set(var3)
print(newVar)
# 列表,数据可以修改
students = ["a", 'b', "c"]
print(students[1])

# 元组,数据不可修改
students = ("a", 'b', "c")
print(students[1])

# 字典
dic = {'姓名': 'xiaoyong', "性别": "男"}
dic['兴趣'] = '音乐'
print(dic['兴趣'])
print(dic.get('姓名'))

# 栈测试
print("\n栈测试:")
stack = Stack()
stack.push(1)
stack.push(2)
stack.push(3)
print(stack.length())
print(stack.isEmpty())
print(stack.top())
stack.pop()
print(stack.top())

# 队列测试
print("\n队列测试:")
queue = Queue()
queue.enQueue(1)
queue.enQueue(2)
queue.enQueue(3)
print(queue.length())
print(queue.isEmpty())
print(queue.head())
print(queue.tail())
queue.deQueue()
print(queue.head())
print(queue.tail())

mylib.py

该模块为引用模块,实现两个类,Hello为父类,Hi为子类

代码:

# coding=UTF-8
__author__ = 'xiaoyong'

class Hello:
    def __init__(self, name):
        self._name = name

    def sayHello(self):
        print("class Hello: sayHello")
        print ("模块名:{0}").format(__name__)

# 类的继承
class Hi(Hello):
    def __init__(self, name):
        Hello.__init__(self, name)

    def sayHi(self):
        print("class Hi: sayHi")

stackqueue.py

该模块为引用模块,Stack和Queue类分别实现栈和队列两种数据结构

代码:

# coding= utf-8
__author__ = 'xiaoyong'


# 栈实现
class Stack(object):
    def __init__(self):
        self.stack = []

    def push(self, item):
        self.stack.append(item)

    def pop(self):
        if self.stack != []:
            return self.stack.pop(-1)
        else:
            return None

    def top(self):
        if self.stack != []:
            return self.stack[-1]
        else:
            return None

    def length(self):
        return len(self.stack)

    def isEmpty(self):
        return self.stack == []


# 队列实现
class Queue(object):
    def __init__(self):
        self.queue = []

    def enQueue(self, item):
        self.queue.append(item)

    def deQueue(self):
        if self.queue != []:
            return self.queue.pop(0)
        else:
            return None

    def head(self):
        if self.queue != []:
            return self.queue[0]
        else:
            return None

    def tail(self):
        if self.queue != []:
            return self.queue[-1]
        else:
            return None

    def length(self):
        return len(self.queue)

    def isEmpty(self):
        return self.queue == []

结语

以上三个模块基本涵盖了Python所有基本语法的使用,仅作参考查询之用;如需深入学习Python相关知识,比如RESTful API开发,web2Py,Flask等框架使用,请Google之或参考相关官网。

浅析java内存管理机制

发表于 2015-07-29 | 更新于 2018-11-02 | 字数统计6.3k字 | 阅读时长21分钟 | 分类于 java学习笔记

内存管理是计算机编程中的一个重要问题,一般来说,内存管理主要包括内存分配和内存回收两个部分。不同的编程语言有不同的内存管理机制,本文在对比C++和java语言内存管理机制的不同的基础上,浅析java中的内存分配和内存回收机制,包括java对象初始化及其内存分配,内存回收方法及其注意事项等……

java与C++内存管理机制对比

在C++中,所有的对象都会被销毁,局部对象的销毁发生在以右花括号为界的对象作用域的末尾处,而程序猿new出来的对象则应该主动调用delete操作符从而调用析构函数去回收对象占用的内存。但是C++这种直接操作内存的方式存在很大内存泄露风险,而且人为管理内存复杂且困难。

在java中,内存管理由JVM完全负责,java中的“垃圾回收器”负责自动回收无用对象占据的内存资源,这样可以大大减少程序猿在内存管理上花费的时间,可以更集中于业务逻辑和具体功能实现;但这并不是说java有了垃圾回收器程序猿就可以高枕无忧,将内存管理抛之脑外了!一方面,实际上java中还存在垃圾回收器没法回收以某种“特殊方式”分配的内存的情况(这种特殊方式我们将在下文中进行详细描述);另一方面,java的垃圾回收是不能保证一定发生的,除非JVM面临内存耗尽的情况。所以java中部分对象内存还是需要程序猿手动进行释放,合理地对部分对象进行管理可以减少内存占用与资源消耗。

java内存分配

java程序执行过程

  1. 首先Java源代码文件(.java后缀)会被Java编译器编译为字节码文件(.class后缀),然后由JVM中的类加载器加载各个类的字节码文件,加载完毕之后,交由JVM执行引擎执行(执行过程还包括将字节码编译成机器码),JVM执行引擎在执行字节码时首先会扫描四趟class文件来保证定义的类型的安全性,再检查空引用,数据越界,自动垃圾收集等。在整个程序执行过程中,JVM会用一段空间来存储程序执行期间需要用到的数据和相关信息,这段空间一般被称作为Runtime Data Area(运行时数据区),也就是我们常说的JVM内存

  2. 类加载器分为启动类加载器(不继承classLoader,属于虚拟机的一部分;负责加载原生代码实现的Java核心库,包括加载JAVA_HOME中jre/lib/rt.jar里所有的 class);扩展类加载器(负责在JVM中扩展库目录中去寻找加载Java扩展库,包括JAVA_HOME中jre/lib/ext/xx.jar或-Djava.ext.dirs指定目录下的 jar 包);应用程序类加载器(ClassLoader.getSystemClassLoader()负责加载Java类路径classpath中的类)

  1. 类加载机制的流程:包括了加载、连接(验证、准备、解析)、初始化五个阶段
    • 加载:查找装载二进制文件,通过一个类的全限定名获取类的二进制字节流,并将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构;在 Java 堆中生成一个代表这个类的 java.lang.Class 对象,作为对方法区中这些数据的访问入口。
    • 验证:为了确保Class文件中的字节流包含的信息符合当前虚拟机的要求,完成以下四个阶段的验证:文件格式的验证、元数据的验证、字节码验证和符号引用验证。
    • 准备:准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配
    • 解析:解析阶段是虚拟机将常量池中的符号引用转化为直接引用的过程
    • 初始化:初始化阶段是根据程序员通过程序指定的主观计划去初始化类变量和其他资源,也就是执行类构造器()方法的过程

现代硬件内存架构

  1. 一个有两个或者多个 CPU 的现代计算机上同时运行多个线程是可能的,如果你的 Java 程序是多线程的,在你的 Java 程序中每个 CPU 上一个线程可能同时(并发)执行
  2. CPU在寄存器上的执行操作速度稍微大于CPU缓存层的执行速度,远大于在主存上的执行速度
  3. Java内存模型中的堆栈分布在硬件内存结构中的CPU寄存器,CPU缓存层,CPU主存中,大部分分布在主存中

java内存模型划分

一般来讲,我们将java内存划分为以下几个区域, 如图:

GC备注:

  1. 年轻对象存放在年轻代,采用Minor GC(指从年轻代空间(包括 Eden 和 Survivor 区域)回收内存); 长期存活的年老对象以及大对象直接存放在年老代,采用Full GC(Full GC == Major GC指的是对老年代/永久代的stop the world的GC),回收速度慢;JVM维护一个对象的年龄来进行对象的内存区域转移,从Eden-Survivor-老年代
  2. 新生代包括一个Eden区,两个survivor的from和to区(8:1:1),负责年轻小对象的回收;Eden区存放新创建的大量对象,回收频繁,所以区域大;Survivor存放每次垃圾回收后存活的对象
  3. 一个对象的成员变量可能随着这个对象自身存放在堆上
  4. 一个Object的大小计算方法:一个引用4byte+空Object本身占据8byte+其它数据类型占据自身大小byte(例如char占用2byte);然而由于系统分配以8byte为单位,所以每个Object占据的大小必须为8的倍数,比如一个空的Object应该占据4+8=12,也就是说需要占据16byte

下文中将要提到的内存分配与回收主要是指对象所占据的堆内存的释放与回收。

java对象创建及初始化

java对象创建之后,就会在堆内存拥有自己的一块区域,接着就是对象的初始化过程。对象一般通过构造器来进行初始化,构造器是一种与类名相同的没有返回值的特殊方法;如果一个类中没有定义构造函数,则系统会自动生成一个不接受任何参数的默认构造器;但是如果已经定义一个构造器(无论是否有参数),编译器就不会再自动创建默认构造器了;我们可以对构造函数进行多次重载(即传递不同数目或不同顺序的参数列表),也可以在一个构造器中调用另一个构造器,但是只能调用一次,并且必须将构造器放在最起始处,否则编译器会报错。

那么类成员初始化又是怎么做的呢?顺序是怎样的呢?java中所有变量在使用前都应该得到恰当的初始化,即使是方法的局部变量,如果不进行初始化就会发生编译错误;而如果是类的成员变量,即使你不进行初始化赋值,系统也是会给与其一个初始值的,例如char、int类型的初始值都是0,对象引用不进行初始化则默认为null。

类成员初始化顺序总结:先静态后普通再构造, 先父类后子类,同级看书写顺序

1.先执行父类静态变量和静态代码块,再执行子类静态变量和静态代码块
2.先执行父类普通变量和代码块,再执行父类构造器(static方法) 
3.先执行子类普通变量和代码块,再执行子类构造器(static方法) 
4.static方法初始化先于普通方法,静态初始化只有在必要时刻才进行且只初始化一次。

注意:子类的构造方法,不管这个构造方法带不带参数,默认的它都会先去寻找父类的不带参数的构造方法。如果父类没有不带参数的构造方法,那么子类必须用supper关键子来调用父类带参数的构造方法,否则编译不能通过。

java内存回收

垃圾回收器(4种收集器)和finalize()方法

java中垃圾回收器可以帮助程序猿自动回收无用对象占据的内存,但它只负责释放java中创建的对象所占据的所有内存,通过某种创建对象之外的方式为对象分配的内存空间则无法被垃圾回收器回收;而且垃圾回收本身也有开销,GC的优先级比较低,所以如果JVM没有面临内存耗尽,它是不会去浪费资源进行垃圾回收以恢复内存的。最后我们会发现,只要程序没有濒临存储空间用完那一刻,对象占用的空间就总也得不到释放。我们可以通过代码System.gc()来主动启动一个垃圾回收器(虽然JVM不会立刻去回收),在释放new分配内存空间之前,将会通过finalize()释放用其他方法分配的内存空间。

  1. Serial收集器:一个单线程的新生代收集器,它进行垃圾收集时,必须暂停其他所有的工作线程,直到它收集结束。简单高效
  2. Parallel(并行)收集器:JVM缺省收集器,其最大的优点是使用多个线程来通过扫描并压缩堆。串行收集器在GC时会停止其他所有工作线程(stop-the-world),CPU利用率是最高的,所以适用于要求高吞吐量(throughput)的应用,但停顿时间(pause time)会比较长,所以对web应用来说就不适合,因为这意味着用户等待时间会加长。而并行收集器可以理解是多线程串行收集,在串行收集基础上采用多线程方式进行GC,很好的弥补了串行收集的不足,可以大幅缩短停顿时间,因此对于空间不大的区域(如young generation),采用并行收集器停顿时间很短,回收效率高,适合高频率执行。
  3. CMS收集器:基于“标记-清除”算法实现的,它使用多线程的算法去扫描老生代堆(标记)并对发现的待回收对象进行回收(清除),容易产生大量内存碎片使得大对象无法创建然后不得不提前触发full GC。CPU资源占用过大,标记之后容易产生浮动垃圾只能留到下一次GC处理
  4. G1收集器:G1收集器是基于“标记-整理”算法实现的收集器,也就是说它不会产生空间碎片。G1是一个针对多处理器大容量内存的服务器端的垃圾收集器,其目标是在实现高吞吐量的同时,尽可能的满足垃圾收集暂停时间的要求。它可以非常精确地控制停顿,既能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒,具备了一些实时Java(RTSJ)的垃圾收集器的特征。垃圾收集器

finalize()方法的工作原理是:一旦垃圾回收器准备好释放对象占用的存储空间,将首先调用并且只能调用一次该对象的finalize()方法(通过代码System.gc()实现),并且在下一次垃圾回收动作发生时,才会真正回收对象占用的内存。所以如果我们重载finalize()方法就能在垃圾回收时刻做一些重要的清理工作或者自救该对象一次(只要在finalize()方法中让该对象重新和引用链上的任何一个对象建立关联即可)。finalize()方法用于释放用特殊方式分配的内存空间,这是因为我们可能在java中调用非java代码来分配内存,比如Android开发中调用NDK。那么,当我们调用C中的malloc()函数分配了存储空间,我们就只能用free()函数来释放这些内存,这样就需要我们在finalize()函数中用本地方法调用它。

对象内存状态&&引用形式及回收时机

  • java对象内存状态转换图

  • 如何判断java对象需要被回收?GC判断方法

    • 引用计数,引用计数法记录着每一个对象被其它对象所持有的引用数,被引用一次就加一,引用失效就减一;引用计数器为0则说明该对象不再可用;当一个对象被回收后,被该对象所引用的其它对象的引用计数都应该相应减少,它很难解决对象之间的相互循环引用问题循环引用实例
    • 可达性分析算法:从GC Root对象向下搜索其所走过的路径称为引用链,当一个对象不再被任何的GC root对象引用链相连时说明该对象不再可用,GC root对象包括四种:方法区中常量和静态变量引用的对象,虚拟机栈中变量引用的对象,本地方法栈中引用的对象; 解决循环引用是因为GC Root通常是一组特别管理的指针,这些指针是tracing GC的trace的起点。它们不是对象图里的对象,对象也不可能引用到这些“外部”的指针。
    • 采用引用计数算法的系统只需在每个实例对象创建之初,通过计数器来记录所有的引用次数即可。而可达性算法,则需要再次GC时,遍历整个GC根节点来判断是否回收
  • java对象的四种引用
    1.强引用 :创建一个对象并把这个对象直接赋给一个变量,eg :Person person = new Person(“sunny”); 不管系统资源有么的紧张,强引用的对象都绝对不会被回收,即使他以后不会再用到。
    2.软引用 :通过SoftReference类实现,eg : SoftReference p = new SoftReference(new Person(“Rain”));内存非常紧张的时候会被回收,其他时候不会被回收,所以在使用之前要判断是否为null从而判断他是否已经被回收了。
    3.弱引用 :通过WeakReference类实现,eg : WeakReference p = new WeakReference(new Person(“Rain”));不管内存是否足够,系统垃圾回收时必定会回收
    4.虚引用 :不能单独使用,主要是用于追踪对象被垃圾回收的状态,为一个对象设置虚引用关联的唯一目的是希望能在这个对象被收集器回收时收到一个系统通知。通过PhantomReference类和引用队列ReferenceQueue类联合使用实现

常见垃圾回收算法参考图

  • 停止-复制算法
    这是一种非后台回收算法,将可用内存按容量划分为大小相等的两块,每次只使用其中的一块,内存浪费严重.它先暂停程序的运行,然后将所有存活的对象从当前堆复制到另外一个堆,没被复制的死对象则全部是垃圾,存活对象被复制到新堆之后全部紧密排列,就可以直接分配新空间了。此方法耗费空间且效率低,适用于存活对象少。
  • 标记-清扫算法
    同样是非后台回收算法,该算法从堆栈区和静态域出发,遍历每一个引用去寻找所有需要回收的对象,对每个找到需要回收对象都进行标记。标记结束之后,开始清理工作,被标记的对象都会被释放掉,如果需要连续堆空间,则还需要对剩下的存货对象进行整理;否则会产生大量内存碎片
  • 标记-整理算法
    先标记需要回收的对象,但是不会直接清理那些可回收的对象,而是将存活对象向内存区域的一端移动,然后清理掉端以外的内存。适用于存活对象多。

  • 分代算法
    在新生代中,每次垃圾收集时都会发现有大量对象死去,只有少量存活,因此可选用停止复制算法来完成收集,而老年代中因为对象存活率高、没有额外空间对它进行分配担保,就必须使用标记—清除算法或标记—整理算法来进行回收。

JVM性能调优

  1. JVM分配超大堆(前提是物理机的内存足够大)来提升服务器的响应速度,但分配超大堆的前提是有把握把应用程序的 Full GC 频率控制得足够低,因为一次 Full GC 的时间造成比较长时间的停顿。控制 Full GC 频率的关键是保证应用中绝大多数对象的生存周期不应太长,尤其不能产生批量的、生命周期长的大对象,这样才能保证老年代的稳定
  2. 分配超大堆时,如果用到了 NIO 机制分配使用了很多的 Direct Memory,则有可能导致 Direct Memory 的 OutOfMemoryError 异常,这时可以通过-XX:MaxDirectMemorySize 参数调整 Direct Memory 的大小
  3. 调整线程堆栈,socket缓冲区,JNI占用的内存以及虚拟机、GC消耗的内存
  4. “-Xms and -Xmx (or: -XX:InitialHeapSize and -XX:MaxHeapSize)”参数:分别指定初始堆和最大堆大小,Xms一般代表着堆内存的最小值,JVM在运行时可以动态调整堆内存大小,如果我们 设置Xms=Xmx就相当于设置了一个固定大小的堆内存;例如:“java -Xms128m -Xmx2g MyApp”启动一个初始化堆内存为 128M,最大堆内存为 2G,名叫 “MyApp” 的 Java 应用程序;当我们设置Xmx最大堆内存不恰当时就很容易发生内存溢出,这样我们可以通过设置 - XX:+HeapDumpOnOutOfMemoryError 让 JVM 在发生内存溢出时自动生成堆内存快照,默认保存在JVM的启动目录下名为 java_pid.hprof 的文件里,分析它可以很好地定位到溢出位置

Linux下面查看Jvm性能信息的命令

  1. jstat: 用于查看Jvm的堆栈信息,能够查看eden,survivor,old,perm等堆区的的容量,利用率信息,对于查看系统是不是有内存泄漏以及参数设置是否合理有不错的意义。例如’’’ jstat -gc 12538 5000 —- 即会每5秒一次显示进程号为12538的java进成的GC情况 ‘’’参数解释
  2. jstack:用来查看Jvm当前的线程dump的,可以看到当前Jvm里面的线程状况,对于查找blocked线程比较有意义
  3. jmap:用来查看Jvm当前的heap dump的,可以看出当前Jvm中各种对象的数量,所占空间等等;尤其值得一提的是这个命令可以导出一份binary heap dump的bin文件,这个文件能够直接用Eclipse Memory Anayliser来分析,并找出潜在的内存泄漏的地方。
  4. 非jvm命令—netstat:通过这个命令可以看到Linux系统当前在各个端口的链接状态,比如查看数据库连接数等

内存相关问题

  1. 内存泄露是指分配出去的内存没有被回收回来,由于失去了对该内存区域的控制(例如你把它的地址给弄丢了),因而造成了资源的浪费。Java 中一般不会产生内存泄露,因为有垃圾回收器自动回收垃圾,但这也不绝对,Java堆内也可能发生内存泄露(Memory Leak; 当我们 new 了对象,并保存了其引用,但是后面一直没用它,而垃圾回收器又不会去回收它,这边会造成内存泄露
  2. 内存溢出是指程序所需要的内存超出了系统所能分配的内存(包括动态扩展)的上限
  3. 符号引用:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到了内存中。
  4. 直接引用:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那说明引用的目标必定已经存在于内存之中了。
  5. 双亲委派模型:表示类加载器之间的加载顺序从顶至下的层次关系,加载器之间的父子关系一般都是通过组合来实现,而不是继承。可以防止内存中出现多份同样的字节码,并确保加载顺序
  6. 双亲委派模型的工作过程是:在loadClass函数中,首先会判断该类是否被加载过,加载过则进行下一步—-解析,否则进行加载;如果一个类加载器收到了类加载器的请求,先不会自己尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求(它的搜说范围中没有找到所需的类时,子加载类才会尝试自己去加载)
  7. 静态分派和动态分派:静态分派发生在编译阶段,是指依据静态类型(变量声明时定义的变量类型)来决定方法的执行版本,例如方法重载中依据参数的定义类型来定位具体应该执行的方法;动态分派发生在运行期,根据变量实例化时的实际类型来决定方法的执行版本,例如方法重写;目前的 Java 语言(JDK1.6)是一门静态多分派、动态单分派的语言。
  8. 动态分派具体实现Java虚拟机是通过在方法区中建立一个虚方法表,通过使用方法表的索引来代替元数据查找以提高性能。虚方法表中存放着各个方法的实际入口地址,如果子类没有覆盖父类的方法,那么子类的虚方法表里面的地址入口与父类是一致的;如果重写父类的方法,那么子类的方法表的地址将会替换为子类实现版本的地址。方法表是在类加载的连接阶段(验证、准备、解析)进行初始化,准备了子类的初始化值后,虚拟机会把该类的虚方法表也进行初始化。
  9. JDK7和8中内存模型变化:JDK7中把String常量池从永久代移到了堆中,并通过intern方法来保证不在堆中重复创建一个对象;JDK7开始使用G1收集器替代CMS收集器。JDK8使用元空间来替代原来的方法区,并且提供了字符串去重功能,也就是G1收集器可以识别出堆中那些重复出现的字符串并让他们指向同一个内部char[]数组,而不是在堆中存在多份拷贝

参考链接

  1. 《Java编程思想》
  2. Java的内存回收机制
  3. java内存空间详解
  4. 理解Java垃圾回收机制
  5. 深入理解Java虚拟机
  6. Java堆内存和JVM参数
  7. Java内存划分

利用Python框架Flask实现RESTful架构服务端

发表于 2015-07-26 | 更新于 2016-07-20 | 字数统计3.2k字 | 阅读时长12分钟

不管是进行app开发还是web开发,server端的开发都是必不可少的部分,最近打算尝试一下服务端开发;考虑到开发效率以及目前主流的前后端API标准,我打算采用python的flask框架来搭建一个简单server端作为移动app的Web Service来使用,API格式采用RESTful标准,本文将讲述RESTful架构服务端的大致实现过程。

RESTful架构

  1. 什么是RESTful架构:

    • 每一个URI代表一种资源;
    • 客户端和服务器之间,传递这种资源的某种表现层
    • 客户端通过四个HTTP动词,对服务器端资源进行操作,实现”表现层状态转化”。
  2. Representational State Transfer 表现层状态转化

    • Resoure:资源—-所谓”资源”,就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务
    • URL:统一资源定位符—-要获取资源,访问它的URL就可以了 * URI:统一资源标识符—-每种资源对应一个特定的URI
    • Representation:表现层 —- 资源呈现形式,如json,text,html
    • State Trasfer:状态转移 —- http无状态协议中的GET、POST、PUT、DELETE
  3. REST架构设计六原则

    • 前后端统一接口
    • 无状态 —- 每次客户端请求必需包含完整的信息
    • 可缓存 —- 服务器端必需指定哪些请求是可以缓存的
    • 服务端客户端业务分离
    • 分层系统
    • 按需编码
  4. 优点

    • 可以利用缓存Cache来提高响应速度
    • 通讯本身的无状态性可以让不同的服务器的处理一系列请求中的不同请求,提高服务器的扩展性
    • 浏览器即可作为客户端,简化软件需求
    • 相对于其他叠加在HTTP协议之上的机制,REST的软件依赖性更小
      *不需要额外的资源发现机制,在软件技术演进中的长期的兼容性更好

API的设计实现

  1. 注册登录的Token实现
    • Token是什么:是服务端生成的一串字符串,作为客户端进行请求的一个令牌;当客户端第一次登录时,服务端会去数据库查询用户名和密码并进行对比是否正确,如果正确服务器就会生成一个Token返回给客户端,并将该token存到服务器的session里边,以后客户端只需带上这个Token前来请求数据即可,通过比对session就可以验证登录,无需再次带上用户名和密码,也不需要再查询数据库。
    • Token有什么好处:可以减轻服务器的压力,减少频繁的查询数据库,使服务器更加健壮
    • Token的生成方式:服务端通过把客户端第一次登录时传输的部分参数(例如deviceId,MAC地址)+时间戳+随机salt进行拼接之后然后生成一个散列值,返回给客户端使用sharepreference保存,以后请求数据都要带上token
  2. 传输数据的加密算法: 对用户名,密码,deviceID等重要隐私信息进行拼接然后RSA加密
    • RSA算法: RSA是一种公钥加密算法(非对称加密),它的加密和解密使用不同密钥。具体做法是这样,客户端在登录前先向服务器请求一个公钥密钥,然后用这个公钥密钥加密需要传输的数据(数据是指根据密码,用户名等生成的散列值),然后发送给服务器。服务器使用私钥密钥解密,然后与根据数据库中的用户名和密码计算出来的散列值进行比较,一致的话,登录成功。然后服务端生成token返还给客户端,该token也需要进行加密比较安全,采用AES对称加密,采用同一个密钥可以同时用作信息的加密和解密。
    • 摘要算法主要有MD5和SHA-1,又叫作Hash算法或散列算法,是一种将任意长度的输入浓缩成固定长度的字符串的算法,这个过程是不可逆的; 保证不同内容的文件生成的散列值一定不同,相同内容的文件生成的散列值一定相同
    • 数字签名主要经过以下几个过程: 信息发送者使用一单向散列函数(HASH函数)对信息生成信息摘要; 信息发送者使用自己的私钥签名信息摘要; 信息发送者把信息本身和已签名的信息摘要一起发送出去; 信息接收者通过使用与信息发送者使用的同一个单向散列函数(HASH函数)对接收的信息本身生成新的信息摘要,再使用信息发送者的公钥对信息摘要进行验证,以确认信息发送者的身份和信息是否被修改过。
    • 数字加密主要经过以下几个过程: 当信息发送者需要发送信息时,首先生成一个对称密钥,用该对称密钥加密要发送的报文; 信息发送者用信息接收者的公钥加密上述对称密钥; 信息发送者将第一步和第二步的结果结合在一起传给信息接收者,称为数字信封; 信息接收者使用自己的私钥解密被加密的对称密钥,再用此对称密钥解密被发送方加密的密文,得到真正的原文。
    • 区别数字签名和数字加密的过程虽然都使用公开密钥体系,但实现的过程正好相反,使用的密钥对也不同。数字签名使用的是发送方的密钥对,发送方用自己的私有密钥进行加密,接收方用发送方的公开密钥进行解密,这是一个一对多的关系,任何拥有发送方公开密钥的人都可以验证数字签名的正确性。数字加密则使用的是接收方的密钥对,这是多对一的关系,任何知道接收方公开密钥的人都可以向接收方发送加密信息,只有唯一拥有接收方私有密钥的人才能对信息解密。另外,数字签名只采用了非对称密钥加密算法,它能保证发送信息的完整性、身份认证和不可否认性,而数字加密采用了对称密钥加密算法和非对称密钥加密算法相结合的方法,它能保证发送信息保密性。

Flask框架

Flask框架是一个Python编写的基于Werkzeug WSGI工具箱和Jinja2模板引擎的轻量级web开发框架。Flask只保留了简单的核心,但是有着丰富的extension来实现其他功能模块以达到类似于Django这种全栈式框架的功能效果。例如ORM需要使用SQLAlchemy扩展、Oauth需要使用Oauthlib扩展, 此外还有大量的扩展用以支持数据库整合、表单验证、上传处理、缓存、生成后台和各种开放验证等等。

代码实现

下面我们将通过具体代码来运用Flask框架实现一个RESTful Web Service返回json数据以及template前端页面。该项目中有两个路由,一个用Flask-RESTful的路由形式返回json数据,另一个用Flask原生路由形式返回一个基于Jinja2模板的Flask-Bootstrap风格的注册登录页面;其中json数据通过SQLAlchemy插件从MySQL数据库中获取,然后显示到浏览器中。

配置Flask环境

  1. 新建一个Flask项目,步骤如下

    • 打开PyCharm编辑器(安装教程请Google),点击File按钮,选择new project
    • 选择左侧列表中Flask选项,修改Location中的”untitled”为你想要的项目名称,选择合适的interpreter,一般默认即可,Pycharm会自动下载安装Flask框架
    • 点击Create,可以看到项目结构:static文件夹、templates文件夹、py文件;Flask项目创建成功
  2. Mac安装Flask框架遇到的小bug
    Pycharm自动安装Flask框架时使用的是”pip install Flask”命令,但是在mac中会因为没有sudo权限导致无法安装成功,Pycharm会提示我们去terminal中输入命令行安装;然而,当我们在命令行中输入”sudo pip install Flask”时却会发现下载安装过程中依然会出现“bad md5 hash mismatch”的报错,解决步骤如下:

    • Step1:更新pip到1.3以上,输入命令”sudo pip install —upgrade pip”
    • Step2:安装Flask不使用缓存,输入命令”sudo pip install —no-cache-dir Flask”
  3. 部分需要用到的Flask插件pip安装命令

    # Flask-RESTful插件: 用于快速开发REST API
    sudo pip install flask-restful
    
    # Flask-Bootstrap插件: 用于构建Bootsrap风格的网页;也可以直接下载引用Bootstrap
    sudo pip install flask-bootstrap
    
    # Flask-Pymysql插件: 一个类似于mysqldb的Python数据库驱动插件
    sudo pip install flask-pymysql 
    
    # Flask-SQLAlchemy插件: 一个用于操作SQL数据库的ORM
    sudo pip install flask-sqlalchemy
    

配置MySQL数据库

在该项目中,我们连接MySQL中的test数据库(如果该数据库不存在请自行创建),然后新建一个user表,包含id和name两个属性,后面要显示到浏览器中的json数据就是从这个表中提取。我们可以通过直接执行MySQL命令来操作数据库,也可以在Python文件中通过SQLAlchemy来执行SQL语句操作数据库;当然这一切的前提是在你的计算机上安装MySQL并开启服务,我们推荐使用xampp集成安装环境,非常方便,而且同时适用于win和mac。下面列出常用的MySQL命令:

# 创建表user,包含id(主键)、name两个字段
create table user (
id int(4) not null primary key, name varchar(20)
) engine=innodb default charset=utf8;

# 创建表book,包含id(主键)、name、user_id三个字段,其中user_id字段为book表的外键字段名
对应user表中的id字段,一个user可能有一本或多本book
create table book (
id int(4) not null primary key, name varchar(20), user_id varchar(20)
) engine=innodb default charset=utf8;

# 给book表增加一个category字段
alter table book add category varchar(20) primary key;

# 列出表book的结构
desc book;

# 列出表book所有数据
select * from book

# 清空表book所有数据
truncate table book

# 添加外键
alter table book add constraint FK_ID foreign key(user_id) REFERENCES user(id);

# 删除外键, FK_ID为外键名
alter TABLE book DROP FOREIGN KEY FK_ID;

app.py

在app.py中:
一方面: 我们通过SQLAlchemy连接数据库并查询user表中的用户数据信息,作为json数据返回给浏览器;这一部分采用RESTful插件实现, 通过访问localhost://5000/restful就可以看到返回的json数据
另一方面: 我们在flaskTest函数中加载login.html页面,该页面通过采用bootstrap框架实现.

# coding=utf-8
__author__ = 'xiaoyong'

from flask import Flask, request, render_template
from flask.ext.bootstrap import Bootstrap
from flask_restful import fields, marshal_with, Api, Resource

from sqlalchemy import Column, String, create_engine, ForeignKey
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base

import json
from datetime import datetime

app = Flask(__name__)
api = Api(app)
bootstrap = Bootstrap(app)

# 初始化MySQL数据库连接引擎
db_engine = create_engine('mysql+pymysql://root:@localhost:3306/test')

Base = declarative_base()


# 数据库表user对应的User对象
class User(Base):
    __tablename__ = 'user'

    id = Column(String(20), primary_key=True)
    name = Column(String(20))

    # 一对多外键关系
    books = relationship('Book')


class Book(Base):
    __tablename__ = 'book'

    id = Column(String(20), primary_key=True)
    name = Column(String(20))

    user_id = Column(String(20), ForeignKey('user.id'))


# 创建DBSession类型
DBSession = sessionmaker(bind=db_engine)
# 创建DBsession对象
session = DBSession()

# Query user表中的所有行数据
db_users = session.query(User).all()
session.close()


@app.route('/flask/<user_name>', methods=['GET', 'POST'])
def flaskTest(user_name):
    # 访问http://localhost:5000/flask/xy,进入登陆页
    if user_name == 'xy' and request.method == 'GET':
        return render_template('login.html')
    # 点击login,返回用户json数据
    if request.method == 'POST' and request.form['user']:
        u = request.form['user']
        p = request.form['pw']
        dic = {'user': u, 'password': p}
        return json.dumps(dic)
    else:
        return 'error url'

# 返回的json数据模型
class restfulModel(object):
    def __init__(self, users, state):
        self.users = users
        self.state = state


# marshal-蒙版
resource_fileds = {
    'users': fields.List(fields.String),
    'state': fields.String(default=''),
    'expires': fields.Float(default=0.0),
    'date': fields.DateTime(default=str(datetime.now()))

}
# RESTful插件
class restfulTest(Resource):
    @marshal_with(resource_fileds)
    def get(self):
        user_list = []
        for user in db_users:
            user_dic = {}
            user_dic['name'] = user.name
            user_dic['id'] = user.id
            user_list.append(str(user_dic))
        return restfulModel(user_list, 'OK')

api.add_resource(restfulTest, '/restful')
if __name__ == '__main__':
    app.run(debug=True)

login.html

该注册登录页面是基于Jinja2模板和bootstrap框架实现

PS:实现该页面注册框居中的css代码:
form.form-signin {
width: 20%;
margin-left: auto;
margin-right: auto;
}

结语

上述即为通过Flask框架搭建资源服务器的大致过程,涉及到RESTful架构,SQLAlchemy ORM,Bootstrap,json处理等相关知识;我们可以看出利用Python框架搭建服务器的便捷性,很容易上手,如果需要深入学习相关知识请参考flask官网和相关extensions官网教程。

1…131415…18
CharlesXiao

CharlesXiao

在码农炼成之路不断挣扎……stay hungry……keep learning……

87 日志
18 分类
78 标签
github weibo Daijiale的个人站点
推荐阅读
  • RocksDB
  • Google FE
© 2015.05.16 – 2019 CharlesXiao
本站总访问量:
|
总访客数:
|
博客全站共169.6k字