Java中注解(Annotation)的用法。

在Java中注释的英文为Comment。而注解的英文是Annotation。

虽然注解和注释一样,也是在源代码中添加额外的补充信息。但注解都是以@开头的。

注解可以在类、字段变量、方法、接口等位置进行标记。主要为后续一些诸如:代码生成、数据校验、资源整合等工作进行铺垫。


基本注解

例如一段代码:

@Override
public String toString() {
    return "Hello World";
}

这段代码是表示重写了父类的toString()方法。如果不使用@Override代码也可以正常执行。但使用了@Override代表告诉编译器这是一个重写方法。如果我们把方法名打错,就提前报错。这可以在前期排除一些简单的问题。

如果代码不加@Override,那在我们把方法名打错的情况,这个方法就变成一个普通的方法,而不是重写方法。

注解本身是一种数据类型,是一种接口类型。是一种语法糖,其中复杂的过程由编译器来帮我们完成。

直到Java11共有11个内置注解。其中有 5 个是基本注解,它们来自于 java.lang 包。有 6 个是元注解,它们来自于 java.lang.annotation 包,自定义注解会用到元注解。

  • @Override表示重写方法
  • @Deprecated表示弃用方法
  • @SuppressWarnings用于抑制警告
  • @SafeVarargs用于抑制警告
  • @FunctionalInterface用于指定接口,保证该接口只能包含一个抽象方法。

在Java的文档注释中同样也有@deprecated,仅仅差了一个开头的大小写。虽然两者的目的差不多,都是为了表示该方法已经过时。但两者的作用却不一样文档注释作用与文档,编译器不会给出警告,注释也不直接作用于程序。而注解是直接用于修饰程序中的程序单元,如方法、类和接口等。并且注解是Java5之后才开始支持。而文档注释仅仅在程序外整理。


元注解

元注解是负责对其它注解进行说明的注解。

Java5定义了4个注解,分别是@Documented、@Target、@Retention 和 @Inherited。Java8又增加了@Repeatable和@Native两个注解。


自定义注解

基本注解和元注解,如果这两种注解不能满足你的需求,可以自定义注解。

不包含任何成员变量的注解称为标记注解,例如基本注解中的@Override注解就属于标记注解。

包含成员变量的注解称为元数据注解,因为它们可以接受更多的元数据。


注解的用法

注解的实现的原理很大的一部分是基于反射实现。

使用反射获取到类对象,进而获取构造器、字段和方法等。在源码中,类、构造器、字段和方法等均实现了AnnotatedElement接口。而这个接口包含了关于注解的一些方法。

可以通过反射来获取某个类或方法是否拥有注解。注解了些什么。需要按照这些注解整形那些步骤。

注解是一种分散式的元数据,与源代码绑定。

xml是一种集中式的元数据,与源代码无绑定。

在Java后台的开发过程中,服务器的配置项大多会存放在一个xml文件中,而Spring基于注解进行配置,从而替代了配置文件的功能。两者各有好处也有缺点。

注解有几个主要用途:

  • 生成文档,通过代码里标识的元数据生成javadoc文档。
  • 编译检查,通过代码里标识的元数据让编译器在编译期间进行检查验证。
  • 编译时动态处理,编译时通过代码里标识的元数据动态处理,例如动态生成代码。
  • 运行时动态处理,运行时通过代码里标识的元数据动态处理,例如使用反射注入实例

参考:

听说你只会用注解,不会自己写注解?

java注解的使用

代码风格

##代码风格

##次行风格和尾行风格

块的写法有两种常用方式:次行(next-line) 风格和行尾(end-of-line) 风格。

public class Test
{
	public static void main(String[] args)
	{
		System.out.println("Block Styles");
	}
}
public class Test{
	public static void main(String[] args){
		System.out.println("Block Styles");
	}
}

次行风格将括号垂直对齐,因而使程序容易阅读;而行尾风格更节省空间,并有助于避
免犯一些细小的程序设计错误。这两种风格都是可以采纳的。

对于类型和泛型的理解

类型

对于强类型语言,我们需要先定义变量类型,再使用变量。


这让我不禁让我想到去吃的肯德基疯狂星期四。肯德基的疯狂星期四里面有8个蛋挞的套餐。通常一盒蛋挞是两个装。因此如果你点了疯狂星期四的套餐,你会收到四个蛋挞盒,共计八个蛋挞。

也就是说,如果你是店员,你需要准备四个盒子来装8个蛋挞。

对于肯德基的店员来说,一个店员能装的蛋挞数量,这是拿到的盒子限制的;但一个盒子只能装两个蛋挞,这是盒子大小限制的;而盒子大小是肯德基规定的。

对于计算机的数据来说,一个整形能存储的数据大小,这是字节数限制的;但一个字节最多能存储255的正整数,这是字节大小限制的(也就是8位);而字节大小是计算机行业规定的。


但肯德基和计算机还有一个很大的不同,就是进制和统计的不同。

肯德基是十进制,四个盒子分别是2、2、2、2。这四个盒子是分别计算。

肯德基合计是2+2+2+2=8,这符合我们的常理。因为我们是分开计算的。

计算机是十六进制,四个字节分别是FF、FF、FF、FF。这四个字节是在一个整体中,为0xFFFFFFFF。

计算机合计是255+65280+16711680+4278190080=4294967295。

因此计算机显得不是那么好看。


泛型

对于面向对象也是,我们总说先定义对象类型,再实例化和使用对象。

但类型并不是对象,而是对象的模板。


假设我们的程序在单线程环境中有一段代码。我们把这段代码看作是一个面包店中一台烘焙饼干的机器。但这家面包店由于电力负载问题,一次只能有一台烘焙饼干的机器在运行。

最开始,人们发明了一个烘焙饼干的机器,但这个机器只能制作小熊形状的饼干。突然人们想吃小兔饼干,于是又发明了另一个机器,但这个机器只能制作小兔形状的饼干。

人们希望有更多的饼干形状。于是出现了越来越多的机器,用于生产不同形状的饼干。这些机器功能都非常单一,一种机器只能生产一种形状的饼干。这些机器有很多非常不方便的地方。我们以小熊饼干机器为例:

  • 机器内部有很多个小熊模具,用于压出饼干形状。如果希望制作有小兔饼干就必须全部替换机器中的小熊模具。
  • 假设制作小熊饼干的机器有潜在问题,那其他所有制作不同形状饼干的机器也可能有潜在问题。
  • 这些不同的机器占据了非常大的空间位置。
  • 维护这些机器非常的复杂和繁琐。

于是人们希望有这样一种机器——这个机器有一个卡槽,在卡槽中放入需要的模具,就可以生成需要形状的饼干。这样只需要一台机器,在适当的时候放入模具,就可以生产需要形状的饼干了。

之后机器真的发明出来了,人们称这台机器为泛型烘焙机。因为它是泛用的。放入小熊模具后,这机器就变成小熊饼干烘焙机;放入小兔模具,这机器就变成小兔饼干烘焙机。


对于程序来说,类型就像是一个烘培用的小熊饼干模具,它本身并不能食用。而用这个模具做出来的小熊饼干才是我们需要的对象,这个对象才有耳朵、有身体躯干、有它的香味、并且可以食用。

因此,泛型更像是类型的模板,也就是模具的模板。我们可以使用小熊模具制造小熊饼干,也可以使用兔子模具制造兔子饼干。我们只需要在卡槽中替换模具就行了。而这个替换模具的位置就是泛型。

泛型就像是占位符,就像泛型烘焙机中放置模具的位置。

搭建SVN服务器与客户端

搭建SVN服务器与客户端

服务器使用VisualSVN,而客户端使用TortoiseSVN。

首先,需要有一台主机安装VisualSVN,这台主机将作为服务器,为下游客户端提供版本控制服务。而下游的客户端需要安装TortoiseSVN来使用服务器提供的服务。


服务器

服务器安装

下面是VisualSVN的官网。

Subversion Server for Windows | VisualSVN Server

你可以在这里先查看VisualSVN不同版本的价格和对应的功能。当然也可以只使用免费的社区版。

Pricing | VisualSVN Server

下面是VisualSVN官网的下载地址。可以自行选择需要的版本。

Download | VisualSVN Server

你可以在下面的链接中找到官方的安装流程。在安装过程中可能会出现Select the authentication mode,也就是选择身份验证模式。如果选择Use Windows authentication则需要提供许可证密钥。在安装时需要注意。

Getting Started | VisualSVN Server

服务器配置

在VisualSVN安装完成之后,我们需要先配置服务器的网络环境。之后创建供客户端使用的存储库。

配置服务器的网络环境可以参考下面的文章。

SVN服务器远程访问 – 简书 (jianshu.com)

存储库的搭建可以参考下面的文章。

搭建SVN服务器详细教程

如果使用Windows身份验证模式,可以在安装后,查看官网的这篇文章进行配置。

How to configure Integrated Windows Authentication in VisualSVN Server | VisualSVN Help Center


客户端

下面是TortoiseSVN的官网。

Home · TortoiseSVN

下面是TortoiseSVN的下载地址。可以自行选择需要的版本。

Downloads · TortoiseSVN

安装的TortoiseSVN默认是英文的,如果需要中文,可以在下面的网址选择对应版本的汉化包进行安装。

Downloading File / – TortoiseSVN – OSDN

对汉化流程不熟悉的可以查看下面的文章。

解决SVN安装语言包后无法选择中文的问题。

在客户端中安装TortoiseSVN完成后,可以直接使用存储库的URL进行访问。

C# 控制台读取按键及组合键

C# 控制台读取按键及组合键

下面代码可以从控制台读取按键,同时也可以读取组合按键。但无法单独读取Shift、Ctrl或Alt。

// TreatControlCAsInput需要配合按键。该属性会导致Console.ReadLine()异常。
// 参考:https://docs.microsoft.com/zh-cn/dotnet/api/system.console.readkey?view=net-6.0
// Prevent example from ending if CTL+C is pressed.
// Ctrl+C视为普通按键,不会杀死控制台。
Console.TreatControlCAsInput = true;
ConsoleKeyInfo cki;
Console.WriteLine("Press any combination of CTL, ALT, and SHIFT, and a console key.");
Console.WriteLine("Press the Escape (Esc) key to quit: \n");
do
{
    cki = Console.ReadKey(intercept: true); //读取输入。默认intercept为false,表示按下的按键会被打印到控制台。
    Console.Write(" --- You pressed ");
    // 这里的"&"在非bool的情况下是位运算符。
    if ((cki.Modifiers & ConsoleModifiers.Alt) != 0) Console.Write("ALT+");
    if ((cki.Modifiers & ConsoleModifiers.Shift) != 0) Console.Write("SHIFT+");
    if ((cki.Modifiers & ConsoleModifiers.Control) != 0) Console.Write("CTL+");
    Console.WriteLine(cki.Key.ToString());
} while (cki.Key != ConsoleKey.Escape);

下面的代码用来读取组合键Alt+H。可以配合Console.TreatControlCAsInput使用,让Alt+C不中止程序。

do
{
    ConsoleKeyInfo cki1 = Console.ReadKey(true);
    if ((cki1.Modifiers & ConsoleModifiers.Alt) != 0 && cki1.Key == ConsoleKey.H)
    {
        Console.WriteLine("触发组合按键Alt+H");
    }
} while (true);

下面的代码用来读取组合键Alt+H+K。

do
{
    // 第一个按键组合,是否为Alt+H
    ConsoleKeyInfo cki1 = Console.ReadKey(true);
    bool condition1 = (cki1.Modifiers & ConsoleModifiers.Alt) != 0 && cki1.Key == ConsoleKey.H;
    // 第二个按键组合,是否为Alt+K
    ConsoleKeyInfo cki2 = Console.ReadKey(true);
    bool condition2 = (cki2.Modifiers & ConsoleModifiers.Alt) != 0 && cki2.Key == ConsoleKey.K;
    if (condition1 && condition2)
    {
        Console.WriteLine("触发组合按键Alt+H+K");
    }
} while (true);

可以再添加一个条件来限制输入。Console.KeyAvailable表示当按键操作可用时。

ConsoleKeyInfo cki;
do
{
    Console.WriteLine("\nPress a key to display; press the 'x' key to quit.");

    // Your code could perform some useful task in the following loop. However,
    // for the sake of this example we'll merely pause for a quarter second.

    // 如果按键操作可用
    while (Console.KeyAvailable == false)
        Thread.Sleep(250); // Loop until input is entered.

    cki = Console.ReadKey(true);
    Console.WriteLine("You pressed the '{0}' key.", cki.Key);
} while (cki.Key != ConsoleKey.X);

我暂时没有可以检测控制台单独按下控制键的检测方法。例如检测控制台按下Alt键的检测方法。

C#中特性(Attribute)的用法。

### C#中特性(Attribute)的用法。

特性其实类似于修饰器,在方法执行的前后可以额外执行其他的代码,而不需要主动去做额外的设计。

其用法其实就是在类或方法前加上[Attribute()]。

using System; 
public class AnyClass
{
    [Obsolete("Don't use Old method, use New method", true)]
    static void Old() { }

    static void New() { }

    public static void Main()
    {
        Old();
    }
}

上面这个例子,在这个例子中我们使用了Obsolete特性,它标记了一个不应该再被使用的程序实体。

第一个参数是一个字符串,它解释了 为什么该实体是过时的以及应该用什么实体来代替它。实际上,你可以在这里写任何文本。第二个参数告诉编译器应该把使用这个过时的程序实体当作一种错误。它的默认值是false,也就是说编译器对此会产生一个警告。

当我们尝试编译上面这段程序的时候,我们将会得到一个错误:

AnyClass.Old()' is obsolete: 'Don't use Old method, use New method'

我们也可以自己实现特性。首先要从System.Attribute派生出我们自己的特性类(一个从System.Attribute抽象类继承而来的类,不管是直接还是间接继承,都会成为一个特性类。特性类的声明定义了一种可以被放置在声明之上新的特性)。

using System; 
public class HelpAttribute : Attribute
{

}

[Help()]
public class AnyClass
{

}

注意:对一个特性类名使用Attribute后缀是一个惯例。当我们把特性添加到一个程序实体,是否包括Attribute后缀是我们的自由。编译器会首先在System.Attribute的派生类中查找被添加的特性类。如果没有找到,那么编译器会添加Attribute后缀继续查找。

接下来把上面的特性添加到具体的程序实体中。

using System; 
public class HelpAttribute : Attribute
{
    public HelpAttribute(String Descrition_in)
    {
        this.description = Description_in;
    }
    protected String description;
    public String Description
    {
        get
        {
            return this.description;

        }
    }
}

[Help("this is a do-nothing class")]
public class AnyClass
{

}

在这个例子中,我们给HelpAttribute特性类添加了一个属性并且在后续的部分中会在运行时环境中查寻它。

Java中的堆与栈——变量和对象的生存空间

###Java中的堆与栈——变量和对象的生存空间

内存中有两个程序员需要注意的区域:

  • 对象的生存空间——堆(heap)
  • 方法调用及变量的生存空间——栈(steak)

变量用不同的维度看待有不同的叫法。

当一些变量们被声明在类中时,它们代表每个独立对象的“字段”,他们被称为实例变量。实例变量存在于所属的对象中。

区域变量也被称作栈变量,表示该变量是局部变量还是实例变量。

局部变量和方法的参数都是被声明在方法中。他们是暂时的,且生命周期只限于方法被放在栈上的这段期间(也就是方法调用至执行完毕为止)。

当你调用一个方法时,该方法会放在调用栈的栈顶。实际上被放到栈顶的是堆栈块,它带有方法的状态,包括执行到那一行程序及其所有的局部变量值。

对象存在于堆中,不论对象是否声明或创建。如果局部变量是个对该对象的引用,只有变量本身会放在栈上。对象本身只会在堆上。

无论是实例变量还是局部变量,只要是对象都会在堆上。

当要新建一个对象时,Java必须在堆上帮对象找一个位置。至于需要多少空间取决于该对象多有实例变量的空间。实例变量存在于对象所属的堆空间上。

对象的实例变量是存放于该对象中的。如果实例全部都是基本数据类型,则Java会依据变量空间大小开辟空间。如果实例变量是个对象,则会保存其引用的值而不是对象本身。如果只声明而没有赋值,则会只留下变量的空间。直到引用变量被赋值一个新的对象,这个引用的对象才会在堆上占有空间。

记录最近的针对网关报文的Excel处理工具设计路线。

##记录最近的针对网关报文的Excel处理工具设计路线。

需求:

首先说下开发的需求:最近有个产品是汽车网关。汽车网关主要功能和路由器差不多,也就是查询某条报文是否需要被转发。虽然没有家用路由器那么多的协议和额外功能,但报文比较多,一般汽车上也会有400-600条不同的报文。有些报文需要以高频的速度转发到不同的车身节点上。也就是说网关需要了解这几百条报文从哪来,到哪去。网关要快速响应并转发。

这几百条报文在代码中实现不算复杂,即使只用Switch和if语句也可以实现。但考虑到后续的维护,这些判断语句一定会给我们照成网关查询响应速度慢和报文变更麻烦等一系列问题。因此我希望可以将设计和维护聚焦在报文和后续变更上。而不需要考虑报文转发的代码逻辑。

假设我们有10条新增的报文需要被转发,那我们只需要在数据结构(或数据库)中添加这10条报文,而不需要修改网关的底层转发代码。如果我们使用Switch和if语句,我们不需要数据结构(或数据库),但得不断的往转发代码中添加条件,这会导致代码臃肿,可读性也会直线下降。

所以我需要设计一个简单的工具,来统一管理报文表和转发代码的关系。这个工具可以让我导入路由表后生成一份报文转发代码。这份代码我可以直接合成进我的网关工程中。网关每次收到报文后都会执行这份代码,看是否需要被转发之后进行后续的动作。

这样一来对变更的管理就从对代码管理变成了对表格的管理。如果有报文变更,我只需要维护报文表就行了。

目标:

假设需求上有600条不同的报文,如果使用巨大的switch和if语句是不合理也的。因为网关作快速响应的硬件,如果查询时间太长是会影响性能的。而且网关路由的变更是非常频繁的,经常需要添加、删除或对转发条件做出修改。

我有几个设计的目标和对应的方法:

  • 网关的查询快捷——需要使用二分查询算法来查询报文。
  • 报文变更方便——将客户需求整理成Excel。维护Excel就相当于维护程序。
  • 报文变更不影响网关程序结构——使用DAO方式生成C文件。将C文件合成进网关工程。实现模块化。

设计:

报文有几个主要属性,分别是:源地址、报文ID、报文长度、目的地址、周期。

还有有些次要属性,属于标定项,意思是当达成特定条件时特定的报文才需要被转发(或者说不应该被转发)。

我选择用Python来写。主要有几个原因:

  • 比起其他语言我Python会的多点。
  • Python有一些现成的Excel处理库,可以很方便的使用。
  • 我对界面的设计要求不高,即使后续添加新的功能也可以使用简单的界面添加。

下面是整个设计思路:

根据客户提供的需求把600条报文整理后放进Excel中,软件从Excel文件中提取数据,做出排序去重和判断后生成一份我们需要的C语言文件。

  • 我需要软件在Windows7上运行。所以选用3.8版本的Python。
  • 界面使用Python原生的tkinter库。配合外部的TKinterDesigner做界面设计。
  • Excel文件的数据提取使用第三方的xlwings库。
  • 数据提取后使进行数据验证,排序和去重。这部分使用一些算法就可以实现。
  • DAO功能使用Python中string库的Template模板替换。
  • 软件的打包可以使用Python原生的PyInstaller库,也可以使用TKinterDesigner直接发布。

其中还需要用到一些小东西。

  • tkinter是单线程界面,配合外部Excel提取需要使用多线程。
  • tkinter可以添加进度条让用的人看起来舒服一些。
  • 界面提醒可以使用日志打印也可以使用输出重定向,把程序运行情况打印到界面上或写入日志文件。
  • DAO的实现可以配合Json的解析。让模板替换的自定义程度更高一些。

Python中函数装饰器@

##Python中函数装饰器@

Python中的@是函数修饰器,它其实是种语法糖。本身并不会语言的功能产生影响,但可以更方便程序员的使用。让代码看起来更清晰和简洁。

def funA(fn):
    ...


def funB():
    ...


funB = funA(funB)

在上面这段代码中我们需要注意几个地方:

  • 这里定义了两个函数,分别是funA和funB。而funA是有参数的。
  • 在这段代码的最后还执行了一行语句。函数funB被当作参数传入了funA。并且,把funB重新赋值成了funA(funB)的返回值。

在上面的代码中有一件事悄悄发生了,那就是函数funB的地址已经找不到了。

代码中的函数funB已经成为了funA(funB)的返回值。如果返回值为None,那funB则为None。

如果使用了函数装饰器@,那上面的代码与下面的代码是等价的。

#funA 作为装饰器函数
def funA(fn):
    ...


#funB 作为被装饰函数,funB 被 funA 装饰
@funA
def funB():
    ...

这段代码同样有值得注意的地方:

  • 函数funA是必须有参数的,因为它需要把函数funB作为参数传入。
  • 语句funB = funA(funB)似乎“消失”了。但这条语句依然会被执行,只是被隐藏了。

结合上面两段代码来看,函数装饰器@的作用就是把被装饰函数传给装饰函数,以此来进行功能上的扩展。但需要强调,被装饰函数会被重新赋值为装饰器的返回值!


##单个修饰器,修饰不带参数的函数

函数装饰器@经常可以被用来,插入日志、性能测试、事务处理、记录时间戳等等。

假设现在我们新写了个功能,需要用日志进行测试。但我们不应该直接把日志功能加入到新的函数中。因为这会导致功能耦合,代码的可读性非常的差。在这种情况下,我们可以来看个使用函数装饰器@的例子:

def log(function):
    def wrapper():
        print("log start...")
        function()
        print("log end...")
    return wrapper
 
@log
def test():
    print("test...")
 
test()

代码执行结果如下:

log start...
test...
log end...

上面这段代码等价于:

def log(function):
    def wrapper():
        print("log start...")
        function()
        print("log end...")
    return wrapper
 
def test():
    print("test...")

test = log(test)
test()

因为函数log返回了一个函数给test,实际上也就是返回了函数的地址给test。这也test才能被调用。如果函数log返回None,那test的地址内容也就是None,会抛出错误:

TypeError: ‘NoneType’ object is not callable

因为标识符test为None,而我们却想把空值None当作函数来调用,这当然是不可行的。


##单个修饰器,修饰带参数的函数

上面被修饰的函数是没有传入参数的,如果我们新写的函数是有传入参数的怎么办呢?我们可以这样写:

def log(function):
    def wrapper(*arg, **kwargs):
        print("log start...")
        function(*arg, **kwargs)
        print("log end...")

    return wrapper


@log
def test(arg):
    print(arg)


test("test_num")

代码的执行结果如下:

log start...
test_num
log end...

使用“*arg, **kwargs”的话被装饰函数test的所有参数都会被传入到装饰器中。


##多个修饰器

一个被函数可以被多个修饰器修饰。假设,我某个函数我既要观察日志,也需要记录时间戳。我们就可以使用函数修饰器来进行多个事务上的处理。

def log(function):
    def wrapper(*arg, **kwargs):
        print("log start...")
        function(*arg, **kwargs)
        print("log end...")

    return wrapper


def time_stamp(function):
    def wrapper(*arg, **kwargs):
        print("time start...")
        function(*arg, **kwargs)
        print("time end...")

    return wrapper


@time_stamp
@log
def test(arg):
    print(arg)


test("test_num")

代码的执行结果如下:

time start...
log start...
test_num
log end...
time end...

在上面的修饰器会先被执行。


还需要考虑一个问题,修饰器可以嵌套么?

def log(function):
    def wrapper(*arg, **kwargs):
        print("log start...")
        function(*arg, **kwargs)
        print("log end...")



    return wrapper


@log
def time_stamp(function):
    def wrapper(*arg, **kwargs):
        print("time start...")
        function(*arg, **kwargs)
        print("time end...")


    return wrapper

@time_stamp
def test(arg):
    print(arg)

对于上面这段代码,并没有按照预期的那样正常运行,会抛出错误。即使在内置函数wrapper加上返回值return function,也不会按照预期的那样执行函数test的内容。

尽量还是不要使用嵌套的函数修饰符。

Python的应用程序打包方式——PyInstaller

##Python的应用程序打包方式——PyInstaller

PyInstaller并不是一个Python的原生模块,因此需要自己下载。可以使用pip的方式在线下载或自行下载.whl文件离线安装PyInstaller模块。

但无论是那种方式,在准备好打包环境后才正式开始阅读这篇文章。


当你拥有一个Python项目,你想把你的项目打包成一个.exe的运行程序。这样你就可以直接其他人的电脑上运行,无需安装Python解释器,也无需进行编译。

打包的过程结束后,可能会出现几个东西,分别是:__pycache__文件夹、build文件夹、dist文件夹和一个单独的.spec文件。

  • __pycache__文件夹,名称很直白,Python Cache,也就是Python缓存的意思。这个文件会出现在你项目自身的目录里。Python解释器会将* .py 脚本文件进行编译,并将结果保存到__pycache__目录中。下次运行工程时,若解释器发现这个.py 脚本没有修改过,就会跳过编译这文件,直接运行以前生成的保存在__pycache__的.pyc 文件。这对于大型工程是有好处的,可以大大缩短项目运行前的准备时间。如果不想暴露源码也可以使用.pyc 文件文件。但这个文件夹本身不属于PyInstaller产生的,而是Python解释器产生的。
  • build文件夹,是PyInstaller在打包程序时产生的临时文件夹。
  • dist文件夹,是PyInstaller打包出来的可执行文件目录。打包完成后的程序会放在该目录中。一般有两种方式,一种方式是生成目录,另一种方式是生成单一的.exe应用程序。
  • .spec文件,是PyInstaller为打包而准备的配置文件。就像一份清单一样,你想指定你的项目如何打包,打包成什么样。例如是否使用图标、项目使用的资源文件在哪、如何指定项目依赖等等。这些都可以先写入.spec文件中,之后使用PyInstaller的命令,根据.spec里的内容将你的项目打包成你想要的应用程序。似于cmake的.makefile文件,都是用于控制编译构建过程的配置文件。

-h,–help查看PyInstaller的帮助信息。
-F,-onefile生成单个的可执行文件。
-D,–onedir生成一个目录(包含多个文件)作为可执行程序,这也是默认方式。
-o DIR,–out=DIR指定.spec文件生成的目录。如果没有指定,则默认在当前目录生成.spec 文件
-n NAME,–name=NAME指定项目(产生的.spec)名字。如果省略该选项,那么第一个脚本的主文件名将作为.spec的名字。
-w,–windowed,–noconsolc指定程序运行时不显示命令行窗口(仅对 Windows 有效)。

将项目打包成单一可执行文件和文件夹有什么区别么?

  • 单一可执行文件比文件夹的启动时间要长。因为当程序运行时,单一的可执行文件需要解压程序的第三方依赖文件到临时文件夹中。
  • 单一可执行文件的文件结构和工程目录是一样的。但是生成文件夹就不一样了,若程序中包含相对路径,这个相对路径是文件夹目录的,这点需要注意。

有更多问题可以在PyInstaller的wiki中查看:https://github.com/pyinstaller/pyinstaller