GithubHelp home page GithubHelp logo

blog's Introduction

Hi there 👋

blog's People

Contributors

duyuanch avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

blog's Issues

C语言switch详解

C语言switch详解

switch语句允许测试变量与值列表的相等性,每个值称之为案例或者case,程序会检查switch后面的值并且与case后面的值比对,如果相等则执行后面的代码或代码块

语法

switch在C语言中的语法如下

switch(expression) {

   case constant-expression  :
      statement(s);
      break; /* optional */

   case constant-expression  :
      statement(s);
      break; /* optional */

   /* you can have any number of case statements */
   default : /* Optional */
   statement(s);
}
  • switch语句中使用的表达式必须具是intenum类型,否则如float等其他数据类型是无法通过的编译的,因为编译器需要switch后面的语句和case后面的值精确匹配,而计算机无法精确表达一个float数据类型
  • switch可以任意个case语句(包括没有), 值和语句之间使用:分隔
  • case后面的值必须是int常量值,或者返回结果为int类型的表达式,以下代码无法编译通过
switch (1) {
    case 1.1:
        break;
}
int a;
scanf("%d", &a);
switch (a) {
    case a + 1:
        break;
}
  • switch后面的变量值和case后面的常量值匹配相等后,case后面的代码将会被执行,直到break语句被执行后跳出switch代码块

  • break不是必须的,如果没有break,则执行完当前case的代码块后会继续执行后面case代码块的内容,直到执行break才可以退出

  • switch有一个默认的情况,我们用default关键词表示,当switch后面的变量和所有case后面的常量都不匹配的情况下,默认执行default后面的语句

switch_statement

Example 1

#include <stdio.h>

int main () {

    /* local variable definition */
    char grade;
    scanf("%c", &grade);

    switch(grade) {
        case 'A' :
            printf("Excellent!\n" );
            break;
        case 'B' :
        case 'C' :
            printf("Well done\n" );
            break;
        case 'D' :
            printf("You passed\n" );
            break;
        case 'F' :
            printf("Better try again\n" );
            break;
        default :
            printf("Invalid grade\n" );
    }

    printf("Your grade is  %c\n", grade );

    return 0;
}

Example 2

#include <stdio.h>
int main() {

    printf("Please input your grade(1-100):");
    int grade;
    scanf("%d", &grade);

    switch (grade / 10) {
        case 10:
        case 9:
            printf("A\n");
            break;

        case 8:
        case 7:
            printf("B\n");
            break;

        case 6:
        case 5:
            printf("C\n");
            break;
        default:
            break;

    }
    return 0;
}

插入排序 - Insertion Sort

15923110-bb6d1e8cbeeda277

#include <stdio.h>
#include <stdbool.h>

void insertSort(int array[], int n) {
    for (int i = 1; i < n; i++) {
        int insertValue = array[i];
        int j = i - 1;
        while (j >= 0 && insertValue < array[j]) {
            array[j + 1] = array[j];
            j--;
        }
        array[j + 1] = insertValue;
    }
}

void printArray(int array[], int n) {
    for (int i = 0; i < n; ++i) {
        printf("%d\t", array[i]);
    }
}

int main() {
    int array[] = {9, 7, 5, 3, 1, 8, 6, 4, 2, 0};
    insertSort(array, sizeof(array) / sizeof(array[0]));
    printArray(array, sizeof(array) / sizeof(array[0]));
    return 0;
}

计算数列1+(1+2)+(1+2+3)+...+(1+2+3+....n)和

计算数列1+(1+2)+(1+2+3)+...+(1+2+3+....n)

如果你对循环特别熟悉,这个数列求和就显得特别简单,一个循环嵌套就搞定了,代码如下

#include <stdio.h>
int main(){

    int n;
    printf("please input n:");
    scanf("%d", &n);

    int sum  = 0;
    for (int i = 0; i <= n; ++i) {
        int subSum = 0;
        for (int j = 0; j <= i; ++j) {
            subSum += j; //累加第i项的和
        }
        sum += subSum;
    }
    printf("sum is %d\n", sum);
    return 0;
}

但是问题来了,不允许用循环嵌套,如何用一个循环做,代码如下

#include <stdio.h>
int main(){

    int n;
    printf("please input n:");
    scanf("%d", &n);

    int sum = 0;
    int subSum = 0;
    for (int i = 1; i <= n; ++i) {
        subSum += i; //累加第i项的和
        sum += subSum; //累加前i项的和
    }
    printf("sum is %d\n", sum);
    return 0;
}

一键脚本搭建SS服务器

前言

写了一个Shaodwsocks一键服务器搭建脚本

https://github.com/shellhub/shellhub

连接VPS服务器

ssh root@your_vps_ip_address

下载脚本并安装

复制下面的代码在vps执行

wget -N --no-check-certificate https://raw.githubusercontent.com/shellhub/shellhub/master/proxy/shadowsocks.sh && chmod +x shadowsocks.sh && ./shadowsocks.sh

输入连接密码

直接回车默认为shellhub

******************************************************
* OS     : Debian Ubuntu CentOS                      *
* Desc   : auto install shadowsocks on CentOS server *
* Author : https://github.com/shellhub               *
******************************************************

Password used for encryption (Default: shellhub):

输入端口号

该端口号为服务器监听端口号,可以手动输入,或者直接回车默认生成

Server port(1-65535) (Default: 19058):4454

选择加密方式

默认为aes-256-cfb,可以选择对应的数字

1:	aes-128-cfb
2:	aes-192-cfb
3:	aes-256-cfb
4:	chacha20
5:	salsa20
6:	rc4-md5
7:	aes-128-ctr
8:	aes-192-ctr
9:	aes-256-ctr
10:	aes-256-gcm
11:	aes-192-gcm
12:	aes-128-gcm
13:	camellia-128-cfb
14:	camellia-192-cfb
15:	camellia-256-cfb
16:	chacha20-ietf
17:	bf-cfb
Select encryption method (Default: aes-256-cfb):3

安装完成

会生成如下配置信息,分别为服务器ip地址,端口号,加密方式,密码,还有一个包含所有配置信息的链接(ss开头)

Install completed
ip_address:	192.168.23.3
server_port:	4454
encryption:	aes-256-cfb
password:	shellhub
ss_link:	ss://YWVzLTI1Ni1jZmI6c2hlbGxodWJAMTkyLjE2OC4yMy4zOjQ0NTQK

同时会生成二维码,客服端可以直接扫描

screenshot from 2018-08-09 11-09-43

oh-my-zsh

原文更新地址

#25

Oh-My-Zsh!提高你CLI(Command-line interface
)的神奇工具 - Ubuntu教程

1_dcfi6nexitpjcfpgdloonw

我是命令行界面的忠实粉丝......我不喜欢使用我的电脑鼠标!这促使我寻找出色的工具来增强我在CLI上的用户体验,一次偶然的机会机会在YouTube上观看了国外YouTuber使用该工具,促使我对他产生了兴趣.本教程基于Ubuntu Linux,其他操作系统差不多

by the way,关注我的YouTube频道呗

https://www.youtube.com/c/CSWikiTech

以下是oh-my-zsh部分功能

  • 命令验证
  • 在所有正在运行的shell**享命令历史记录
  • 拼写纠正
  • 主题提示(Agnoster,RobbyRussell,......)
  • 目录历史
  • 通过zshenv,zprofile,zshrc,zlogin和zlogout启动/关闭脚本
  • 强大的自动完成功能。您可以使用TAB键浏览不同的选项,然后使用enter键选择正确的文件夹。例如Bash会打印所有选项。

peek 2018-08-31 09-45

  • 添加插件:例如Git插件包含大量有用的Git别名。 此插件显示活动分支并提供有关Git状态的可视反馈:

peek 2018-08-31 09-51

  • 绿色:如果没有发生变化的分支
  • 黄色:未跟踪文件
  • 带有加号图标的黄色:准备提交的文件

安装指南

我在我的Linux Mint上执行此安装指南。为了向您展示Oh-My-Zsh的基本功能,我将安装Git插件(Git-core)。此插件提供有关项目的Git状态的可视反馈。

  1. 安装必备软件包
$ sudo apt install git-core zsh
  1. 根据官方脚本安装Oh-My-Zsh
# 通过curl
sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
#通过wget
sh -c "$(wget https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)"
  1. 安装Powerline字体以使用图标为CLI增添趣味
$ sudo apt install fonts-powerline
  1. 将主题从'robbyrussell'改为'agnoster'为传奇的Oh-My-Zsh主题
$ vim ~/.zshrc

想要看到修改后的主题结果,执行下面命令

$ source ~/.zshrc

peek 2018-08-31 10-04

  1. 现在的主题提示信息太长了,我们去掉用户名和主机名

peek 2018-08-31 10-16

  1. 修改默认shell,这样下次打开Terminal的时候就默认使用zsh而不是bash
$ chsh -s $(which zsh)

安装插件

所有插件都列在Plugins,自定义插件可以安装在〜/.oh-my-zsh/custom/plugins中。要使用插件,只需将其添加到〜/.zshrc文件中的插件列表即可。明智地添加,因为太多的插件会减慢shell的启动速度。插件之间使用空格分割。

colored-man-pages

在这个例子中,我安装了一个有用的插件,为你的手册页提供颜色突出显。
peek 2018-08-31 10-29

zsh-syntax-highlighting

另一个很棒的插件是shell的语法高亮。除此之外,此插件还能够验证命令的正确性
screenshot from 2018-08-31 10-38-54

# 安装
cd /home/shellhub/.oh-my-zsh/custom/plugins
git clone https://github.com/zsh-users/zsh-syntax-highlighting
# 添加到.zshrc配置文件中的plugins中
vim ~/.zshrc

# 例子
plugins=(
  git
  autojump
  colored-man-pages
  zsh-syntax-highlighting
  zsh-autosuggestions
)

zsh-autosuggestions

您还可以使用zsh-autosuggestions来完成命令。它根据您的命令历史记录建议命令。很有用!要选择建议的命令,请按向右箭头键。

screenshot from 2018-09-04 08-50-25

安装方式和zsh-syntax-highlighting一样

$ git clone https://github.com/zsh-users/zsh-autosuggestions

然后添加zsh-syntax-highlighting到插件列表中(vim ~/.zshrc)

autojump

autojump可以实现快速跳转到目标目录,如下所示

peek 2018-09-04 10-29

然后别忘记添加到~/.zshrc配置文件中

vim ~/.zshrc

关注一下别名

目录历史

Oh-My-Zsh会自动记住您访问过的最后20个目录。您可以使用dirs -vd来按时间顺序列出历史记录。

您可以使用cd +1转到上一个目录,依此类推,如下图我们还可以直接输入数字进行跳转到对应的目录

screenshot from 2018-09-04 09-03-32

其他有趣的别名

/ -> cd /
~ -> cd ~
.. -> cd ..
... -> cd ../..
.... -> cd ../../..

我相信你已经找到规律了吧

take test_folder # 创建一个文件夹并进入这个文件夹,效果和下面类似
mkdir test_folder && cd test_folder
take folder1/folder2/folder3
x # 解压tar, bz2, rar, gz, tbz2, tgz, zip, Z, 7z各种压缩文件

## 更新和卸载

更新

upgrade_oh_my_zsh

卸载

uninstall_oh_my_zsh

更多学习资源

总结

Oh-My-Zsh太爽了

史上最详细的gson教程

JSON简介

JSON(JavaScript Object Notation)是一种由道格拉斯·克罗克福特构想和设计、轻量级的数据交换语言,该语言以易于让人阅读的文字为基础,用来传输由属性值或者序列性的值组成的数据对象。尽管JSON是JavaScript的一个子集,但JSON是独立于语言的文本格式,并且采用了类似于C语言家族的一些习惯。
JSON 数据格式与语言无关,脱胎于 JavaScript,但目前很多编程语言都支持 JSON 格式数据的生成和解析。JSON 的官方 MIME 类型是 application/json,文件扩展名是 .json。

简单的JSON数据

一个Person对应的JSON对象

{
  "firstName": "John",
  "lastName": "Smith",
  "isAlive": true,
  "age": 27,
  "address": {
    "streetAddress": "21 2nd Street",
    "city": "New York",
    "state": "NY",
    "postalCode": "10021-3100"
  },
  "phoneNumbers": [
    {
      "type": "home",
      "number": "212 555-1234"
    },
    {
      "type": "office",
      "number": "646 555-4567"
    },
    {
      "type": "mobile",
      "number": "123 456-7890"
    }
  ],
  "children": [],
  "spouse": null
}

JSON的优点

  • 更加易读
  • 更轻量级(传输,编写,解析)
  • 节约成本(减少网络流量,节约宽带)
  • 支持平台广(几乎所有编程语言都支持JSON)

JSON的缺点

配置文件用XML较多,如Android里面通过XML定义布局,主流框架(Spring家族)用XML较多

开始解析JSON

本例我们使用https://api.github.com/ 测试

{
  "current_user_url": "https://api.github.com/user",
  "current_user_authorizations_html_url": "https://github.com/settings/connections/applications{/client_id}",
  "authorizations_url": "https://api.github.com/authorizations",
  "code_search_url": "https://api.github.com/search/code?q={query}{&page,per_page,sort,order}",
  "commit_search_url": "https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}",
  "emails_url": "https://api.github.com/user/emails",
  "emojis_url": "https://api.github.com/emojis",
  "events_url": "https://api.github.com/events",
  "feeds_url": "https://api.github.com/feeds",
  "followers_url": "https://api.github.com/user/followers",
  "following_url": "https://api.github.com/user/following{/target}",
  "gists_url": "https://api.github.com/gists{/gist_id}",
  "hub_url": "https://api.github.com/hub",
  "issue_search_url": "https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}",
  "issues_url": "https://api.github.com/issues",
  "keys_url": "https://api.github.com/user/keys",
  "notifications_url": "https://api.github.com/notifications",
  "organization_repositories_url": "https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}",
  "organization_url": "https://api.github.com/orgs/{org}",
  "public_gists_url": "https://api.github.com/gists/public",
  "rate_limit_url": "https://api.github.com/rate_limit",
  "repository_url": "https://api.github.com/repos/{owner}/{repo}",
  "repository_search_url": "https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}",
  "current_user_repositories_url": "https://api.github.com/user/repos{?type,page,per_page,sort}",
  "starred_url": "https://api.github.com/user/starred{/owner}{/repo}",
  "starred_gists_url": "https://api.github.com/gists/starred",
  "team_url": "https://api.github.com/teams",
  "user_url": "https://api.github.com/users/{user}",
  "user_organizations_url": "https://api.github.com/user/orgs",
  "user_repositories_url": "https://api.github.com/users/{user}/repos{?type,page,per_page,sort}",
  "user_search_url": "https://api.github.com/search/users?q={query}{&page,per_page,sort,order}"
}

使用GSON解析JSON

在项目中添加gson依赖

  • Maven
<dependency>
  <groupId>com.google.code.gson</groupId>
  <artifactId>gson</artifactId>
  <version>2.8.5</version>
</dependency>
  • Gradle (Android platform)
dependencies {
    implementation 'com.google.code.gson:gson:2.8.5'
}

Room SQLite持久层框架

原文链接

前言

Android中提供了SQLite数据库进行数据的持久化 ,并提供了对应API访问数据库,而Room框架提供了SQLite数据访问抽象层,为高效的数据库访问层带来便捷

APP可以缓存用户数据,当APP离线时便从SQLite读取数据,当重新连线时即可完成和服务器数据的同步

谷歌官方强烈推荐使用Room框架操作SQLite数据库

Room architecture diagram

Hello World

首先在build.gradle中添加必要依赖

dependencies {
    def room_version = "1.1.1"

    implementation "android.arch.persistence.room:runtime:$room_version"
    annotationProcessor "android.arch.persistence.room:compiler:$room_version" // use kapt for Kotlin

    // optional - RxJava support for Room
    implementation "android.arch.persistence.room:rxjava2:$room_version"

    // optional - Guava support for Room, including Optional and ListenableFuture
    implementation "android.arch.persistence.room:guava:$room_version"

    // Test helpers
    testImplementation "android.arch.persistence.room:testing:$room_version"
}

创建实体类User,@Entity表示该类对应数据库中的表,@ColumnInfo后面的name属性对应数据库中的字段名,并实现该实体类的GetterSetter方法

@Entity
public class User {
    @PrimaryKey
    private int uid;

    @ColumnInfo(name = "first_name")
    private String firstName;

    @ColumnInfo(name = "last_name")
    private String lastName;

    public int getUid() {
        return uid;
    }

    public void setUid(int uid) {
        this.uid = uid;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return "User{" +
                "uid=" + uid +
                ", firstName='" + firstName + '\'' +
                ", lastName='" + lastName + '\'' +
                '}';
    }
}

创建实体类对应的daoUserDao,完成User的增删改查(CRUD)接口定义,@Dao注解定义一个dao层,参数赋值(传递)使用:clumn_name进行赋值

@Dao
public interface UserDao {
    @Query("SELECT * FROM user")
    List<User> getAll();

    @Query("SELECT * FROM user WHERE uid IN (:userIds)")
    List<User> loadAllByIds(int[] userIds);

    @Query("SELECT * FROM user WHERE first_name LIKE :first AND "
            + "last_name LIKE :last LIMIT 1")
    User findByName(String first, String last);

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertAll(User... users);

    @Delete
    void delete(User user);
}

创建AppDatabase@Database注解表示这是一个数据库操作类,entities对应Entity实体类,version用于数据库版本升级,并在该抽象类中定义一个返回dao层的抽象方法

@Database(entities = {User.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}

初始化用于操作数据库的实例对象AppDatabase,需要注意的是不能在主线程中初始化,必须新开启一个线程进行初始化,否则会报错,或者无法创建数据库

new Thread(new Runnable() {
    @Override
    public void run() {
        AppDatabase db = Room.databaseBuilder(getApplicationContext(),
                AppDatabase.class, "database-name").build();
    }
}).start();

测试

增加,也可以传一个User数组

for (int i = 0; i < 10; i++) {
    User user = new User();
    user.setUid(i);
    user.setFirstName("Shell" + i);
    user.setLastName("Hub" + i);
    insertAll(db, user);
}

如果报以下错误,修改dao层的注解为@Insert(onConflict = OnConflictStrategy.REPLACE)

android.database.sqlite.SQLiteConstraintException: UNIQUE constraint failed: User.uid (code 1555 SQLITE_CONSTRAINT_PRIMARYKEY)

查询所有数据

for (User user : db.userDao().getAll()) {
    System.out.println(user);
}

other...

Room单例模式

最好使用设计模式中的单例模式获取数据库实例,因为每次获取数据库实例都很耗时并且耗内存,我们可以自定义一个类继承Application并定义一个public static方法获取数据库实例

public class App extends Application {
    private static Context context;
    private static final String DATABASE_NAME = "SHELLHUB";
    private static AppDatabase DB_INSTANCE;
    @Override
    public void onCreate() {
        super.onCreate();
        context = getApplicationContext();

        new Thread(new Runnable() {
            @Override
            public void run() {
                DB_INSTANCE = Room.databaseBuilder(getApplicationContext(),
                        AppDatabase.class, DATABASE_NAME).build();

            }
        }).start();
    }

    public static AppDatabase getDB() {
        return DB_INSTANCE;
    }
}

深入浅出Java Object源码

Java中的Object

Java中的Object类位于java.lang包中,每一个Java类直接或者间接继承自Object,如果一个类没有继承任何类,那么该类默认直接继承Object,如果一个类继承了某一个类,那么Object间接继承了Object,因此所有Java对象都可以访问Object中定义的方法,因此Object是所有类的顶级父类。

Object源码解析

toString方法,返回该对象的String表示,Object中的toString方法返回类名+@+hashCode的无符号16进制

public String toString() {
    return getClass().getName() + "@" + Integer.toHexString(hashCode());
}

推荐开发中一般都会重写子类中的toString方法,使其更有意义,注意当输入对象时,默认调用对象的toString方法

Student s = new Student();

// Below two statements are equivalent
System.out.println(s);
System.out.println(s.toString());

println源码如下

/**
 * Prints an Object and then terminate the line.  This method calls
 * at first String.valueOf(x) to get the printed object's string value,
 * then behaves as
 * though it invokes <code>{@link #print(String)}</code> and then
 * <code>{@link #println()}</code>.
 *
 * @param x  The <code>Object</code> to be printed.
 */
public void println(Object x) {
    String s = String.valueOf(x);
    synchronized (this) {
        print(s);
        newLine();
    }
}

hashCode方法详解,对于每一个对象,JVM生成一个独一无二的数字-hashCode,它为不同的对象返回不同的整数,一个普遍错误的理解就是该方法返回对象的内存地址,这样的理解是不对的,他使用某种算法把对象内存地址转换为整数,即hashCode,hashCode方法是native的,因为java无法操作内存地址,所以java底层是使用C/C++访问对象的内存地址,故他的源码无法观看,你需要下载openjdk才可以查看源码
hashCode的使用: 返回用于搜索集合中的对象的哈希值。 JVM(Java虚拟机)使用哈希码方法,同时将对象保存为散列相关的数据结构,如HashSet,HashMap,Hashtable等。基于哈希码保存对象的主要优点是搜索变得容易。

public native int hashCode();

不能根据hashCode值判断是否是同一个对象,如以下代码,虽然他们的hashCode是一样的,但是他们不是同一个对象

String firstStr = new String("hashCode");
String secondStr = new String("hashCode");

System.out.println(firstStr == secondStr); //false
System.out.println(firstStr.hashCode()); //147696667
System.out.println(secondStr.hashCode()); //147696667

为什么他们的hashCode是一样的呢,看源码如下

/**
 * Returns a hash code for this string. The hash code for a
 * {@code String} object is computed as
 * <blockquote><pre>
 * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
 * </pre></blockquote>
 * using {@code int} arithmetic, where {@code s[i]} is the
 * <i>i</i>th character of the string, {@code n} is the length of
 * the string, and {@code ^} indicates exponentiation.
 * (The hash value of the empty string is zero.)
 *
 * @return  a hash code value for this object.
 */
public int hashCode() {
    //hash默认是0
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        //s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]
        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

为什么源码中使用了质数31,这里找了一片博文why 31
所以从源码得出,相同的字符串内容的hashCode的值是一样的,即使他们不是同一个对象,再看以下比较特殊的几个例子,结果看源码就可以知道了

System.out.println("".hashCode()); //0
System.out.println("a".hashCode()); //97
System.out.println("A".hashCode()); //65
System.out.println("aA".hashCode()); // 3072

equals方法,Object默认是判断是否是同一个对象,所以如果子类没有重写,默认比较是否是同一个对象

public boolean equals(Object obj) {
    return (this == obj);
}

例子

Object object = new Object();

System.out.println(object.equals(object)); //true
System.out.println(object.equals(null)); //false

getClass方法,返回“this”对象的类对象,用于获取对象的实际运行时类。它还可用于获取此类的元数据。返回的Class对象是由所表示的类的静态同步方法锁定的对象。因为它是final的所以我们不会覆盖它。

Object obj = new String("github/shellhub");
Class cls =obj.getClass();
System.out.println(cls.getName());

finalize()方法,在对象被垃圾收集之前调用此方法。当垃圾收集器确定没有对该对象的更多引用时,垃圾收集器会在对象上调用它。我们应该覆盖finalize()方法来处理系统资源,执行清理活动并最小化内存泄漏。例如,在销毁Servlet对象web容器之前,总是调用finalize方法来执行会话的清理活动。
注意:即使该对象有多次符合垃圾回收条件,对象也只调用一次finalize方法。

public class Test {
    public static void main(String[] args) {
        Test t = new Test();
        System.out.println(t.hashCode());

        t = null;

        // calling garbage collector
        System.gc();
        System.out.println("end");
    }

    @Override
    protected void finalize() {
        System.out.println("finalize method called");
    }
}

注意finalize方法不一定会执行,多试几次

clone方法用于克隆一个和该对象完全一样的新对象

Swift快速入门教程

C语言考纲模拟题

一维数组定义格式(在主函数(main()))

  • 定义的同时初始化(第一种考察方式)
int arr[5] = {1, 2, 3, 4, 5};
  • 从键盘读入(不用指针)
int arr[10]; //定义长度为10的数组
for(int i = 0; i < 10; i++){
    printf("请输入第%d个数\n", i + 1);
    scanf("%d", &arr[i]);
}
  • 从键盘读入(使用指针方式)
int arr[10]; //定义长度为10的数组
int *p = arr; //p指向arr
for(int i = 0; i < 10; i++){
    printf("请输入第%d个数\n", i + 1);
    scanf("%d", p);
    p++;
}
  • 正确区别下标与数组值(数组下标为i,数组元素为arr[i]),如下标下标为奇数的元素值乘以2,其余不变
for(int i = 0; i < 10; i++){
    if(i % 2 == 1){ //i为下标
        arr[i] = arr[i] * 2; //arr[i]为数组中的第i个元素
    }
}

一维数组函数(记住就行)

  • 求和(直接调用就行)
int sum(int *arr, int n){
    int sum = 0;
    for(int i = 0; i < n; i++){
        sum = sum + arr[i];
    }
}

在主函数中调用
printf("数组和是%d\n", sum(arr, 10)); //注意10是数组长度,视情况而定

  • 求平均值(直接调用就行了)
float avg(int *arr, int n){
    float sum = 0;
    for(int i = 0; i < n; i++){
        sum = sum + arr[i];
    }
    return sum / n; //返回平均值
}

在主函数中调用
printf("数组平均值是%.2f\n", avg(arr, 10)); //注意10是数组长度,视情况而定

  • 求最大值(直接调用就行了)
int max(int *arr, int n){
    int max = arr[0];
    for(int i = 0; i < n; i++){
        if(arr[i] > max){
            max = arr[i];
        }
    }
    return max; //返回最大值
}

在主函数中调用
printf("数组最大值是%d\n", max(arr, 10)); //注意10是数组长度,视情况而定

  • 求最小值(直接调用就行了)
int min(int *arr, int n){
    int min = arr[0];
    for(int i = 0; i < n; i++){
        if(arr[i] < min){
            min = arr[i];
        }
    }
    return max; //返回最大值
}

在主函数中调用
printf("数组最小值是%d\n", min(arr, 10)); //注意10是数组长度,视情况而定

  • 冒泡排序(只要排序,直接调用就可以了)
void sort(int *arr, int n){
    for(int i = 0; i < n - 1; i++){
        for(int j = 0; j < n - 1 - i; j++){
            if(arr[j] > arr[j + 1]){ //这是升序,降序直接改用`<`符号
                int t = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = t;
            }
        }
    }
}
  • 查找某个数字出现的次数
void find(int *arr, int n){
    printf("请输入一个数字:");
    int num;
    scanf("%d", num);

    int count = 0; //统计出现的次数
    for(int i = 0; i < n; i++){
        if(arr[i] == num){
            count++;
        }
    }
    printf("数字%d出现了%d次\n", num, count);
}

二维数组考察

  • 定义的同时赋值
int arr[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}; //3行4列
  • 先定义再赋值
for(int i = 0; i < 3; i++){
    for(int j = 0; j < 4; j++){
        printf("请输入第%d行第%d列的元素值:", i, j); //可选提示信息
        scanf("%d", &arr[i][j]);
    }
}
  • 二维数组的转置(如3行4列转置成4行3列)
int arr[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}; //3行4列
printf("打印转置前的数组\n");
for(int i = 0; i < 3; i++){
    for(int j = 0; j < 4; j++){
        printf("%d\t", arr[i][j]);
    }
    printf("\n");
}
int brr[4][3]; //存储转置以后的数组
for(int i = 0; i < 3; i++){
    for(int j = 0; j < 4; j++){
        brr[j][i] = arr[i][j]; //转置的公式[i][j] => [j][i]
    }
}
printf("打印转置后的数组\n");
for(int i = 0; i < 4; i++){
    for(int j = 0; j < 3; j++){
        printf("%d\t", arr[i][j]);
    }
    printf("\n");
}
  • 将行最值放入新的数组中等操作

假设数组为3行4列,即arr[3][4],所以定义最大值数组int max[3]

int max[3]; //存储三行的最大值
int min[3]; //存储三行的最小值
for(int i = 0; i < 3; i++){
    max[i] = arr[i][0]; //假设第i的最大值为第i行的第一个元素
    min[i] = arr[i][0]; //假设第i的最小值为第i行的第一个元素
    for(int j = 0; j < 4; j++){
        if(arr[i] > max[i]){
            max[i] = arr[i];
        }
        if(arr[i] < min[i]){
            min[i] = arr[i];
        }
    }
}

for(int i = 0; i < 3; i++){
  printf("第%d行的最大值是%d\n", i + 1, max[i]);
}
  • 将列最值放入新的数组中等操作

假设数组为3行4列,即arr[3][4],所以定义最大值数组int max[4]

int max[4]; //存储三行的最大值
int min[4]; //存储三行的最小值
for(int j = 0; j < 4; j++){
    max[j] = arr[j][0];//假设每一列的最大值为第j行第0行的元素
    for(int i = 0; i < 3; j++){
        if(arr[i][j] > max[j]){
            max[j] = arr[i][j];
        }
        if(arr[i][j] < min[j]){
            min[j] = arr[i][j];
        }
    }
}

for(int i = 0; i < 4; i++){
  printf("第%d列的最大值是%d最小值是%d\n", i + 1, max[i], min[i]);
}

递归函数(常考)

  • 计算1-100的和(直接调用)
int sum(int n){
    if(n == 1){
        return 1;
    }else{
      return n + sum(n-1);
    }
}

在主函数中调用

printf("1-50的和是%d\n", sum(50));
printf("1-100的和是%d\n", sum(100));
  • 计算n的阶乘(n!)
int fac(int n){
    if(n == 1){
        return 1;
    }else{
      return n * fac(n-1);
    }
}

在主函数中调用

printf("5的阶乘是%d\n", fac(5));
printf("10的阶乘是%d\n", fac(10));
//打印1-n的阶乘
printf("请输入n:");
int n;
scanf("%d", &n);
for(int i = 1; i <n ;i++){
    printf("%d! = %d\n", i, fac(i));
}

更多递归函数请参考我写的文章

结构体+文件综合考察

  • 定义学生的结构体(可以是其他类型的)
typedef struct{
    int id; //学号
    char name[50]; //学生姓名
    int age; //定义学生的年龄
}Student;
  • 循环录入学生结构体信息
//第一步定义学生结构体数组
//int arr[5]; //定义整型数组int为类型,arr为数组名(回顾)
Student stu[5]; //Student为类型,stu为数组名
//循环录入
for(int i = 0; i < 5; i++){ //以下代码是死的喔,好好背一下
      printf("请输入第%d个学生的信息:", i + 1);
      printf("学号:");
      scanf("%d", &stu[i].id);

      printf("姓名:");
      scanf("%s", stu[i].name);

      printf("年龄:");
      scanf("%d", &stu[i].age);
}
  • 循环把文件写入到D磁盘中的data.txt文件下面
FILE *fp = fopen("D:\\data.txt", "w"); //打开文件
if(fp != NULL){
  printf("打开文件成功\n");
  fprintf(fp, "学号\t姓名\t年龄\n");
  for(int i = 0; i < 5; i++){
    fprintf(fp, "%d\t%s\t%d\n", stu[i].id, stu[i].name, stu[i].age);
  }
  fclose(fp); //关闭文件
}else{
    printf("打开文件失败\n");
}
  • 循环把文件从磁盘中读取
FILE *fp = fopen("D:\\data.txt", "r");
if(fp != NULL){
  printf("打开文件成功\n");
  int id;
  char name[100];
  int age;
  fscanf(fp, "学号\t姓名\t年龄\n");//读取表头扔掉
  for(int i = 0; i < 5; i++){
    fscanf(fp, "%d %s %d", &id, name, &age);
    printf("%d\t%s\t%d\n", id, name, age);
  }
  fclose(fp);
}else{
    printf("打开文件失败\n");
}

宏(传参数)

  • 求正方形的面积
#include <stdio.h>

#define S(x) (x)*(x)
int main()
{
	printf("请输入正方形的边长:");
	int x;
	scanf("%d", &x);
	printf("正方形的面积是%d\n", S(x));
	return 0;
}
  • 求长方形的面积
#include <stdio.h>

#define S(x, y) (x)*(y)
int main()
{
	printf("请输入长方形的长:");
	int x;
	scanf("%d", &x);

	printf("请输入长方形的宽:");
	int y;
	scanf("%d", &y);

	printf("长方形的面积%d\n", S(x, y));
	return 0;
}

结语

本文档只供学习使用(祝大家能学到东西)^_^ 2019.11.21

最大公约数和最小公倍数问题

求解最大公约数和最小公倍数

什么是最公约数

最大公因数,也称最大公约数、最大公因子,指两个或多个整数共有约数中最大的一个

什么是最大公倍数

两个或多个整数公有的倍数叫做它们的公倍数

暴力破解法

#include<stdio.h>

void swap(int *pa, int *pb){
	int t = *pa;
	*pa = *pb;
	*pb = t;
}
int main(){

	printf("请输入第一个数:");
	int a;
	scanf("%d", &a);

	printf("请输入第一个数:");
	int b;
	scanf("%d", &b);

	//保证a <= b
	if(a < b){
		swap(&a, &b); //交换a,b的值
	}

	//求解最大公约数
	int max = 1;
	for(int i = 1; i <= b; i++){
		if(a % i == 0 && b % i == 0){
			max = i; //求解最大的公约数
		}
	}
	printf("最大公约数:%d\n", max);

	//求解最小公倍数
	int min = a;
	while(1){
		if(min % a == 0 && min % b == 0){
			break; //找到了最小公倍数
		}
		min++;
	}
	printf("最小公倍数:%d\n", min);
	return 0;
}

输入

请输入第一个数:12
请输入第一个数:16

输出

最大公约数:4
最小公倍数:48

数学定理

最大公约数 * 最小公倍数 = 两数之积

所以根据这个数学定理,我们只需要求出最大公约数,就可以通过定理推出最小公倍数,改进后的代码如下

#include<stdio.h>

void swap(int *pa, int *pb){
	int t = *pa;
	*pa = *pb;
	*pb = t;
}
int main(){

	printf("请输入第一个数:");
	int a;
	scanf("%d", &a);

	printf("请输入第二个数:");
	int b;
	scanf("%d", &b);

	//保证 a <= b
	if(a < b){
		swap(&a, &b); //交换a,b
	}

	//求最大公约数
	int max = 1;
	for(int i = 1; i <= b; i++){
		if(a % i == 0 && b % i == 0){
			max = i; //Çó½â×î´óµÄ¹«Ô¼Êý
		}
	}
	printf("最大公约数:%d\n", max);

	//求最小公倍数
	printf("最小公倍数:%d\n", a * b / max);
	return 0;
}

算法优化

#include<stdio.h>

void swap(int *pa, int *pb){
    int t = *pa;
    *pa = *pb;
    *pb = t;
}
int main(){
    printf("请输入第一个数:");
    int a;
    scanf("%d", &a);

    printf("请输入第二个数:");
    int b;
    scanf("%d", &b);

    //保证 a <= b
    if(a < b){
        swap(&a, &b); //交换a,b
    }
    int multi = a * b; //保存a和b的值
    while (a % b != 0){
        int t = a % b;
        a = b;
        b = t;
    }
    printf("最大公约数:%d\n", b);
    printf("最小公倍数:%d\n", multi / b);

    return 0;
}

Bubble Sort - 冒泡排序

Bubble Sort - 冒泡排序

15923110-65a6e527966ba60c

bubbleSort.c

#include <stdio.h>
#include <stdbool.h>

void bubbleSort(int array[], int length, int isAES);

void print(int array[], int length);

int main() {
    int array[] = {7, 3, 5, 1, 3};
    int length = sizeof(array) / sizeof(int);

    //aes sort
    bubbleSort(array, length, true);
    print(array, length);

    //desc sort
    bubbleSort(array, length, false);
    print(array, length);
    return 0;
}

void bubbleSort(int array[], int length, int isAES) {

    for (int i = 0; i < length - 1; ++i) {
        bool swapped = false;
        for (int j = 0; j < length - i - 1; ++j) {
            if (isAES) {
                if (array[j] > array[j + 1]) {
                    int temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;

                    swapped = true;
                }
            } else {
                if (array[j] < array[j + 1]) {
                    int temp = array[j];
                    array[j] = array[j + 1];
                    array[j + 1] = temp;

                    swapped = true;
                }
            }
        }

        if (!swapped) {
            //no element was swapped, array has been sorted, end loop
            break;
        }
    }
}

void print(int array[], int length) {
    printf("[");
    for (int i = 0; i < length; ++i) {
        if (i != length - 1) {
            printf("%d, ", array[i]);
        } else {
            printf("%d", array[i]);
        }
    }
    printf("]\n");
}

BubbleSort.java

package github.shellhub;


import java.util.Arrays;
import java.util.StringJoiner;

public class BubbleSort {
    public static void main(String[] args) {
        int[] array = new int[]{7, 3, 5, 1, 3};

        //aes sort
        bubbleSort(array, true);
        print(array);

        //desc sort
        bubbleSort(array, false);
        print(array);

    }

    public static void bubbleSort(int[] array, boolean isAES) {
        for (int i = 0, length = array.length; i < length; i++) {
            boolean swapped = false;
            for (int j = 0; j < length - i - 1; j++) {
                if (isAES) {
                    if (array[j] > array[j + 1]) {
                        int temp = array[j];
                        array[j] = array[j + 1];
                        array[j + 1] = temp;

                        swapped = true;
                    }
                } else {
                    if (array[j] < array[j + 1]) {
                        int temp = array[j];
                        array[j] = array[j + 1];
                        array[j + 1] = temp;

                        swapped = true;
                    }
                }
            }

            //no element was swapped, end loop
            if (!swapped) {
                break;
            }
        }
    }

    public static void print(int[] array) {
        StringJoiner joiner = new StringJoiner(",", "[", "]");
        Arrays.stream(array).forEach(element -> joiner.add(element + ""));
        System.out.println(joiner.toString());
    }
}

Docker中使用MySQL

原文地址

#21

安装Docker

Linux或者Mac用户建议使用一件脚本安装

https://github.com/docker/docker-install

Windows用户(好久没用这个系统了)

https://docs.docker.com/docker-for-windows/install/#what-to-know-before-you-install

进入主题

首先下载使用dockek下载mysql镜像

# download mysql from docker library
docker pull mysql

启动mysql服务器

# mysql给该容器搞一个标识
# 3306:3306映射容器端口到本机端口
# root为数据库密码
# latest为mysql版本,此处表示最新版本,可以不填写
docker run --name mysql -p 3306:3306 -e MYSQL_ROOT_PASSWORD=root -d mysql:latest

进入mysql容器

docker exec -it mysql bash

登录mysql

# root为数据库密码,这儿显示输入
mysql -u root -proot

例子

# 显示数据库
show databases;
# 创建数据库
create databases db_example;

总结

DDDDDDDDDDDDDDDDDDocker用起来太舒服了

Linux (Unix) 命令笔记

pwd 返回工作目录
ls 列举出目录的内容
ls /etc 列举指定目录的内容
ls -l 以长格式列举出目录的内容
ls -a 显示隐藏文件
ls -all ls -a & ls -l的组合
echo ~ 打印用户目录
cd 切换到用户目录
cd ~ 切换到用户目录
cd - 切换到上一次访问的目录
cd firstname\ lastname/ 使用转义
cd 'untitled folder/' 使用单引号
. 当前目录,如执行./app.sh将执行当前目录下的app.sh脚本
.. 上级目录
../../ 上一级目录的上一级目录
../../.. 不用多说
file 判断文件类型
man ls 查看ls的使用手册
man command 查看特定命令的使用手册
mkdir helloworld 当前目录下创建helloworld文件夹
mkdir -p hello/world 创建多级目录
rmdir hello 删除文件夹
rm -rf hello 强制删除文件夹hello及里面的内容
touch filename 创建文件
touch hello/filename hello文件夹下创建文件
rm filename 删除指定文件
rm hello/filename 删除对应文件夹下的文件
rm -rf * 强制删除当前文件夹下的所有内容
cp filename dir 复制文件到目录
cp -r dir dstdir 复制文件夹dir到指定文件夹dstdir
mv filename dir 移动文件到目录
mv dir dstdir 移动文件夹到文件夹
mv oldname newname 修改文件名
mv olddir newdir 修改文件夹名字
grep -r "findstr" . 查找当前目录级子目录中出现的字符串

Android实现网易云热门和历史标签效果

Android网易云历史搜索和热门标签

原文地址

最近开发了一个网易云音乐播放器,有这么一个需求,需要展示搜索建议,历史搜索记录
项目地址: https://github.com/shellhub/NetEaseMusic

自动换行标签

从效果图可以看到,标签如果太长无法容纳会自动换行,虽然我们可以自己实现自定义View,但是人生苦短没必要重复造轮子,这里推荐谷歌推出的库flexbox-layout

添加依赖

implementation 'com.google.android:flexbox:1.1.0'

代码实现

首先这个布局的话我们可以使用RecyclerView去实现,这里需要使用到RecyclerView的多布局,但是为了简单起见我们只看其中的RecyclerView就可以了。

首先我们在你的Activity或者Fragment中初始化RecyclerView,然后设置RecyclerView的布局管理器,然后就可以了,简单吧,就一行代码

rvHots.setLayoutManager(new FlexboxLayoutManager(getContext()));

完整代码如下

HistoryFragment.java

public class HistoryFragment extends Fragment {

    private String TAG = TagUtils.getTag(this.getClass());
    @BindView(R.id.iv_remove_history)
    ImageView ivRemoveHistory;

    @BindView(R.id.rvHots)
    RecyclerView rvHots;

    @BindView(R.id.rvHistory)
    RecyclerView rvHistory;

    HotSearchAdapter mHotSearchAdapter;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_search_hot, container, false);
        ButterKnife.bind(this, view);

        setup();
        return view;
    }

    @Override
    public void onStart() {
        super.onStart();
        if (!EventBus.getDefault().isRegistered(this)) {
            EventBus.getDefault().register(this);
        }
    }

    private void setup() {
        rvHots.setAdapter(mHotSearchAdapter = new HotSearchAdapter());
        rvHots.setLayoutManager(new FlexboxLayoutManager(getContext()));
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onHotsReady(HotResponse hotResponse) {
        mHotSearchAdapter.setHots(hotResponse.getResult().getHots());
        mHotSearchAdapter.notifyDataSetChanged();
    }
}

HistoryAdapter.java

package shellhub.github.neteasemusic.adapter;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import com.blankj.utilcode.util.LogUtils;

import org.greenrobot.eventbus.EventBus;

import java.util.ArrayList;
import java.util.List;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import butterknife.BindView;
import butterknife.ButterKnife;
import lombok.Data;
import shellhub.github.neteasemusic.R;
import shellhub.github.neteasemusic.model.entities.HistoryEvent;
import shellhub.github.neteasemusic.util.TagUtils;

@Data
public class HistoryAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private String TAG = TagUtils.getTag(this.getClass());
    private List<String> histories = new ArrayList<>();

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.hot_item, parent, false);
        ButterKnife.bind(this, view);
        return new HistoryViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof HistoryViewHolder) {
            ((HistoryViewHolder) holder).bind(position);
        }
    }

    @Override
    public int getItemCount() {
        LogUtils.d(TAG, histories.size());
        return histories.size();
    }

    public class HistoryViewHolder extends RecyclerView.ViewHolder {
        @BindView(R.id.tv_hot)
        TextView tvHistory;

        public HistoryViewHolder(@NonNull View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }

        public void bind(int position) {
            tvHistory.setText(histories.get(position));

            itemView.setOnClickListener((view) -> {
                EventBus.getDefault().post(new HistoryEvent(histories.get(position)));
            });
        }
    }

}

使用SSH方式登录谷歌云服务器

原文连接

#49

google-cloud

使用SSH方式登录谷歌云服务器

  • 通过网页端登录Google Cloud

  • 切换到root用户

    sudo su
  • 修改root密码

    passwd
    # Changing password for user root.
    # New password:新密码
    # Retype new password:重新录入新密码
    # passwd: all authentication tokens updated successfully.
  • 修改ssh配置

    Google Cloud默认是不允许用ssh远程登录

    vim /etc/ssh/sshd_config

    按如下规则修改配置

    PermitRootLogin yes # 默认为: PermitRootLogin no
    PasswordAuthentication yes #默认为: PasswordAuthentication no
  • 重启ssh服务

    service sshd restart
  • 使用ssh等第三方工具登录Google Cloud

设计模式: Java中的工厂设计模式

前言

工厂设计模式(Factory Design Pattern)属于创建模式之一,工厂设计模式在JDK,Spring,Stuts被广泛使用
factory-design-pattern

当一个类或者接口有多个子类,并且基于输入返回特定的子类,此时会使用工厂设计模式。这种模式负责从客户端到工厂类的实例化。

让我们首先学习如何在java中实现工厂设计模式,然后我们将研究工厂模式的优势,我们将在JDK中看到一些工厂设计模式的使用。请注意,此模式也称为工厂方法设计模式。

工厂设计模式: 超类

工厂设计模式中的超类可以是接口,抽象类或普通的java类。对于我们的工厂设计模式示例,我们使用带有重写的toString()方法的抽象超类进行测试。

package com.github.shellhub.model;

public abstract class Computer {

	public abstract String getRAM();
	public abstract String getHDD();
	public abstract String getCPU();

	@Override
	public String toString(){
		return "RAM= "+this.getRAM()+", HDD="+this.getHDD()+", CPU="+this.getCPU();
	}
}

工厂设计模式: 子类

假设我们有两个子类PCServer,具有以下实现。

PC.java

package com.github.shellhub.model;

public class PC extends Computer {

	private String ram;
	private String hdd;
	private String cpu;

	public PC(String ram, String hdd, String cpu){
		this.ram=ram;
		this.hdd=hdd;
		this.cpu=cpu;
	}
	@Override
	public String getRAM() {
		return this.ram;
	}

	@Override
	public String getHDD() {
		return this.hdd;
	}

	@Override
	public String getCPU() {
		return this.cpu;
	}

}

Server.java

package com.github.shellhub.model;

public class Server extends Computer {

	private String ram;
	private String hdd;
	private String cpu;

	public Server(String ram, String hdd, String cpu){
		this.ram=ram;
		this.hdd=hdd;
		this.cpu=cpu;
	}
	@Override
	public String getRAM() {
		return this.ram;
	}

	@Override
	public String getHDD() {
		return this.hdd;
	}

	@Override
	public String getCPU() {
		return this.cpu;
	}

}

工厂类

既然现在我们准备好了超类和子类,我们可以编写工厂类。以下是基本的实现。

package com.github.shellhub.factory;

import com.github.shellhub.model.Computer;
import com.github.shellhub.model.PC;
import com.github.shellhub.model.Server;

public class ComputerFactory {

    public static Computer getComputer(String type, String ram, String hdd, String cpu) {
        if ("PC".equalsIgnoreCase(type)) {
            return new PC(ram, hdd, cpu);
        } else if ("Server".equalsIgnoreCase(type)) {
            return new Server(ram, hdd, cpu);
        }

        return null;
    }
}

使用

这是一个简单的测试客户端程序,它使用上面的工厂设计模式实现。

package com.github.shellhub;

import com.github.shellhub.factory.ComputerFactory;
import com.github.shellhub.model.Computer;

public class TestFactory {

    public static void main(String[] args) {
        Computer pc = ComputerFactory.getComputer("pc", "2 GB", "500 GB", "2.4 GHz");
        Computer server = ComputerFactory.getComputer("server", "16 GB", "1 TB", "2.9 GHz");
        System.out.println("Factory PC Config::" + pc);
        System.out.println("Factory Server Config::" + server);
    }

}

Output:

Factory PC Config::RAM= 2 GB, HDD=500 GB, CPU=2.4 GHz
Factory Server Config::RAM= 16 GB, HDD=1 TB, CPU=2.9 GHz

工厂设计模式的优点

  • 工厂设计模式提供了接口而不是实现的代码方法。
  • 工厂模式从客户端代码中删除实际实现类的实例化。工厂模式使我们的代码更健壮,耦合更少,易于扩展。例如,我们可以轻松更改PC类实现,因为客户端程序不知道这一点。
  • 工厂模式通过继承提供实现和客户端类之间的抽象。

工厂设计模式在JDK中的应用

  1. java.util.CalendarResourceBundleNumberFormat getInstanc()方法使用Factory模式。
  2. 包装类中的valueOf()方法,如BooleanInteger等。

数据结构 - 链表

链表的定义

链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。

相对于线性表链表的优点

使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。

几种常见的链表

单向链表:

链表中最简单的一种是单向链表,它包含两个域,一个信息域和一个指针域。这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值。

408px-singly-linked-list svg

linked_list

一个单向链表的节点被分成两个部分。第一个部分保存或者显示关于节点的信息,第二个部分存储下一个节点的地址。单向链表只可向一个方向遍历。

双向链表

一种更复杂的链表是“双向链表”或“双面链表”。每个节点有两个连接:一个指向前一个节点,而另一个指向下一个节点

610px-doubly-linked-list svg 1

循环链表

在一个 循环链表中, 首节点和末节点被连接在一起。

350px-circularly-linked-list svg

链表的操作

插入操作(insert)

linked_list_insertion_0

linked_list_insertion_1

linked_list_insertion_2

linked_list_insertion_3

删除操作(Deletion)

linked_list_deletion_0

linked_list_deletion_1

linked_list_deletion_2

linked_list_deletion_3

单向链表

Linked List

  • A Linked List is a linear collection of data elements, called nodes, each
    pointing to the next node by means of a pointer. It is a data structure
    consisting of a group of nodes which together represent a sequence.
  • Singly-linked list: linked list in which each node points to the next node and the last node points to null
  • Doubly-linked list: linked list in which each node has two pointers, p and n, such that p points to the previous node and n points to the next node; the last node's n pointer points to null
  • Circular-linked list: linked list in which each node points to the next node and the last node points back to the first node
  • Time Complexity:
    • Access: O(n)
    • Search: O(n)
    • Insert: O(1)
    • Remove: O(1)
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct node {
    char *key;
    int value;
    struct node *next;
} node;

node *head = NULL;

void print() {
    for (node *ptr = head; ptr != NULL; ptr = ptr->next) {
        printf("key=%s->value=%d\n", ptr->key, ptr->value);
    }
}

bool isEmpty() {
    return head == NULL;
}

int length() {
    int count = 0;
    for (node *ptr = head; ptr != NULL; ptr = ptr->next) {
        count++;
    }
    return count;
}

void insertLast(char *key, int value) {
    node *link = malloc(sizeof(node));
    link->key = key;
    link->value = value;
    link->next = NULL;

    if (isEmpty()) {
        head = link;
        return;
    }
    //find the last node
    node *ptr = head;
    node *previous = NULL;
    while (ptr != NULL) {
        previous = ptr;
        ptr = ptr->next;
    }
    //insert
    if (previous != NULL) {
        previous->next = link;
    }
}

void insertFirst(char *key, int value) {
    node *link = malloc(sizeof(node));
    link->key = key;
    link->value = value;

    if (isEmpty()) {
        link->next = NULL;
    } else {
        link->next = head;
    }
    head = link;
}

void insertAt(int position, char *key, int value) {
    if (position > length() + 1 || position <= 0) {
        perror("this position is invalid\n");
        return;
    }

    node *link = malloc(sizeof(node));
    link->key = key;
    link->value = value;

    if (position == 1) {
        insertFirst(key, value);
        return;
    }

    if (position == length() + 1) {
        insertLast(key, value);
        return;
    }

    node *ptr = head;
    node *previous = NULL;
    int count = 0;
    while (ptr != NULL && count++ <= position) {
        previous = ptr;
        ptr = ptr->next;
    }

    if (previous != NULL) {
        link->next = previous->next;
        previous->next = link;
    }

}

node *deleteFirst() {
    if (!isEmpty()) {
        node *deleteNode = head;
        head = head->next;
        return deleteNode;
    }
}

node *deleteLast() {
    if (!isEmpty()) {
        node *ptr = head;
        node *previous = NULL;
        while (ptr->next != NULL) {
            previous = ptr;
            ptr = ptr->next;
        }
        node *deleteNode = previous->next;
        previous->next = NULL;
        return deleteNode;
    }
}


int main() {
    printf("current link list length is %d\n", length());
    insertLast("key1", 1);
    insertFirst("key0", 0);
    insertLast("key2", 2);
    insertLast("key3", 3);

    insertAt(1, "key4", 4);
    insertAt(length(), "key5", 5);
    insertAt(length() + 1, "key6", 6);
    print();

    node *deleteNode = deleteFirst();
    printf("delete first node: key=%s->value=%d\n", deleteNode->key, deleteNode->value);
    print();

    deleteNode = deleteLast();
    printf("delete last node: key=%s->value=%d\n", deleteNode->key, deleteNode->value);
    print();
}

设计模式之 - 单例模式

Java单例模式是四中创建模式之一,本文我们讲从例子讲解单例模式的方方面面

Java单例模式(Singleton)

  • Java限制了类的实例,保证了JVM中只能存在一个某个类的一个实例

  • 单实例类必须提供一个访问该实例的全局方法(其实就是public static)

  • 单例模式使用在日志(logging),驱动(driver),缓存(caching),线程池(thread pool)

  • Singleton设计模式也用于其他设计模式,如Abstract Factory,Builder,Prototype,Facade等。

java-singleton-pattern

要实现单例子模式,我们有很多不同的实现方法,但很多地方是相同的

  • 私有化构造器,防止从其他类中构造实例

  • 私有化该静态单例

  • 定义public static的get方法获取该单例,便于从其他类中获取该实例

在后面的部分中,我们将学习Singleton模式实现的不同方法以及设计实现的关注点。

  1. 急切的初始化 (饿汉模式)
  2. 静态代码块初始化
  3. 延迟初始化 (懒汉模式)
  4. 线程安全单例模式
  5. Bill Pugh 单例实现
  6. 使用反射来破解单例模式
  7. 枚举类型的单例
  8. 序列化和单例模式

急切的初始化

急切初始化模式,顾名思义就是当类加载时就会创建实例对象,这是实现单例模式的最简单方法,但是这样存在一个缺点就是虽然创建了该实例,但是有可能该实例不会被其他类使用,并且类的加载会变慢,因为类加载时需要创建对象

举一个例子

public class EagerInitializedSingleton {

    //私有化该实例
    private static final EagerInitializedSingleton instance = new EagerInitializedSingleton();

    //私有化构造器防止该类被其他类实例化
    private EagerInitializedSingleton() {
    }

    //提供getter方法
    public static EagerInitializedSingleton getInstance() {
        return instance;
    }
}

静态代码块初始化

这种方法和上一种方法很相似,提供一个static代码块,并提供异常处理信息

public class StaticBlockSingleton {

    private static StaticBlockSingleton instance;

    private StaticBlockSingleton() {
    }

    //在静态代码块中处理异常
    static {
        try {
            instance = new StaticBlockSingleton();
        } catch (Exception e) {
            throw new RuntimeException("Exception occured in creating singleton instance");
        }
    }

    public static StaticBlockSingleton getInstance() {
        return instance;
    }
}

可以看到这两种创建单实例模式都是在使用之前就创建了该实例,效率不是很高,接下来我们看看其他创建单实例的方式

延迟初始化 (懒汉模式)

先看例子

public class LazyInitializedSingleton {

    private static LazyInitializedSingleton instance;

    private LazyInitializedSingleton(){}

    public static LazyInitializedSingleton getInstance(){
        if(instance == null){
            instance = new LazyInitializedSingleton();
        }
        return instance;
    }
}

可以看到该实例的创建方式放在了public方法中了,当要使用对象时才会判断是否需要创建,这种方式对与单线程系统是安全的,但是对于多线程系统是不安全的,因为没有实现同步,

线程安全单例模式

先看代码

public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;

    private ThreadSafeSingleton() {
    }

    public static synchronized ThreadSafeSingleton getInstance() {
        if (instance == null) {
            instance = new ThreadSafeSingleton();
        }
        return instance;
    }

}

可以看到方法上加上了synchronized关键词,这样多线程中同一个时刻只能有一个线程访问该方法,即锁,保证了线程安全,性能上没有没有无synchronized的高
为了避免每次额外的开销,使用双重检查锁定原理。在这种方法中,synchronized块在if条件中使用,并进行额外的检查以确保只创建一个singleton类的实例。

public class ThreadSafeSingleton {

    private static ThreadSafeSingleton instance;

    private ThreadSafeSingleton() {
    }

    public static ThreadSafeSingleton getInstanceUsingDoubleLocking() {
        if (instance == null) {
            synchronized (ThreadSafeSingleton.class) {
                if (instance == null) {
                    instance = new ThreadSafeSingleton();
                }
            }
        }
        return instance;
    }

}

Bill Pug 单实例

在Java 5之前,Java内存模型有很多问题,上面实现的单例模式在很多场景下是有问题的,比如多线程环境中多个线程同时获取单例,因此Bill Pug提出了一种使用内部内创建单实例的方法

public class BillPughSingleton {

    private BillPughSingleton() {
    }

    private static class SingletonHelper {
        private static final BillPughSingleton INSTANCE = new BillPughSingleton();
    }

    public static BillPughSingleton getInstance() {
        return SingletonHelper.INSTANCE;
    }
}

可以看到该类中有一个静态内部内SingletonHelper,并在里面实现了对象的初始化,当类创建时静态内部类不会被加载,只有当getInstance方法被调用时,内部内才会被加载

这种方法创建单实例被广泛应用,你无须因为多线程环境而同步(synchronized),我在我的项目中大量使用这样的方式实现单例模式,这种方法简单容易理解,并很方便实现

使用反射来破解单例模式

上面实现的单实例模式真的就是真的就无懈可击了么?其实不是的,我们可以通过反射机制实现创建不同的对象,即使你私有化了构造函数

public class ReflectionSingletonTest {

    public static void main(String[] args) {
        EagerInitializedSingleton instanceOne = EagerInitializedSingleton.getInstance();
        EagerInitializedSingleton instanceTwo = null;
        try {
            Constructor[] constructors = EagerInitializedSingleton.class.getDeclaredConstructors();
            for (Constructor constructor : constructors) {
                //Below code will destroy the singleton pattern
                constructor.setAccessible(true);
                instanceTwo = (EagerInitializedSingleton) constructor.newInstance();
                break;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(instanceOne.hashCode() == instanceTwo.hashCode());//output: false
    }

}

当你运行上面的代码,输出结果是false,说明两个对象不是同一个对象,也就是可以通过反射机制构造出不同的实例,反射机制在很多框架如Spring,Hibernate中都广泛使用

枚举类型的单例

为了克服这种情况,Joshua Bloch建议使用Enum来实现Singleton设计模式,因为Java确保任何枚举值只在Java程序中实例化一次。由于Java Enum值是全局可访问的,因此单例也是如此。缺点是枚举类型有些不灵活;例如,它不允许延迟初始化

public enum EnumSingleton {

    INSTANCE;

    public static void doSomething(){
        //do something
    }
}

ArrayList源码解析

ArrayList底层使用数组实现,所以随机访问特定位置的元素的速度特别快,时间复杂度为0(1)

transient Object[] elementData; // non-private to simplify nested class access

ArrayList默认分配大小为10的容量

/**
 * Default initial capacity.
 */
private static final int DEFAULT_CAPACITY = 10;

ArrayList可以给定特定索引删除元素,时间复杂度为O(n)

/**
 * Removes the element at the specified position in this list.
 * Shifts any subsequent elements to the left (subtracts one from their
 * indices).
 *
 * @param index the index of the element to be removed
 * @return the element that was removed from the list
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E remove(int index) {
    //检查元素索引的正确性
    rangeCheck(index);

    //当ArrayList被修改时,该变量累加
    modCount++;
    //获取对应索引位置的元素
    E oldValue = elementData(index);

    //计算需要往前移动的元素个数
    int numMoved = size - index - 1;

    //如果删除的不是最后一个元素,则把对应index后面的元素依次往前移动一个位置
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //移动完成后需要吧数组中的最后一个元素置为空,方便GC回收
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

也可以删除ArrayList中对应的对象,时间复杂度也为O(n)

/**
 * Removes the first occurrence of the specified element from this list,
 * if it is present.  If the list does not contain the element, it is
 * unchanged.  More formally, removes the element with the lowest index
 * <tt>i</tt> such that
 * <tt>(o==null&nbsp;?&nbsp;get(i)==null&nbsp;:&nbsp;o.equals(get(i)))</tt>
 * (if such an element exists).  Returns <tt>true</tt> if this list
 * contained the specified element (or equivalently, if this list
 * changed as a result of the call).
 *
 * @param o element to be removed from this list, if present
 * @return <tt>true</tt> if this list contained the specified element
 */
public boolean remove(Object o) {
    //可以看出ArrayList可以存储null值,先判断是否删除null,否则如果当执行equals方法时会抛出空指针异常
    if (o == null) {
        for (int index = 0; index < size; index++)
            //暴力循环获取第一个为null的索引
            if (elementData[index] == null) {
                //快速移除元素,没有进行索引判断,因为该索引是安全的,不会越界
                fastRemove(index);
                //删除成功
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            //调用equals方法暴力查找需要移除的元素
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

fastRemove方法如下,该方法删除元素时跳过了索引检查

/*
 * Private remove method that skips bounds checking and does not
 * return the value removed.
 */
private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

接下来看看add方法

/**
 * Appends the specified element to the end of this list.
 *
 * @param e element to be appended to this list
 * @return <tt>true</tt> (as specified by {@link Collection#add})
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

在指定位置添加元素,时间复杂度为O(n)

/**
 * Inserts the specified element at the specified position in this
 * list. Shifts the element currently at that position (if any) and
 * any subsequent elements to the right (adds one to their indices).
 *
 * @param index index at which the specified element is to be inserted
 * @param element element to be inserted
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public void add(int index, E element) {
    //首先检查index的合法性(0<=index<=arrayList.size)
    rangeCheckForAdd(index);

    //扩容处理
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //指定位置的所有元素依次往后移动一个位置
    System.arraycopy(elementData, index, elementData, index + 1,
                     size - index);
    //插入元素
    elementData[index] = element;
    size++;
}

清空ArrayList,时间复杂度O(n)

/**
 * Removes all of the elements from this list.  The list will
 * be empty after this call returns.
 */
public void clear() {
    //每次修改该变量都会自加操作
    modCount++;

    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        //全部置空,让GC清理内存
        elementData[i] = null;

    //长度变为0
    size = 0;
}

判断是否含某一个元素

public boolean contains(Object o) {
    //获取对应索引进行判断
    return indexOf(o) >= 0;
}

你真的知道JAVA中抽象类和接口的区别么?

详解

在抽象类中可以写非抽象的方法,从而避免在子类中重复书写他们,这样可以提高代码的复用性,这是抽象类的优势;接口中只能有抽象的方法。

public abstract class Person {
    void eat(){
        System.out.println("it is valid");
    }
}

接口中的方法不能有具体实现,以下代码报错

public interface Person {
  /**
   * Interface abstract methods cannot have body
   */
  void eat(){
      System.out.println("it is valid");
  }
}

抽象类中定义的方法默认不是public,接口中定义的方法默认是public,如我们用main方法测试一下

public abstract class Application {
    /**
     * JVM可以正确找到main方法
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("no problem");
    }
}

下面代码无法正常运行

Error: Main method not found in class com.github.Application, please define the main method as:
public static void main(String[] args)
or a JavaFX application class must extend javafx.application.Application

public abstract class Application {
    /**
     * JVM可以正确找到main方法
     * @param args
     */
    static void main(String[] args) {
        System.out.println("no problem");
    }
}

可以直接运行(JDK8)

public interface Application {
    /**
     * JVM可以正确找到main方法
     * @param args
     */
    static void main(String[] args) {
        System.out.println("no problem");
    }
}

一个类可以继承一个抽象类(JAVA的单继承),但是可以实现多个接口

抽象类中的成员变量可以不初始化,接口中的成员变量必须初始化,默认为static, final

public abstract class Person {
    //no problem
    int age;
}

报错,需要初始化

public interface Person {
    //Variable 'age' might not have been initialized
    int age;
}

接口中的变量没有必要写上static final
screenshot from 2018-07-23 19-32-54

抽象类中可以定义构造函数,但是不能实例化,至于接口的话之前说过,是不能有方法具体的实现的,故不能有构造函数

public abstract class Person {
    String name;
    String age;
    Person(String name, int age) {

    }
}

screenshot from 2018-07-23 19-38-28

速度比较

public abstract class AbstractClass {
    public abstract void say();
}
public interface Interface {
    void say();
}
public class SubClass implements Interface{

    @Override
    public void say() {
        System.out.println("github/shellhub");
    }

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            new SubClass().say();
        }
        System.out.println(System.currentTimeMillis() - startTime);

    }
}
public class SubClass extends AbstractClass{

    @Override
    public void say() {
        System.out.println("github/shellhub");
    }

    public static void main(String[] args) {
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000; i++) {
            new SubClass().say();
        }
        System.out.println(System.currentTimeMillis() - startTime);

    }
}

运行10次的时间比较

  • 接口
    18 35 22 30 37 31 30 46 32 40
  • 抽象类
    22 29 28 38 33 36 38 23 28 50

最终胜利:自己判断吧,这点测试结果说明不了什么问题,这个问题不能很片面,因为有很多因素,下面贴一个SO上的回答

screenshot from 2018-07-23 20-10-09

总结

  • 在抽象类中可以写非抽象的方法,从而避免在子类中重复书写他们,这样可以提高代码的复用性,这是抽象类的优势;接口中只能有抽象的方法。

  • 抽象类中定义的成员变量默认不是public,接口中定义的方法默认是public,

  • 一个类可以继承一个抽象类(JAVA的单继承),但是可以实现多个接口 (WTF这是区别么)

  • 抽象类中的成员变量可以不初始化,接口中的成员变量必须初始化,默认为static, final

  • 抽象类中可以定义构造函数,但是不能实例化,接口不能定义构造函数

  • 速度比较(待定)

  • 如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。

  • 想到在补充...

Android使用Monkey & Monkey Script进行App压力测试

使用monkey进行App压力测试

monkey是一个运行在Android模拟器或者真机中可以伪造伪随机用户事件,如点击,触摸,手势系统级的事件,我们可以以随机但可重复的方式使用monkey测试我们开发的应用程序。

在使用之前首先得下载adb,并配置到环境变量

基本使用

基本语法如下,传入可选参数options,以及模拟次数event-count

adb shell monkey [options] <event-count>

例如模拟随机触发1000次,让手机乱点击,所有安装在手机上的应用都有可能被点击

adb shell monkey 1000

如果我们希望指定特定应用,我们可以指定应用的包名,-v可以添加信息

adb shell monkey -p your.package.name -v 500

可以使用以下命令获取帮助信息

adb shell monkey help

更多参数

我直接放上官方表格吧
screenshot from 2018-08-11 08-39-39
screenshot from 2018-08-11 08-41-27

结构体struct

原文链接

#40

struct结构体数据类型

前言

我们知道,在C语言中有一些基本的数据类型,如

  • char
  • int
  • float
  • long
  • double
  • string(c99)

等等数据类型,他们可以表示一些事物的基本属性,但是当我们想表达一个事物的全部或部分属性时,这时候再用单一的基本数据类型明显就无法满足需求了,这时候C提供了一种自定义数据类型,他可以封装多个基本数据类型,这种数据类型叫结构体,英文名称struct,可以使用struct关键词声明结构体

结构体的声明

结构体的声明语法如下

struct [structure tag] /*结构体的标签*/{

   member definition; /*零个或多个成员变量的定义*/
   member definition;
   ...
   member definition;
} [one or more structure variables];  /*一个或多个结构体变量的定义*/

结构体标签(structure tag)是可选的,但是推荐还是写上,这样使得代码更加规范清晰,成员变量的定义一般为基本数据类型,如 int age; char name[10]等,成员变量之间使用;隔开,最后一个成员变量后面的;可选, 如下面定义一个图书信息的结构体变量

struct Books {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book;  

如下所示

struct Books {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id
} book;

我省略了最后一个成员变量后面的分号;代码可以正常运行,但是当我使用gcc编译的时候,出现了下面信息

 gcc struct.c

output

struct.c:8:1: warning: no semicolon at end of struct or union
 } book;
 ^

这是警告提示,提示我们需要在structunion数据类型定义的后面加上分号;,这样的好处就是当我们需要再添加一个成员变量的时候,只需写上该成员变量的定义,而无需先敲;,我太机智了,手动滑稽...

没有成员变量的结构体

我们也可以定义一个空的结构体,有时候我们需要某一个结构体数据类型,但是暂时又不知道如何填充里面的成员变量,我们可以有如下定义

struct Books {
  //TODO
} book;

访问结构体成员

定义完结构体积后接下来就是去访问它并给他赋值,为了访问一个结构体成员变量,我们可以使用成员操作符(.) 成员访问运算符被编码为结构变量名称和我们希望访问的结构成员之间的句点(.)如下所示的完整代码

struct.c

#include <stdio.h>
#include <string.h>

struct Books {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};

int main( ) {

   struct Books Book1;        /* Declare Book1 of type Book */
   struct Books Book2;        /* Declare Book2 of type Book */

   /* book 1 specification */
   strcpy( Book1.title, "C Programming");
   strcpy( Book1.author, "Nuha Ali");
   strcpy( Book1.subject, "C Programming Tutorial");
   Book1.book_id = 6495407;

   /* book 2 specification */
   strcpy( Book2.title, "Telecom Billing");
   strcpy( Book2.author, "Zara Ali");
   strcpy( Book2.subject, "Telecom Billing Tutorial");
   Book2.book_id = 6495700;

   /* print Book1 info */
   printf( "Book 1 title : %s\n", Book1.title);
   printf( "Book 1 author : %s\n", Book1.author);
   printf( "Book 1 subject : %s\n", Book1.subject);
   printf( "Book 1 book_id : %d\n", Book1.book_id);

   /* print Book2 info */
   printf( "Book 2 title : %s\n", Book2.title);
   printf( "Book 2 author : %s\n", Book2.author);
   printf( "Book 2 subject : %s\n", Book2.subject);
   printf( "Book 2 book_id : %d\n", Book2.book_id);

   return 0;
}

编译并执行

gcc struct.c && ./a.out

输出

Book 1 title : C Programming
Book 1 author : Nuha Ali
Book 1 subject : C Programming Tutorial
Book 1 book_id : 6495407
Book 2 title : Telecom Billing
Book 2 author : Zara Ali
Book 2 subject : Telecom Billing Tutorial
Book 2 book_id : 6495700

结构作为函数参数

同样的,我们也可以像基本数据类型一样,把结构体作为函数的参数,如下所示我们定义一个打印结构体的函数

#include <stdio.h>
#include <string.h>

struct Books {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};

/* function declaration */
void printBook( struct Books book );

int main( ) {

   struct Books Book1;        /* Declare Book1 of type Book */
   struct Books Book2;        /* Declare Book2 of type Book */

   /* book 1 specification */
   strcpy( Book1.title, "C Programming");
   strcpy( Book1.author, "Nuha Ali");
   strcpy( Book1.subject, "C Programming Tutorial");
   Book1.book_id = 6495407;

   /* book 2 specification */
   strcpy( Book2.title, "Telecom Billing");
   strcpy( Book2.author, "Zara Ali");
   strcpy( Book2.subject, "Telecom Billing Tutorial");
   Book2.book_id = 6495700;

   /* print Book1 info */
   printBook( Book1 );

   /* Print Book2 info */
   printBook( Book2 );

   return 0;
}

void printBook( struct Books book ) {

   printf( "Book title : %s\n", book.title);
   printf( "Book author : %s\n", book.author);
   printf( "Book subject : %s\n", book.subject);
   printf( "Book book_id : %d\n", book.book_id);
}

编译运行

gcc struct.c && ./a.out

输出

Book 1 title : C Programming
Book 1 author : Nuha Ali
Book 1 subject : C Programming Tutorial
Book 1 book_id : 6495407
Book 2 title : Telecom Billing
Book 2 author : Zara Ali
Book 2 subject : Telecom Billing Tutorial
Book 2 book_id : 6495700

结构体的指针

我们也可以定义结构体指针,像这样

struct Books *struct_pointer;

现在你可以存放结构体变量的地址在结构体变量指针中.和基本数据类型的变量一样,我们使用&操作符取一个变量的地址

struct_pointer = &Book1;

接下来就是使用结构体指针去访问成员变量了,访问的操作符我们由原来的.变为->,没错,这个是不是很形象呢?完整代码如下

#include <stdio.h>
#include <string.h>

struct Books {
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
};

/* function declaration */
void printBook( struct Books *book );
int main( ) {

   struct Books Book1;        /* Declare Book1 of type Book */
   struct Books Book2;        /* Declare Book2 of type Book */

   /* book 1 specification */
   strcpy( Book1.title, "C Programming");
   strcpy( Book1.author, "Nuha Ali");
   strcpy( Book1.subject, "C Programming Tutorial");
   Book1.book_id = 6495407;

   /* book 2 specification */
   strcpy( Book2.title, "Telecom Billing");
   strcpy( Book2.author, "Zara Ali");
   strcpy( Book2.subject, "Telecom Billing Tutorial");
   Book2.book_id = 6495700;

   /* print Book1 info by passing address of Book1 */
   printBook( &Book1 );

   /* print Book2 info by passing address of Book2 */
   printBook( &Book2 );

   return 0;
}

void printBook( struct Books *book ) {

   printf( "Book title : %s\n", book->title);
   printf( "Book author : %s\n", book->author);
   printf( "Book subject : %s\n", book->subject);
   printf( "Book book_id : %d\n", book->book_id);
}

编译运行

gcc struct.c && ./a.out

输出

Book 1 title : C Programming
Book 1 author : Nuha Ali
Book 1 subject : C Programming Tutorial
Book 1 book_id : 6495407
Book 2 title : Telecom Billing
Book 2 author : Zara Ali
Book 2 subject : Telecom Billing Tutorial
Book 2 book_id : 6495700

结构体数组

#include <stdio.h>
#include <string.h>

struct Books {
    char  title[50];
    char  author[50];
    char  subject[100];
    int   book_id;
};

/* function declaration */
void printBook( struct Books *book );
int main( ) {

    struct Books books[2];

    /* book 1 specification */
    strcpy( books[0].title, "C Programming");
    strcpy( books[0].author, "Nuha Ali");
    strcpy( books[0].subject, "C Programming Tutorial");
    books[0].book_id = 6495407;

    /* book 2 specification */
    strcpy( books[1].title, "Telecom Billing");
    strcpy( books[1].author, "Zara Ali");
    strcpy( books[1].subject, "Telecom Billing Tutorial");
    books[1].book_id = 6495700;

    /* print Book1 info by passing address of Book1 */
    printBook( &books[0] );

    /* print Book2 info by passing address of Book2 */
    printBook( &books[1] );

    return 0;
}

void printBook( struct Books *book ) {

    printf( "Book title : %s\n", book->title);
    printf( "Book author : %s\n", book->author);
    printf( "Book subject : %s\n", book->subject);
    printf( "Book book_id : %d\n", book->book_id);
}

编译运行

gcc struct.c && ./a.out

输出

Book 1 title : C Programming
Book 1 author : Nuha Ali
Book 1 subject : C Programming Tutorial
Book 1 book_id : 6495407
Book 2 title : Telecom Billing
Book 2 author : Zara Ali
Book 2 subject : Telecom Billing Tutorial
Book 2 book_id : 6495700

结构体的内存计算

没错,估计你已经知道了,结构体变量的所占用内存空间的大小为各成员变量所占空间之和,如下所示的结构体占用内存大小在注释里面

#include <stdio.h>
#include <string.h>

struct Books {
};

int main( ) {
    printf("%d\n", (int) sizeof(struct Books)); /*0*/
    return 0;
}
#include <stdio.h>
#include <string.h>

struct Books {
    char title[50];
    char author[50];
    char subject[100];
    int book_id;
};

int main() {
    printf("%d\n", (int) sizeof(struct Books)); /*204*/
    return 0;
}

位域

有时候我们内存紧张的时候,我们可以使用位域定义结构体成员变量,比如当我们需要定义一个表示truefalse的时候,如果想这样定义

int isOpen;

明显很浪费空间,因为一个真假值只需要一个字位表示,所以我们可以这样定义

unsigned int isOpen:1;

但是如果你直接写在函数中是会报错的,我们应该写在结构体中

int main() {
    unsigned int isOpen:1; /*编译无法通过*/
    return 0;
}

正确姿势

struct packed_struct {
   unsigned int f1:1;
   unsigned int f2:1;
   unsigned int f3:1;
   unsigned int f4:1;
   unsigned int type:4;
   unsigned int my_int:9;
} pack;

C尽可能紧凑地自动打包上述位字段,前提是字段的最大长度小于或等于计算机的整数字长。如果不是这种情况,那么一些编译器可能允许字段存储器重叠,而其他编译器会将下一个字段存储在下一个字中。

#include <stdio.h>
#include <string.h>

struct packed_struct {
    unsigned int f1:1;
    unsigned int f2:1;
    unsigned int f3:1;
    unsigned int f4:1;
    unsigned int type:4;
    unsigned int my_int:9;
} pack;
int main() {
    printf("%d\n", (int) sizeof(struct packed_struct));
    return 0;
}

输出结果
8

MyBatis学习笔记

安装

想要使用MyBatis首先必须下载mybatis-x.x.x.jar并包含classpath到中.
如果使用Maven构建工具,需要添加依赖到pom.xml中的dependencies子元素中

<dependencies>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.6</version>
    </dependency>
</dependencies>

如果使用的是Gradle构建工具,需要添加依赖到build.gradle

compile group: 'org.mybatis', name: 'mybatis', version: '3.4.6'

构建SqlSessionFactory

SqlSessionFactory可以从SqlSessionFactoryBuilder中构建,而SqlSessionFactoryBuilder可以从配置文件或者从一个配置类中构建,下面是一个获取SqlSessionFactory的🌰

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = null;
try {
    inputStream = Resources.getResourceAsStream(resource);
} catch (IOException e) {
    e.printStackTrace();
}
SqlSessionFactory sqlSessionFactory =
        new SqlSessionFactoryBuilder().build(inputStream);

需要注意的是配置文件需要使用/作为路径分隔符替代包名中的.,可以从getResourceAsStream源码中可见

/*
 * Try to get a resource from a group of classloaders
 *
 * @param resource    - the resource to get
 * @param classLoader - the classloaders to examine
 * @return the resource or null
 */
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
  for (ClassLoader cl : classLoader) {
    if (null != cl) {

      // try to find the resource as passed
      InputStream returnValue = cl.getResourceAsStream(resource);

      // now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
      if (null == returnValue) {
        returnValue = cl.getResourceAsStream("/" + resource);
      }

      if (null != returnValue) {
        return returnValue;
      }
    }
  }
  return null;
}

其中的mybatis-config.xml配置文件是MyBatis框架的核心配置文件,长下面这样
org/mybatis/example/mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

${driver}是从配置文件中读取你的数据库驱动,${url}读取数据库地址,${username} & ${password}则为用户名和密码

SqlSessionFactory中获取SqlSession

通过SqlSessionFactory获取SqlSession,通过SqlSession你可以执行数据库的crud

SqlSession session = sqlSessionFactory.openSession();
try {
  Blog blog = session.selectOne(
    "org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
  session.close();
}

CentOS安装Shadowsocks

CentOS 7 安装 Shadowsocks

简介

CentOS简介

CentOS(Community Enterprise Operating System)是Linux发行版之一,它是来自于Red Hat Enterprise Linux依照开放源代码规定发布的源代码所编译而成。由于出自同样的源代码,因此有些要求高度稳定性的服务器以CentOS替代商业版的Red Hat Enterprise Linux使用。两者的不同,在于CentOS并不包含封闭源代码软件。CentOS 对上游代码的主要修改是为了移除不能自由使用的商标。2014年,CentOS宣布与Red Hat合作,但CentOS将会在新的委员会下继续运作,并不受RHEL的影响

Shadowsocks简介

Shadowsocks可以指:一种基于Socks5代理方式的加密传输协议,也可以指实现这个协议的各种传输包。目前包使用Python、C、C++、C#、Go语言等编程语言开发,大部分主要实现(iOS平台的除外)采用Apache许可证、GPL、MIT许可证等多种自由软件许可协议开放源代码。shadowsocks分为服务器端和客户端,在使用之前,需要先将服务器端部署到服务器上面,然后通过客户端连接并创建本地代理。
在**大陆,本工具也被广泛用于突破防火长城(GFW),以浏览被封锁、屏蔽或干扰的内容。2015年8月22日,Shadowsocks原作者Clowwindy称受到了**政府的压力,宣布停止维护此计划(项目)并移除其个人页面所存储的源代码。因为移除之前就有大量的复制副本,所以事实上并未停止维护,而是转由其他贡献者们持续维护中。

VPS简介

虚拟专用服务器(英语:Virtual private server,缩写为 VPS),是将一台服务器分区成多个虚拟专享服务器的服务。实现VPS的技术分为容器技术和虚拟机技术 。在容器或虚拟机中,每个VPS都可分配独立公网IP地址、独立操作系统、实现不同VPS间磁盘空间、内存、CPU资源、进程和系统配置的隔离,为用户和应用程序模拟出“独占”使用计算资源的体验。VPS可以像独立服务器一样,重装操作系统,安装程序,单独重启服务器。VPS为用户提供了管理配置的自由,可用于企业虚拟化,也可以用于IDC资源租用。 IDC资源租用,由VPS提供商提供。不同VPS提供商所使用的硬件VPS软件的差异,及销售策略的不同,VPS的使用体验也有较大差异。尤其是VPS提供商超卖,导致实体服务器超负荷时,VPS性能将受到极大影响。相对来说,容器技术比虚拟机技术硬件使用效率更高,更易于超卖,所以一般来说容器VPS的价格都高于虚拟机VPS的价格。 这些VPS主机以最大化的效率共享硬件、软件许可证以及管理资源。每个VPS主机都可分配独立公网IP地址、独立操作系统、独立超大空间、独立内存、独立CPU资源、独立执行程序和独立系统配置等。VPS主机用户可在服务器上自行安装程序,单独重启主机。

SSH简介

Secure Shell(安全外壳协议,简称SSH)是一种加密的网络传输协议,可在不安全的网络中为网络服务提供安全的传输环境。SSH通过在网络中创建安全隧道来实现SSH客户端与服务器之间的连接。虽然任何网络服务都可以通过SSH实现安全传输,SSH最常见的用途是远程登录系统,人们通常利用SSH来传输命令行界面和远程执行命令。使用频率最高的场合类Unix系统,但是Windows操作系统也能有限度地使用SSH。2015年,微软宣布将在未来的操作系统中提供原生SSH协议支持。

购买VPS

Google VPS提供商会有很多选择,或者使用我推荐的VPS(不强制要求),本教程推荐使用Vultr

创建VPS

注册完对应厂商的VPS账号并充值后,你就可以开始创建VPS服务器了,但是每个提供商的VPS控制面板不一样,创建要求选择操作系统类型,本教程选择CentOS 7,创建完成后你可以在控制面板累看到以下信息

  • 服务器地址(IP Address)如8.8.8.8
  • 用户名(Username)大部分情况是root
  • 密码(Password)一般很复杂,比如a5X%![jnqsYr.1TW
  • SSH端口号(Port)一般是22

以上信息非常重要!!!是你能不能登陆一台VPS服务器的关键,有一个地方❌均无法远程连接到服务器

连接VPS

VPS服务器一般位于国外(国内的VPS是无法用于科学上网滴),我们需要使用SSH去连接VPS.我推荐几款各大平台的SSH客服端,类Unix操作系统自带SSH客服端

SSH客服端

SSH 连接VPS

ssh [email protected] -p 22

介绍这里的几个参数

  • ssh就是SSH客服端,你可以下载对应平台的SSH客服端
  • root为服务器用户名
  • 140.82.22.61为服务器地址(替换你自己的服务器地址)
  • -p 22为指定ssh监听的端口号为22,p是port(端口号)的缩写,默认是22所以可以不写,忽略后的命令ssh [email protected]

执行上面的命令,输入服务器密码,如果都正确输入的话就可以远程控制服务器了

更新VPS

如果你的VPS软件包没有更新,执行以下命令

yum update -y && yum upgrade -y

安装Shadowsocks

yum install python-setuptools && easy_install pip && pip install shadowsocks

运行Shadowsocks

对以下涉及到的参数说明一下

  • -p 指定端口号
  • -k 指定密码
  • -m 指定加密方式

简单运行

这种方式运行的缺点就是一旦终端与服务器断开连接,进程就被杀了,无法继续科学上网,可以使用下面的后台运行

ssserver -p 443 -k password -m rc4-md5

后台运行

sudo ssserver -p 443 -k password -m rc4-md5 --user nobody -d start

停止服务器

sudo ssserver -d stop

配置文件运行Shadowsocks

创建配置文件

cat <<EOT > /etc/shadowsocks.json
{
    "server":"0.0.0.0",
    "server_port":8388,
    "local_address": "127.0.0.1",
    "local_port":1080,
    "password":"mypassword",
    "timeout":300,
    "method":"aes-256-cfb",
    "fast_open": false
}
EOT

前台运行

ssserver -c /etc/shadowsocks.json

后台运行

ssserver -c /etc/shadowsocks.json -d start
ssserver -c /etc/shadowsocks.json -d stop

防火墙

请打Shadowsocks监听端口号,或者直接关闭防火墙

CentOS 7

打开端口号

sudo firewall-cmd --zone=public --add-port=443/tcp --permanent
sudo firewall-cmd --reload

关闭防火墙

systemctl stop firewalld

CentOS 6

打开端口号

iptables -A INPUT -m state --state NEW -p tcp --dport 443 -j ACCEPT
/etc/init.d/iptables restart

关闭防火墙

service iptables stop  

数据结构与算法之线性表-顺序表实现(C语言版本)

原文托管在Github: #52

数据结构与算法之线性表-顺序表实现(C语言版本)

前言

数据结构与算法是一个程序员必备的技能之一,而顺序表更是每个程序员在面试过程中要经常被问到的,如Java语言中的ArrayList类的底层实现就是使用顺序表实现的,别把顺序表想的有多么高大上,其实就是使用数组实现的一种线性表

什么是线性表

线性表(英语:Linear List)是由n(n≥0)个数据元素(结点)a[0],a[1],a[2]…,a[n-1]组成的有限序列。
其中:

  • 数据元素的个数n定义为表的长度 = "list".length() ("list".length() = 0(表里没有一个元素)时称为空表)
  • 将非空的线性表(n>=1)记作:(a[0],a[1],a[2],…,a[n-1])
  • 数据元素a[i](0≤i≤n-1)只是个抽象符号,其具体含义在不同情况下可以不同

一个数据元素可以由若干个数据项组成。数据元素称为记录,含有大量记录的线性表又称为文件。这种结构具有下列特点:存在一个唯一的没有前驱的(头)数据元素;存在一个唯一的没有后继的(尾)数据元素;此外,每一个数据元素均有一个直接前驱和一个直接后继数据元素。

什么是顺序表

顺序表是在计算机内存中以数组的形式保存的线性表,是指用一组地址连续的存储单元依次存储数据元素的线性结构。

顺序表的实现

为了能实现顺序表的基本操作如(增,删,改,查),我们使用结构体封装一个指向一维数组的指针base,同时提供一个名字叫做length的整型变量表示顺序表中实际有用的元素个数,当插入一个元素时length++, 当删除一个元素时length--,所以length可以记录当前顺序表的长度

顺序表综合案列-学生信息管理系统

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>
#include <assert.h>

#define INIT_SIZE 1
#define INCREMENT_SIZE 5
typedef struct {
    int id; //学号
    int age; //年龄
    char name[10]; //姓名
} Student;

typedef Student ElemType;

typedef struct {
    ElemType *base;
    int size; /* 顺序表的最大容量 */
    int length; /* 记录顺序表的元素个数 */
} SqList;

/**
 * 初始化顺序表
 * @param p 指向顺序表的指针
 * @return 如果初始化成功返回true否则返回false
 */
bool init(SqList *p) {
    p->base = malloc(sizeof(SqList) * INIT_SIZE);
    if (p->base == NULL) {
        return false;
    }
    p->size = INIT_SIZE;
    p->length = 0;
    return true;
}

/**
 * 指定位置插入数据元素
 * @param p 指向顺序表的指针
 * @param index 插入的下标
 * @param elem 插入的元素值
 * @return 如果插入成功返回true,否则返回false
 */
bool insert(SqList *p, int index, ElemType elem) {
    if (index < 0 || index > p->length) {
        perror("插入下标不合法");
        return false;
    }

    //如果顺序表满了,则重新分配更大的容量
    if (p->length == p->size) {
        int newSize = p->size + INCREMENT_SIZE;
        ElemType *newBase = realloc(p->base, newSize);
        if (newBase == NULL) {
            perror("顺序表已满,重新分配内存失败");
            return false;
        }
        p->base = newBase;
        p->size = newSize;
    }

    //从最后一个元素开始依次把元素复制到后面的位置
    for (int i = p->length - 1; i >= index; --i) {
        p->base[i + 1] = p->base[i];
    }
    p->base[index] = elem;
    p->length++;
    return true;
}

/**
 * 删除指定下标的数据元素
 * @param p 指向顺序表的指针
 * @param index 删除的元素的下标
 * @param elem 返回删除的元素
 * @return 如果删除成功返回true否则返回false
 */
bool del(SqList *p, int index, ElemType *elem) {
    if (p->length == 0) {
        perror("顺序是空的,无法执行删除操作");
        return false;
    }
    if (index < 0 || index > p->length - 1) {
        perror("删除位置不合法");
        return false;
    }

    //把删除的元素保存起来
    *elem = p->base[index];
    //从删除位置开始依次把后面的元素赋值到前面
    for (int i = index; i < p->length - 1; ++i) {
        p->base[i] = p->base[i + 1];
    }
    p->length--;
    return true;
}

/**
 * 更新顺序表中特定的元素值
 * @param p 指向顺序表的指针
 * @param index 更新下标
 * @param elem 更改后的新元素值
 * @return 如果更改成功则返回true,否则返回false
 */
bool update(SqList *p, int index, ElemType elem) {
    if (p->length == 0) {
        perror("顺序表是空的,无法指向更新");
        return false;
    }

    if (index < 0 || index > p->length - 1) {
        perror("更新下标不合法");
        return false;
    }

    p->base[index] = elem;
    return true;
}

/**
 * 搜索顺序表中特定下标的元素
 * @param list 指定的顺序表
 * @param index 搜索的下标
 * @param elem 保存搜索到的元素
 * @return 如果搜索成功则返回true,否则返回false
 */
bool search(SqList list, int index, ElemType *elem) {
    if (list.length == 0) {
        perror("顺序表没有任何元素,无法查找");
        return 0;
    }

    if (index < 0 || index > list.length - 1) {
        perror("查找下标不合法");
        return false;
    }

    *elem = list.base[index - 1];
    return true;
}

/**
 * 打印顺序表
 * @param list 指定顺序表
 */
void output(SqList list) {
    printf("学号\t年龄\t姓名\n");
    for (int i = 0; i < list.length; ++i) {
        printf("%d\t%d\t%s\n", list.base[i].id, list.base[i].age, list.base[i].name);
    }
    printf("\n");
}

int main() {
    SqList list;
    while (1) {
        printf("\t\t顺序表的基本操作\n");
        printf("\t\t1.初始化顺序表\n");
        printf("\t\t2.顺序表的插入\n");
        printf("\t\t3.顺序表的删除\n");
        printf("\t\t4.顺序表的查找(下标)\n");
        printf("\t\t5.顺序表的修改\n");
        printf("\t\t6.打印\n");
        printf("\t\t0.退出\n");

        int choice;
        printf("请输入功能编号:");
        scanf("%d", &choice);

        switch (choice) {
            case 1:
                if (init(&list)) {
                    printf("初始化成功\n");
                }
                assert(list.length == 0);
                break;
            case 2:;
                ElemType elem;
                printf("请输入插入的学生学号:");
                scanf("%d", &elem.id);
                printf("请输入插入的学生年龄:");
                scanf("%d", &elem.age);
                printf("请输入插入的学生姓名:");
                scanf("%s", elem.name);

                printf("请输入插入位置:");
                int index;
                scanf("%d", &index);

                if (insert(&list, index, elem)) {
                    printf("插入成功\n");
                }
                break;
            case 3:
                printf("请输入删除位置:");
                scanf("%d", &index);
                if (del(&list, index, &elem)) {
                    printf("删除的学生 学号:%d\t年龄:%d\t姓名:%s\n", elem.id, elem.age, elem.name);
                }
                break;
            case 4:
                printf("请输入要查找的位置:");
                scanf("%d", &index);
                if (search(list, index, &elem)) {
                    printf("查找的学生 学号:%d\t年龄:%d\t姓名:%s\n", elem.id, elem.age, elem.name);
                }
                break;
            case 5:
                printf("请输入更新位置:");
                scanf("%d", &index);

                printf("请输入更新后的学生学号:");
                scanf("%d", &elem.id);
                printf("请输入更新后的学生年龄:");
                scanf("%d", &elem.age);
                printf("请输入更新后的学生姓名:");
                scanf("%s", elem.name);
                if (update(&list, index, elem)) {
                    printf("更新成功\n");
                }
                break;
            case 6:
                output(list);
                break;
            case 0:
                exit(0);
            default:
                printf("输入编号有误,请重新输入\n");
                break;
        }
    }
    return 0;
}

顺序表优缺点

  • 优点
    • 因为顺序表使用数组实现,可以通过下标存取,所以存取速度极快,T(n)=O(1)
    • 无需为表中元素之间的逻辑关系而增加额外的存储空间
    • 空间利用效率高
  • 缺点
    • 插入删除均需要循环移动元素,比较浪费时间 T(n)=O(n)
    • 需要提前预估顺序表的长度(INIT_SIZE),分配太大浪费,造成内存的碎片化

Android中的Parcelable详解

原文链接

#27

这篇主要介绍如何在Android中实现Parcelable

Bundle对象传递数据

我们都知道,可以使用Bundle对象在Andriod各大组建之间传递数据,使用Bundle.putXXX(KEY, VALUE)可以存放一些基本数据类型以及封装类型,以下是Bundle对象可以存储的数据类型

  • primitives(基本数据类型)
  • String
  • Serializable
  • Parcelable

存储的时候使用键值的方式进行存储并传递

如果我们需要使用Bundle传递对象,我们可以implements Serializable,或者implements Parcelable

实现Serializable接口

这种方式非常简单,我们直接实现Serializable接口就行,这个接口是一个空的接口,所以我们不需要做任何操作,如果有必要我们可以定义一个serialVersionUID

public interface Serializable {
}

我们常见的File类就实现了这个接口

public class File
    implements Serializable, Comparable<File>
{
  ...
}

实现Parcelable接口

这个相比与 Serializable稍微复杂一点,不过是 Google推荐的方式,我们来看看官方给的一个例子

public class MyParcelable implements Parcelable {
    private int mData;

    public int describeContents() {
        return 0;
    }

    public void writeToParcel(Parcel out, int flags) {
        out.writeInt(mData);
    }

    public static final Parcelable.Creator<MyParcelable> CREATOR
            = new Parcelable.Creator<MyParcelable>() {
        public MyParcelable createFromParcel(Parcel in) {
            return new MyParcelable(in);
        }

        public MyParcelable[] newArray(int size) {
            return new MyParcelable[size];
        }
    };

    private MyParcelable(Parcel in) {
        mData = in.readInt();
    }
}

这个例子中的对象只包含了一个属性mData,我们在看一个例子

public class User implements Parcelable {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    protected User(Parcel in) {
        name = in.readString();
        age = in.readInt();
    }

    public static final Creator<User> CREATOR = new Creator<User>() {
        @Override
        public User createFromParcel(Parcel in) {
            return new User(in);
        }

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

传递Parcelable对象并解析

传递

User user = new User("shellhub", 23);
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra(KEY, user);
startActivity(intent);

解析

User user = getIntent().getExtras().getParcelable(KEY);

因为方法实现了泛型,所以不需要类型转换

public <T extends Parcelable> T getParcelable(@Nullable String key) {

Serializable 和 Parcelable之间的区别

  • Parcelable比 Serializable快
  • Parcelable需要花更多的时间写更多的代码
  • Serializable实现更加简单
  • Serializable接口会创建大量临时对象并导致相当多的垃圾回收
  • Parcelable数组可以通过android中的Intent传递
public @NonNull Intent putExtra(String name, Parcelable[] value) {

效率对比

1 d4iacvhmfirbgr4c0yqcvw

Q&A

对象中定义的CREATOR可以改为其他名字么?

  • 不可以,如果定义为其他名字会报以下错误
Caused by: android.os.BadParcelableException: Parcelable protocol requires a Parcelable.Creator object called CREATOR on class com.github.shellhub.parcelable.User

示例代码

https://github.com/shellhub/android-samples/tree/master/Parcelable

macOS上使用Shadowsocks科学上网

macOS上使用Shadowsocks科学上网

Step 1

前往Github官网https://github.com/shadowsocks/ShadowsocksX-NG/releases下载最新版的zip压缩包

Step 2

双击下载下来的压缩包进行解压

Step 3

双击打开解压后的Shadowsocks可执行文件,会弹出安全提示,选择Open | 打开确认打开

Step 4

  • 手动配置节点
    单机右上角的纸飞机✈️图标,可以选择Servers | 服务器Servers Preferences | 服务器配置手动配置节点信息,点击左下脚的➕添加配置信息,从左往右,从下网上分别对应填写
    服务器地址端口号加密方式密码备注,配置🌰
    example-config

Step 5

点击Ok | 确认,访问以下任一站点测试

Video

IMAGE ALT TEXT HERE

okhttp 使用及深入理解

okhttp简介

什么是okhttp

An HTTP+HTTP/2 client for Android and Java applications.

官方对okhttp的概括就是一个在Android平台和Java平台支持HTTP和HTTP2的网络请求库

官网传送门

下载okhttp

现在开发主要使用项目构建工具如Maven, Gradle。我们也可以直接下载jar包的形式

<dependency>
 <groupId>com.squareup.okhttp3</groupId>
 <artifactId>okhttp</artifactId>
 <version>3.11.0</version>
</dependency>
  • Gradle依赖(主要在Android项目和Java中使用)
implementation 'com.squareup.okhttp3:okhttp:3.11.0'

okhttp基本使用

先介绍一下okhttp的优点

  • 支持连接池
  • GZIP格式压缩网络数据,减少网络流量
  • Response的缓存,避免重复的网络请求

当网络中断或者延迟比较高时,okhttp会坚守阵地,当网络恢复时okhttp会默默的重新请求网络,如果你的服务器有多个备用地址,当Okhttp尝试连接时失败了,它会继续尝试新的其他的IP,这对于IPv4 + IPv6和冗余数据中心中托管的服务是必需的。 OkHttp使用现代TLS功能(SNI,ALPN)启动新连接,如果握手失败则回退到TLS 1.0

使用OkHttp很简单。它的请求/响应API采用流畅的构建器和不变性设计。它支持同步阻塞调用和带回调的异步调用。

OkHttp支持Android 2.3及更高版本。对于Java,最低要求是1.7。

Talk is check. Let me show you code

Example 1: 下载url

这个代码会下载指定url的内容,并打印的响应体的字符串表示 (完整代码)

OkHttpClient client = new OkHttpClient();

String run(String url) throws IOException {
  Request request = new Request.Builder()
      .url(url)
      .build();

  Response response = client.newCall(request).execute();
  return response.body().string();
}

Example 2: 向服务器提交数据 (完整代码)

public static final MediaType JSON
    = MediaType.parse("application/json; charset=utf-8");

OkHttpClient client = new OkHttpClient();

String post(String url, String json) throws IOException {
  RequestBody body = RequestBody.create(JSON, json);
  Request request = new Request.Builder()
      .url(url)
      .post(body)
      .build();
  Response response = client.newCall(request).execute();
  return response.body().string();
}

Example 3: okhttp设置代理

public static void main(String[] args) {

    /** 使用Builder构建模式实例化 okhttp **/
    OkHttpClient client = new OkHttpClient.Builder()
            .proxy(new Proxy(Proxy.Type.SOCKS,
                    new InetSocketAddress("localhost", 1080)))
            .build();

    /*这个网站被GFW墙了,我们通过ss代理下载它*/
    Request request = new Request.Builder().url("https://www.google.com")
            .build();

    try {
        System.out.println(client.newCall(request).execute().body.string());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

更多okhttp的使用案例我们可以参考 okhttp的 wiki文档

Rxjava2

Rxjava2

首先我们看看什么是RxJava,引用Github主页的话

RxJava – Reactive Extensions for the JVM – a library for composing asynchronous and event-based programs using observable sequences for the Java VM.

前言

在学习RxJava之前我们首先要知道什么叫观察者模式,RxJava中有三个概念

  • 观察者 Observer
  • 被观察者 Observable
  • 订阅 subscribe

如果不好理解这三个概念那我举个例子

我们经常在社交平台观看喜欢的up主上传视频,如果我们喜欢该up主,我们就会订阅他,当他上传新的视频的时候,我们会收到系统的通知,观看的他的视频,如国外的YouTube国内的Bilibili都有订阅功能,到这儿就很清晰的理解上面提到的三种关系

  • 频道主 - 被观察者 Observable
  • 粉丝 - 观察者 Observer
  • 订阅 - 绑定频道主和粉丝之间的关系

现在知道三者之间的关系了吧,一句话总结就是被观察者产生事件源,观察者订阅了被观察者后会接收被观察者产生的事件源

添加依赖

Maven依赖

<dependencies>
    <dependency>
        <groupId>io.reactivex.rxjava2</groupId>
        <artifactId>rxjava</artifactId>
        <version>2.2.4</version>
    </dependency>
</dependencies>

Gradle依赖

implementation "io.reactivex.rxjava2:rxjava:2.2.4"

获取 最新的版本号

开始RxJava2

本课程所有代码均为RxJava2版本测试通过

Hello World

按照编程的常规,我们也从地一个Hello World开始学习RxJava吧

HelloWorld.java

public class HelloWorld {
    public static void main(String[] args) {
        Flowable.just("Hello world").subscribe(System.out::println);
    }
}

如果你的环境还不支持java 8 lambdas,你得手动创建一个Consumer匿名内部类

public class HelloWorld {
    public static void main(String[] args) {
        Flowable.just("Hello world").subscribe(new Consumer<String>() {
            @Override
            public void accept(String s) throws Exception {
                System.out.println(s);
            }
        });
    }
}

output

Hello world

Runtime

Observable.create(emitter -> {
    while (!emitter.isDisposed()) {
        long time = System.currentTimeMillis();
        emitter.onNext(time);
        if (time % 2 != 0) {
            emitter.onError(new IllegalStateException("Odd millisecond!"));
            break;
        }
    }
}).subscribe(System.out::print, Throwable::printStackTrace);

without lambdas

Observable.create(new ObservableOnSubscribe<Long>() {
    @Override
    public void subscribe(ObservableEmitter<Long> emitter) throws Exception {
        while (!emitter.isDisposed()) {
            long time = System.currentTimeMillis();
            emitter.onNext(time);
            if (time % 2 != 0) {
                emitter.onError(new IllegalStateException("Odd millisecond!"));
            }
        }
    }
}).subscribe(new Consumer<Long>() {
    @Override
    public void accept(Long aLong) throws Exception {
        System.out.println(aLong);
    }
}, new Consumer<Throwable>() {
    @Override
    public void accept(Throwable throwable) throws Exception {
        throwable.printStackTrace();
    }
});

output

1544686029328
1544686029329
java.lang.IllegalStateException: Odd millisecond!

更新中...

如何在Linux平台下科学上网

YouTube视频教学

购买高速Shadowsocks账号

首先运行Shadowsocks

如使用 nohup sslocal -c config.json & 后台运行Shadowsocks,假如你的本地机器运行以下ip和port

127.0.0.1 1080

安装proxychains

On Debian/Ubuntu:

apt-get install proxychains

On Centos

yum install proxychains

On Mac OS X:

brew install proxychains-ng

创建配置文件

mkdir ~/.proxychains/ && touch proxychains.conf

编辑配置文件

vim ~/.proxychains/proxychains.conf

strict_chain
proxy_dns
remote_dns_subnet 224
tcp_read_time_out 15000
tcp_connect_time_out 8000
localnet 127.0.0.0/255.0.0.0
quiet_mode

[ProxyList]
socks5  127.0.0.1 1080

运行

使用proxychains运行命令

proxychains google-chrome
proxychains4 curl https://www.twitter.com/
proxychains4 git push origin master

或者这样

proxychains4 bash
wget https://www.google.com
git push origin master

注意:

如果没有安装dig发现无法上网,因为proxychains使用了dig工具

安装dig

ubuntu/debian

sudo apt-get install dnsutils

centos

sudo yum install bind-utils

二分查找算法

在计算机科学中,二分搜索(英语:binary search),也称折半搜索(英语:half-interval search)[1]、对数搜索(英语:logarithmic search)[2],是一种在有序数组中查找某一特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。

二分搜索在情况下的复杂度是对数时间,进行 O(logn) 次比较操作 n 在此处是数组的元素数量, O 是大O记号,(log 是对数)。二分搜索使用常数空间,无论对任何大小的输入数据,算法使用的空间都是一样的。除非输入数据数量很少,否则二分搜索比线性搜索更快,但数组必须事先被排序。尽管特定的、为了快速搜索而设计的数据结构更有效(比如哈希表),二分搜索应用面更广。

二分搜索有许多中变种。比如分散层叠可以提升在多个数组中对同一个数值的搜索。分散层叠有效的解决了计算几何学和其他领域的许多搜索问题。指数搜索将二分搜索拓宽到无边界的列表。二分搜索树和B树数据结构就是基于二分搜索的。

下面gif图对比了普通的顺序查找以及二分查找

二分查找以及线性查找gif图

非递归代码实现

#include <stdio.h>

/**
 * 实现二分查找
 * @param array 查找的对应的数组
 * @param len 数组长度
 * @param key 查找的关键词
 * @return 元素位置
 */
int binarySearch(const int array[], int len, int key){
    int low = 0;
    int high = len - 1;
    while (low <= high) {
        int middle = (low + high) / 2;
        int midVal = array[middle];
        if (midVal == key) {
            return middle;
        } else if (key > midVal) {
            low = middle + 1;
        } else {
            high = middle - 1;
        }
    }
    return -1; //-1表示未找到
}


int main(){
    int array[] = {-1, 0, 1, 2, 3, 4, 5};

    int key; //查找关键词
    printf("请输入查找的关键词:");
    scanf("%d", &key);

    int len = sizeof(array) / sizeof(array[0]); //求出数组长度
    int position = binarySearch(array, len, key);
    if (position != -1) {
        printf("关键词%d查找成功,下标为%d\n", key, position);
    } else {
        printf("数组中无关键词%d\n", position);
    }
    return 0;
}

递归代码实现

#include <stdio.h>

/**
 * 使用二分查找某关键词在数组的特定的下标
 * @param array 查找的数组
 * @param fromIndex 查找的起始下标
 * @param toIndex 查找的终止下标
 * @param key 查找的关键词
 * @return 返回关键词在数组中的索引
 */
int binarySearch(const int array[], int fromIndex, int toIndex, int key) {
    int low = fromIndex;
    int high = toIndex;
    int middle = (low + high) / 2;

    int midVal = array[middle];
    if (key == midVal) { //关键词在查找到
        return middle;
    } else if (key > midVal) { //关键词在右半部分 
        low = middle + 1;
    } else {
        high = middle - 1; //关键词在左半部分
    }

    if (low > high) {
        return -1; //无此关键词,返回-1表示没有查找成功
    }
    return binarySearch(array, low, high, key);
}


int main() {
    int array[] = {-1, 0, 1, 2, 3, 4, 5};

    int key; //查找关键词
    printf("请输入查找的关键词:");
    scanf("%d", &key);

    int len = sizeof(array) / sizeof(array[0]); //求出数组长度
    int position = binarySearch(array, 0, len - 1, key);
    if (position != -1) {
        printf("关键词%d查找成功,下标为%d\n", key, position);
    } else {
        printf("数组中无关键词%d\n", key);
    }
    return 0;
}

参考链接

Java Native Interface(JNI)详细教程

Java Native Interface(JNI)详细教程

maxresdefault

1. Introduction

在有的时候你可能会使用一些本地代码去(Native Code)如C/C++去解决Java性能问题,可以调用如C/C++等语言编写的库(library),这种技术我们叫做Java Native Interface(JNI)。

在使用JNI之前你还应该熟悉以下技术

  1. java
  2. C/C++
  3. gcc/g++ (unix)
  4. Cygwin/MinGW (Windows)
  5. bash/cmd

2. Getting Startted

2.1 使用C实现JNI

Step 1. 第一步: 创建一个HelloJNI.java类

HelloJNI.java

/**
 * 本类主要使用C实现JNI技术
 */
public class HelloJNI {
    /**
     * 运行时加载Native代码
     * 加载hello.dll (Windows)libhello.so (Unixes)
     * 无需加上库的扩展名
     */
    static {
        System.loadLibrary("hello");
    }

    /**
     * 声明native方法由C语言实现
     */
    private native void sayHello();

    //入口函数
    public static void main(String[] args) {
        // invoke the native method
        new HelloJNI().sayHello();
    }
}

注意⚠️:

  1. hello无需加扩展名。
  2. 库必须被包含在java库路径中(保存在java系统变量java.library.path中),否则会抛出UnsatisfiedLinkError异常,可以通过配置VM参数-Djava.library.path=/path/to/lib把JNI库包含到java库路径中去。

Step 2. 编译java源代码文件并生成对应的C/C++头文件(header file)HelloJNI.h

  • JDK 1.8之前的版本需要使用javah工具生成头文件

    javac HelloJNI.java && javah HelloJNI
  • JDK 1.8及以后可以直接通过javac并跟上参数-h生成对应的头文件,用法如下⬇️
    -h <directory> Specify where to place generated native header files

    javac -h . HelloJNI.java

自动生成的头文件HelloJNI.h内容如下⬇️

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJNI
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJNI_sayHello
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

可以看到头文件中声明了一个函数名为Java_HelloJNI_sayHello的函数,生成的函数名规则如下⬇️
Java_{package_and_classname}_{function_name}(JNI_arguments),其中包名中的点(.)应该用下划线(_)代替,如一个类com.github.shellhub.Application对应的JNI函数名为Java_com_github_shellhub_Application_sayHello

JNI函数参数说明

  • JNIEnv * 指向JNI环境的指针,便于访问所有JNI函数
  • jobject 当前java对象的引用this

在这个例子暂时不使用这两个参数,但是后面会使用到

Step 3. 使用C语言实现JNI函数
HelloJNI.c

#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h"

// Implementation of native method sayHello() of HelloJNI class
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
   //在控制台打印字符串Hello World!
   printf("Hello World!\n");
   return;
}

头文件jni.h位于JAVA_HOME\include目录下,使用以下命令可以查看具体位置

  • Unix
    find $(echo $JAVA_HOME) -name jni.h
  • Windows
    cd <JAVA_HOME>\include\win32

Step 4. 编译C程序
不同平台有不同的编译器,待续...ing

SQL经典练习题及答案

查找最晚入职员工的所有信息

CREATE TABLE `employees` (
  `emp_no`     int(11)     NOT NULL,
  `birth_date` date        NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name`  varchar(16) NOT NULL,
  `gender`     char(1)     NOT NULL,
  `hire_date`  date        NOT NULL,
  PRIMARY KEY (`emp_no`)
);

Solution:

SELECT *
FROM employees
WHERE
  hire_date = (SELECT MAX(hire_date)
               FROM employees);

查找入职员工时间排名倒数第三的员工所有信息

CREATE TABLE `employees` (
  `emp_no`     int(11)     NOT NULL,
  `birth_date` date        NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name`  varchar(16) NOT NULL,
  `gender`     char(1)     NOT NULL,
  `hire_date`  date        NOT NULL,
  PRIMARY KEY (`emp_no`)
);

Solution:

SELECT *
FROM employees
ORDER BY hire_date DESC
LIMIT 2, 1;

查找各个部门当前(to_date='9999-01-01')领导当前薪水详情以及其对应部门编号dept_no

CREATE TABLE `dept_manager` (
  `dept_no`   char(4) NOT NULL,
  `emp_no`    int(11) NOT NULL,
  `from_date` date    NOT NULL,
  `to_date`   date    NOT NULL,
  PRIMARY KEY (`emp_no`, `dept_no`)
);
CREATE TABLE `salaries` (
  `emp_no`    int(11) NOT NULL,
  `salary`    int(11) NOT NULL,
  `from_date` date    NOT NULL,
  `to_date`   date    NOT NULL,
  PRIMARY KEY (`emp_no`, `from_date`)
);

Solution:

SELECT
  s.*,
  d.dept_no
FROM salaries s, dept_manager d
WHERE s.to_date = '9999-01-01'
      AND d.to_date = '9999-01-01'
      AND s.emp_no = d.emp_no;

查找所有已经分配部门的员工的last_name和first_name,以及部门编号dept_no

CREATE TABLE `dept_emp` (
  `emp_no`    int(11) NOT NULL,
  `dept_no`   char(4) NOT NULL,
  `from_date` date    NOT NULL,
  `to_date`   date    NOT NULL,
  PRIMARY KEY (`emp_no`, `dept_no`)
);
CREATE TABLE `employees` (
  `emp_no`     int(11)     NOT NULL,
  `birth_date` date        NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name`  varchar(16) NOT NULL,
  `gender`     char(1)     NOT NULL,
  `hire_date`  date        NOT NULL,
  PRIMARY KEY (`emp_no`)
);

Solution:

SELECT
  e.last_name,
  e.first_name,
  d.dept_no
FROM employees e, dept_emp d
WHERE e.emp_no = d.emp_no;

SELECT
  employees.last_name,
  first_name,
  dept_emp.dept_no
FROM employees
  INNER JOIN dept_emp on employees.emp_no = dept_emp.emp_no;

查找所有员工的last_name和first_name以及对应部门编号dept_no,也包括展示没有分配具体部门的员工

CREATE TABLE `dept_emp` (
  `emp_no`    int(11) NOT NULL,
  `dept_no`   char(4) NOT NULL,
  `from_date` date    NOT NULL,
  `to_date`   date    NOT NULL,
  PRIMARY KEY (`emp_no`, `dept_no`)
);
CREATE TABLE `employees` (
  `emp_no`     int(11)     NOT NULL,
  `birth_date` date        NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name`  varchar(16) NOT NULL,
  `gender`     char(1)     NOT NULL,
  `hire_date`  date        NOT NULL,
  PRIMARY KEY (`emp_no`)
);

Solution:

SELECT
  employees.last_name,
  first_name,
  dept_emp.dept_no
FROM employees
  LEFT JOIN dept_emp ON dept_emp.emp_no = employees.emp_no;

查找所有员工入职时候的薪水情况,给出emp_no以及salary, 并按照emp_no进行逆序

CREATE TABLE `employees` (
  `emp_no`     int(11)     NOT NULL,
  `birth_date` date        NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name`  varchar(16) NOT NULL,
  `gender`     char(1)     NOT NULL,
  `hire_date`  date        NOT NULL,
  PRIMARY KEY (`emp_no`)
);
CREATE TABLE `salaries` (
  `emp_no`    int(11) NOT NULL,
  `salary`    int(11) NOT NULL,
  `from_date` date    NOT NULL,
  `to_date`   date    NOT NULL,
  PRIMARY KEY (`emp_no`, `from_date`)
);

Solution:

SELECT
  e.emp_no,
  s.salary
FROM employees AS e INNER JOIN salaries AS s
    ON e.emp_no = s.emp_no AND e.hire_date = s.from_date
ORDER BY e.emp_no DESC;

SELECT
  e.emp_no,
  s.salary
FROM employees AS e, salaries AS s
WHERE e.emp_no = s.emp_no AND e.hire_date = s.from_date
ORDER BY e.emp_no DESC;

查找薪水涨幅超过15次的员工号emp_no以及其对应的涨幅次数t

CREATE TABLE `salaries` (
  `emp_no`    int(11) NOT NULL,
  `salary`    int(11) NOT NULL,
  `from_date` date    NOT NULL,
  `to_date`   date    NOT NULL,
  PRIMARY KEY (`emp_no`, `from_date`)
);

Solution:

SELECT
  emp_no,
  COUNT(*) AS t
FROM salaries
GROUP BY emp_no
HAVING t > 15;

找出所有员工当前(to_date='9999-01-01')具体的薪水salary情况,对于相同的薪水只显示一次,并按照逆序显示

CREATE TABLE `salaries` (
  `emp_no`    int(11) NOT NULL,
  `salary`    int(11) NOT NULL,
  `from_date` date    NOT NULL,
  `to_date`   date    NOT NULL,
  PRIMARY KEY (`emp_no`, `from_date`)
);

Solution:

SELECT
  DISTINCT salary
FROM salaries
WHERE to_date = '9999-01-01'
ORDER BY salary DESC;

SELECT salary
FROM salaries
WHERE to_date = '9999-01-01'
GROUP BY salary
ORDER BY salary DESC;

获取所有部门当前manager的当前薪水情况,给出dept_no, emp_no以及salary,当前表示to_date='9999-01-01'

CREATE TABLE `dept_manager` (
  `dept_no`   char(4) NOT NULL,
  `emp_no`    int(11) NOT NULL,
  `from_date` date    NOT NULL,
  `to_date`   date    NOT NULL,
  PRIMARY KEY (`emp_no`, `dept_no`)
);
CREATE TABLE `salaries` (
  `emp_no`    int(11) NOT NULL,
  `salary`    int(11) NOT NULL,
  `from_date` date    NOT NULL,
  `to_date`   date    NOT NULL,
  PRIMARY KEY (`emp_no`, `from_date`)
);

Solution:

SELECT
  dm.dept_no,
  dm.emp_no,
  s.salary
FROM dept_manager dm
  INNER JOIN salaries s ON
                          dm.emp_no = s.emp_no
                          AND dm.to_date = '9999-01-01'
                          AND s.to_date = '9999-01-01';

获取所有非manager的员工emp_no

CREATE TABLE `dept_manager` (
  `dept_no`   char(4) NOT NULL,
  `emp_no`    int(11) NOT NULL,
  `from_date` date    NOT NULL,
  `to_date`   date    NOT NULL,
  PRIMARY KEY (`emp_no`, `dept_no`)
);
CREATE TABLE `employees` (
  `emp_no`     int(11)     NOT NULL,
  `birth_date` date        NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name`  varchar(16) NOT NULL,
  `gender`     char(1)     NOT NULL,
  `hire_date`  date        NOT NULL,
  PRIMARY KEY (`emp_no`)
);

Solution:

SELECT emp_no
FROM employees
WHERE emp_no NOT IN (SELECT emp_no
                     FROM dept_manager);

SELECT emp_no
FROM (SELECT *
     FROM employees e LEFT JOIN dept_manager dm ON e.emp_no = dm.emp_no)
WHERE dept_no IS NULL;

SELECT employees.emp_no
FROM employees
  LEFT JOIN dept_manager
    ON employees.emp_no = dept_manager.emp_no
WHERE dept_no IS NULL;

查找当前薪水(to_date='9999-01-01')排名第二多的员工编号emp_no、薪水salary、last_name以及first_name,不准使用order by

CREATE TABLE `employees` (
  `emp_no`     int(11)     NOT NULL,
  `birth_date` date        NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name`  varchar(16) NOT NULL,
  `gender`     char(1)     NOT NULL,
  `hire_date`  date        NOT NULL,
  PRIMARY KEY (`emp_no`)
);
CREATE TABLE `salaries` (
  `emp_no`    int(11) NOT NULL,
  `salary`
      int(11) NOT NULL,
  `from_date` date    NOT NULL,
  `to_date`   date    NOT NULL,
  PRIMARY KEY (`emp_no`, `from_date`)
);

Solutioin:

SELECT
  e.emp_no,
  MAX(s.salary) AS salary,
  e.last_name,
  e.first_name
FROM employees e
  INNER JOIN salaries s ON e.emp_no = s.emp_no
WHERE s.to_date = '9999-01-01'
      AND s.salary NOT IN (SELECT MAX(salary)
                           FROM salaries
                           WHERE to_date = '9999-01-01');

查找所有员工的last_name和first_name以及对应的dept_name,也包括暂时没有分配部门的员工

CREATE TABLE `departments` (
  `dept_no`   char(4)     NOT NULL,
  `dept_name` varchar(40) NOT NULL,
  PRIMARY KEY (`dept_no`)
);
CREATE TABLE `dept_emp` (
  `emp_no`    int(11) NOT NULL,
  `dept_no`   char(4) NOT NULL,
  `from_date` date    NOT NULL,
  `to_date`   date    NOT NULL,
  PRIMARY KEY (`emp_no`, `dept_no`)
);
CREATE TABLE `employees` (
  `emp_no`     int(11)     NOT NULL,
  `birth_date` date        NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name`  varchar(16) NOT NULL,
  `gender`     char(1)     NOT NULL,
  `hire_date`  date        NOT NULL,
  PRIMARY KEY (`emp_no`)
);

Solutioin:

SELECT
  em.last_name,
  em.first_name,
  dm.dept_name
FROM (employees em
  LEFT JOIN dept_emp de ON em.emp_no = de.emp_no) LEFT JOIN departments dm ON de.dept_no = dm.dept_no;

查找员工编号emp_no为10001其自入职以来的薪水salary涨幅值growth

CREATE TABLE `salaries` (
  `emp_no`    int(11) NOT NULL,
  `salary`    int(11) NOT NULL,
  `from_date` date    NOT NULL,
  `to_date`   date    NOT NULL,
  PRIMARY KEY (`emp_no`, `from_date`)
);

Solutioin:

SELECT ((SELECT salary
         FROM salaries
         WHERE emp_no = 10001
         ORDER BY salary DESC
         LIMIT 1) -
        (SELECT salary
         FROM salaries
         WHERE emp_no = 10001
         ORDER BY salary ASC
         LIMIT 1)) AS growth;

查找所有员工自入职以来的薪水涨幅情况,给出员工编号emp_no以及其对应的薪水涨幅growth,并按照growth进行升序

CREATE TABLE `employees` (
  `emp_no`     int(11)     NOT NULL,
  `birth_date` date        NOT NULL,
  `first_name` varchar(14) NOT NULL,
  `last_name`  varchar(16) NOT NULL,
  `gender`     char(1)     NOT NULL,
  `hire_date`  date        NOT NULL,
  PRIMARY KEY (`emp_no`)
);
CREATE TABLE `salaries` (
  `emp_no`    int(11) NOT NULL,
  `salary`    int(11) NOT NULL,
  `from_date` date    NOT NULL,
  `to_date`   date    NOT NULL,
  PRIMARY KEY (`emp_no`, `from_date`)
);

Solution:

SELECT
  sCurrent.emp_no,
  (sCurrent.salary - sStart.salary) AS growth
FROM (SELECT
        s.emp_no,
        s.salary
      FROM employees e LEFT JOIN salaries s ON e.emp_no = s.emp_no
      WHERE s.to_date = '9999-01-01') AS sCurrent
  INNER JOIN (SELECT
                s.emp_no,
                s.salary
              FROM employees e LEFT JOIN salaries s ON e.emp_no = s.emp_no
              WHERE s.from_date = e.hire_date) AS sStart
    ON sCurrent.emp_no = sStart.emp_no
ORDER BY growth


SELECT
  sCurrent.emp_no,
  (sCurrent.salary - sStart.salary) AS growth
FROM (SELECT
        s.emp_no,
        s.salary
      FROM employees e, salaries s
      WHERE e.emp_no = s.emp_no AND s.to_date = '9999-01-01') AS sCurrent,
  (SELECT
     s.emp_no,
     s.salary
   FROM employees e, salaries s
   WHERE e.emp_no = s.emp_no AND s.from_date = e.hire_date) AS sStart
WHERE sCurrent.emp_no = sStart.emp_no
ORDER BY growth

统计各个部门对应员工涨幅的次数总和,给出部门编码dept_no、部门名称dept_name以及次数sum

CREATE TABLE `departments` (
  `dept_no`   char(4)     NOT NULL,
  `dept_name` varchar(40) NOT NULL,
  PRIMARY KEY (`dept_no`)
);
CREATE TABLE `dept_emp` (
  `emp_no`    int(11) NOT NULL,
  `dept_no`   char(4) NOT NULL,
  `from_date` date    NOT NULL,
  `to_date`   date    NOT NULL,
  PRIMARY KEY (`emp_no`, `dept_no`)
);
CREATE TABLE `salaries` (
  `emp_no`    int(11) NOT NULL,
  `salary`    int(11) NOT NULL,
  `from_date` date    NOT NULL,
  `to_date`   date    NOT NULL,
  PRIMARY KEY (`emp_no`, `from_date`)
);

Solution:

SELECT
  de.dept_no,
  dp.dept_name,
  COUNT(s.salary) AS sum
FROM (dept_emp AS de INNER JOIN salaries AS s ON de.emp_no = s.emp_no)
  INNER JOIN departments AS dp ON de.dept_no = dp.dept_no
GROUP BY de.dept_no

对所有员工的当前(to_date='9999-01-01')薪水按照salary进行按照1-N的排名,相同salary并列且按照emp_no升序排列

CREATE TABLE `salaries` (
  `emp_no`    int(11) NOT NULL,
  `salary`    int(11) NOT NULL,
  `from_date` date    NOT NULL,
  `to_date`   date    NOT NULL,
  PRIMARY KEY (`emp_no`, `from_date`)
);

Solution:

SELECT
  s1.emp_no,
  s1.salary,
  COUNT(DISTINCT s2.salary) AS rank
FROM salaries AS s1, salaries AS s2
WHERE s1.to_date = '9999-01-01' AND s2.to_date = '9999-01-01' AND s1.salary <= s2.salary
GROUP BY s1.emp_no
ORDER BY s1.salary DESC, s1.emp_no ASC

Android Studio / IntelliJ IDEA 中使用lombok

原文连接

#30

前言

lombok可以通过一个注解自动实现Getter Setter等方法.平时我们可以通过Eclipse或者IntelliJ IDEA快捷键生成Getter Setter方法,当我们需要加入一个新的属性或者修改某属性的变量名或者数据类型时,都需要手动修改Getter Setter方法,这样极为麻烦.有了lombok,使得代码更加简洁,同时节约编码时间。本文适用于IntelliJ IDEAAndroid Studio

添加依赖

我们可以直接下载jar包,并添加到构建路径中,但是推荐你使用构建工具

Maven依赖
<dependencies>
	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.18.2</version>
		<scope>provided</scope>
	</dependency>
</dependencies>

Gradle依赖

compileOnly 'org.projectlombok:lombok:1.18.2'

如果你是用的是其他构建工具,请参考

Usage

@Data 使用

我们可以直接在类名上使用该注解,lombok会自动生成对应的Getter Setter toString等方法

@Data public class DataExample {
    private final String name;
    private int age;
    private double score;
    private String[] tags;
}

@GetterSetter可以设置访问成员变量的权限

@Data public class DataExample {
  @Getter(AccessLevel.NONE)
  private final String name;
  @Getter(AccessLevel.PROTECTED)
  private int age;
  @Setter(AccessLevel.MODULE)
  private double score;
  private String[] tags;
}

@ToString

 import lombok.ToString;

@ToString
public class ToStringExample {
  private static final int STATIC_VAR = 10;
  private String name;
  private Shape shape = new Square(5, 10);
  private String[] tags;
  @ToString.Exclude private int id;

  public String getName() {
    return this.name;
  }

  @ToString(callSuper=true, includeFieldNames=true)
  public static class Square extends Shape {
    private final int width, height;

    public Square(int width, int height) {
      this.width = width;
      this.height = height;
    }
  }
}

更多注解的使用请参考官方文档

安装插件

虽然lombok已经为你生成了繁琐的Getter Setter方法,但是你在你的IDE中还是无法访问lombok编译时自动生成的方法,因为我们需要在IntelliJ IDEAAndroid Studio中安装lombok插件.

安装方式

File -> Settings -> Plugins -> Browse Repositories
image

点击右侧的Install按钮安装,安装完成后需要重启IDE

总结

Lombok是一个很优秀的开源库,通过该开源库你可以实现如Kotlin一样无需手写GetterSetter等方法,极为方便。

Telegram专用代理MTProxy脚本

写一个专门用于搭建Telegram代理MTProxy的脚本

https://github.com/shellhub/shellhub/blob/master/proxy/mt_proxy.sh

支持版本

  • Centos
  • Debian/Ubuntu

如何使用

复制到服务器中自动编译安装

wget -N --no-check-certificate https://raw.githubusercontent.com/shellhub/shellhub/master/proxy/mt_proxy.sh && chmod +x mt_proxy.sh && ./mt_proxy.sh

输入用于客服端连接的端口号,可以直接自动生成

Input server port (defalut: Auto Generated):

输入一个32位16进制的密码用于客服端用来验证服务器,回车自动生成

Input secret (defalut: Auto Generated):

完成安装

***************************************************
* Server : 140.82.22.61
* Port   : 1094
* Secret : 3c6c1efb0244e0285a4c3a28ebc6ce9c
***************************************************

Here is a link to your proxy server:
https://t.me/proxy?server=140.82.22.61&port=1094&secret=3c6c1efb0244e0285a4c3a28ebc6ce9c

And here is a direct link for those who have the Telegram app installed:
tg://proxy?server=140.82.22.61&port=1094&secret=3c6c1efb0244e0285a4c3a28ebc6ce9c
***************************************************

客服端链接到代理服务器

  • 可以手动输入ip地址,端口号,密钥进行链接
  • 可以复制https开头的链接到浏览器打开,浏览器自动打开Telegram配置
  • 可以在app里面直接打开tg:开头的链接

视频教程

IMAGE ALT TEXT HERE

shell/bash脚本技巧

##echo输入带颜色的文字

我们可以使用ANSI转意编码

Black        0;30     Dark Gray     1;30
Red          0;31     Light Red     1;31
Green        0;32     Light Green   1;32
Brown/Orange 0;33     Yellow        1;33
Blue         0;34     Light Blue    1;34
Purple       0;35     Light Purple  1;35
Cyan         0;36     Light Cyan    1;36
Light Gray   0;37     White         1;37

然后使用上面的编码定义我们的颜色

RED='\033[0;31m'
NC='\033[0m' # No Color
printf "I ${RED}love${NC} Stack Overflow\n"

如果你使用echo而不是printf,注意添加-e选项

echo -e "I ${RED}love${NC} Stack Overflow"

需要注意的是echo自带换行功能,所以没有必要在字符串后面加"\n"

初学者角度介绍容器,VMs和Docker

初学者角度介绍容器,VMs和Docker

1_k8n7Jx9UaLRAxum9HMp8nQ

如果你是一个程序员或者技术员,那么您肯定听说过Docker-一种用于在容器中打包,传输,并且运行的有用工具,Docker在过去一段时间到现在得到了很多开发人员和系统管理员的关注,甚至像Google,VMWare,Amazon等大公司也在建立支持他的服务。

我仍然认为了解“容器”是什么以及与虚拟机(VM)的比较的一些基本概念仍然很重要。尽管Internet上有很多关于Docker的出色使用指南,但我找不到很多对初学者友好的概念指南,尤其是关于容器的组成。所以,希望这篇文章能解决这个问题:)

什么是容器和虚拟机

容器和虚拟的目标是相似的,都是将应用和其依赖隔离到一个可以在任何地方运行的独立单元。

此外,容器和VM消除了对物理硬件的需求,从而在能源消耗和成本效率方面都可以更有效地利用计算资源。

容器和VM之间的主要区别在于它们的体系结构方法。让我们仔细看看。

Android开发设置页面

原文连接

#29

我们在项目中经常需要用到设置(Setting),在安卓中主要使用 android.preference.PreferenceFragmentandroid.support.v7.preference.PreferenceFragmentCompat,在Android API 28中PreferenceFragment已经过时,推荐使用PreferenceFragmentCompat

This class was deprecated in API level 28.
Use PreferenceFragmentCompat

screen

添加依赖

implementation group: 'com.android.support', name: 'preference-v7', version: '28.0.0'

创建配置文件

app\src\main\res\xml\preference_setting.xml

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:title="@string/root_title">

    <Preference
        android:key="basic_preference"
        android:title="@string/title_basic_preference"
        android:summary="@string/summary_basic_preference" />

    <Preference
        android:key="stylish_preference"
        android:title="@string/title_stylish_preference"
        android:summary="@string/summary_stylish_preference" />

    <Preference
        android:key="preference_with_icon"
        android:title="Preference with icon"
        android:summary="This preference has an icon"
        android:icon="@android:drawable/ic_menu_camera" />

    <PreferenceCategory
        android:title="@string/inline_preferences">

        <CheckBoxPreference
            android:key="checkbox_preference"
            android:title="@string/title_checkbox_preference"
            android:summary="@string/summary_checkbox_preference" />

        <SwitchPreference
            android:key="switch_preference"
            android:title="Switch preference"
            android:summary="This is a switch" />

        <DropDownPreference
            android:key="dropdown_preference"
            android:title="@string/title_dropdown_preference"
            android:summary="@string/summary_dropdown_preference"
            android:entries="@array/entries_list_preference"
            android:entryValues="@array/entryvalues_list_preference" />

    </PreferenceCategory>

    <PreferenceCategory
        android:title="@string/dialog_based_preferences">

        <EditTextPreference
            android:key="edittext_preference"
            android:title="@string/title_edittext_preference"
            android:summary="@string/summary_edittext_preference"
            android:dialogTitle="@string/dialog_title_edittext_preference" />

        <ListPreference
            android:key="list_preference"
            android:title="@string/title_list_preference"
            android:summary="@string/summary_list_preference"
            android:entries="@array/entries_list_preference"
            android:entryValues="@array/entryvalues_list_preference"
            android:dialogTitle="@string/dialog_title_list_preference" />

        <MultiSelectListPreference
            android:key="multi_select_list_preference"
            android:title="@string/title_multi_list_preference"
            android:summary="@string/summary_multi_list_preference"
            android:entries="@array/entries_list_preference"
            android:entryValues="@array/entryvalues_list_preference"
            android:dialogTitle="@string/dialog_title_multi_list_preference" />

    </PreferenceCategory>

    <PreferenceCategory
        android:title="@string/launch_preferences">

        <!-- This PreferenceScreen tag serves as a screen break (similar to page break
             in word processing). Like for other preference types, we assign a key
             here so it is able to save and restore its instance state. -->
        <PreferenceScreen
            android:key="screen_preference"
            android:title="@string/title_screen_preference"
            android:summary="@string/summary_screen_preference">

            <!-- You can place more preferences here that will be shown on the next screen. -->

            <CheckBoxPreference
                android:key="next_screen_checkbox_preference"
                android:title="@string/title_next_screen_toggle_preference"
                android:summary="@string/summary_next_screen_toggle_preference" />

        </PreferenceScreen>

        <PreferenceScreen
            android:title="@string/title_intent_preference"
            android:summary="@string/summary_intent_preference">

            <intent android:action="android.intent.action.VIEW"
                android:data="http://www.android.com" />

        </PreferenceScreen>

    </PreferenceCategory>

    <PreferenceCategory
        android:title="@string/preference_attributes">

        <CheckBoxPreference
            android:key="parent_checkbox_preference"
            android:title="@string/title_parent_preference"
            android:summary="@string/summary_parent_preference" />

        <!-- The visual style of a child is defined by this styled theme attribute. -->
        <CheckBoxPreference
            android:key="child_checkbox_preference"
            android:dependency="parent_checkbox_preference"
            android:layout="?android:attr/preferenceLayoutChild"
            android:title="@string/title_child_preference"
            android:summary="@string/summary_child_preference" />

    </PreferenceCategory>

</PreferenceScreen>

创建SettingFragment

使用addPreferencesFromResource方法添加上一部添加的方法

public class SettingsFragment extends PreferenceFragmentCompat{
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        addPreferencesFromResource(R.xml.preference_setting.xml);
    }
}

保存与检索

PreferenceFragmentCompat自动保存用户选择的配置信息,然后我们可以通过getDefaultSharedPreferences(android.content.Context)读取键值对配置信息

参考连接

https://developer.android.com/reference/android/support/v7/preference/PreferenceFragmentCompat

https://developer.android.com/reference/android/preference/PreferenceFragment

经典递归程序案列汇总(C语言版本)

经典递归程序案列汇总(C语言版本)

  • 逆序打印字符串(方法一)

如: 1234abc->cba4321

#include <stdio.h>
#include <string.h>
/**
 * 逆序打印字符串
 * @param str 字符串
 * @param len 长度
 */
void reverse(char *str, int len) {
    if (len == 0) {
        return; //结束递归调用
    } else {
        printf("%c", str[len - 1]); //打印最后一个字符
        reverse(str, len - 1);
    }
}
int main() {
    char str[] = "1234abc";
    reverse(str, strlen(str));

    return 0;
}

输出: cba4321

  • 逆序打印字符串(方法二)

如: 1234abc->cba4321

#include <stdio.h>

/**
 * 逆序打印一个字符串
 * @param str
 */
void reverse(char *str) {
    if (*str == '\0') {
        return;
    } else {
        reverse(++str);
        printf("%c", *(--str));
    }
}

int main() {
    char str[] = "1234abc";
    reverse(str);
    return 0;
}

输出: cba4321

  • 10进制转换为2进制

如十进制数12转换成二进制数1100

#include <stdio.h>

/**
 * 十进制数转换乘二进制
 * @param n 对应的十进制数
 */
void decimal2Binary(int n){
    if (n == 0) {
        return;
    } else {
        decimal2Binary(n / 2);
        printf("%d", n % 2);
    }
}
int main() {

    int n = 12;
    printf("十进制数%d转换乘二进制数为:", n);
    decimal2Binary(n);
    return 0;
}

输出: 十进制数12转换乘二进制数为:1100

  • 递归实现n^k(n的k次幂)

如当n=2,k=3, 结算结果为:2*2*2=8

#include <stdio.h>

/**
 * 递归实现n的k次幂
 * @param n 底数
 * @param k 指数(幂)
 * @return 返回n的k次幂
 */
int pow(int n, int k){
    if (k == 0) {
        return 1;
    } else {
        return n * pow(n, k - 1);
    }
}
int main() {
    int n = 2; //底数
    int k = 10; //指数
    printf("%d的%d次幂=%d\n", n, k, pow(n, k));
    return 0;
}

输出: 2的10次幂=1024

  • 递归逆序打印一个数字的各位数字

1234 -> 4321

#include <stdio.h>

/**
 * 逆序输出一个正整数的各个数字
 * @param n
 */
void reverse(int n){
    if (n == 0) {
        return;
    } else {
        printf("%d", n % 10);
        reverse(n / 10);
    }
}
int main() {
    int n = 123456;
    printf("%d逆序输出为:", n);
    reverse(n);
    return 0;
}

输出: 123456逆序输出为:654321

  • 递归正序打印一个数字的各位数字

1234 -> 1234

#include <stdio.h>

/**
 * 递归正序打印正整数
 * @param n 正整数
 */
void travel(int n){
    if (n == 0) {
        return;
    } else {
        travel(n / 10);
        printf("%d", n % 10);
    }
}
int main() {
    int n = 123456;
    printf("%d正序输出为:", n);
    travel(n);
    return 0;
}

输出: 123456正序输出为:123456

  • 递归实现n!=(1*2*3*4*...n)

5!=120

  • 递归实现n的阶乘(n!)

如: 5!=5*4*3*2*1=120

#include <stdio.h>
/**
 * 递归求解n!
 * @param n  正整数
 * @return 返回n的阶乘
 */
int factorial(int n) {
    if (n == 0 || n == 1) {
        return 1;
    } else {
        return n * factorial(n - 1);
    }
}
int main() {
    int n = 5;
    printf("%d!=%d\n", n, factorial(n));
    return 0;
}

输出: 5!=120

  • 递归打印斐波那契前20项

如: 1 1 2 3 5 8...

#include <stdio.h>

/**
 * 求斐波那契数列第n项
 * @param n 第n项
 * @return 返回第项的结果
 */
int fib(int n){
    if (n == 1 || n == 2) { //第一项和第二项
        return 1;
    } else {
        return fib(n - 1) + fib(n - 2);//后一项等于前两项之和
    }
}
int main() {

    //打印前20项,每5个换一行
    for (int i = 1; i <= 20; i++) {
        printf("%d\t", fib(i));
        if (i % 5 == 0) {
            printf("\n");
        }
    }
    return 0;
}

输出:

1       1       2       3       5
8       13      21      34      55
89      144     233     377     610
987     1597    2584    4181    6765
  • 递归判断一个字符串是否是回文字符串

如: 123321是回文字符串,123abcaba321不是回文字符串

#include <stdio.h>
#include <string.h>

/**
 * 递归判断一个字符串是否是回文字符串
 * @param str 需要判断是否是回文字符串
 * @return 如果是回文字符串返回1,否则返回0
 */
int check(char *str, int start, int end) {
    printf("str[%d]=%c <-> str[%d]=%c\n", start, str[start], end, str[end]);
    if (start >= end) {
        return 1;
    } else if (str[start] != str[end]) {
        return 0;
    } else {
        return check(str, start + 1, end - 1);
    }
}

int main() {
    char str[] = "123321";
    printf("%s%s\n", str, check(str, 0, strlen(str) - 1) ? "是回文字符串" : "不是回文字符串");
    return 0;
}

输出: 123321是回文字符串

  • 递归求解汉诺塔问题

汉诺塔:汉诺塔(又称河内塔)问题是源于印度一个古老传说的益智玩具。大梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘。

#include <stdio.h>

/**
 * 从A柱子通过中间柱子B移动n个盘子到C柱子上
 * @param A 第一根柱子
 * @param B 第二根柱子
 * @param C 第三根柱子
 * @param n 盘子数量
 */

int count = 0;

void move(char from, char to) {
    printf("第%d次移动: move from %c to %c\n", ++count, from, to);
}

void hanoi(char A, char B, char C, int n) {
    if (n == 1) {
        move(A, C);
        return;
    } else {
        hanoi(A, C, B, n - 1);
        move(A, C);
        hanoi(B, A, C, n - 1);
    }
}

int main() {

    printf("请输入需要移动盘子🥘的数量:");
    int n;
    scanf("%d", &n);
    hanoi('A', 'B', 'C', n);
    return 0;
}

输出:

请输入需要移动盘子🥘的数量:3
第1次移动: move from A to C
第2次移动: move from A to B
第3次移动: move from C to B
第4次移动: move from A to C
第5次移动: move from B to A
第6次移动: move from B to C
第7次移动: move from A to C
  • 递归求解数组中得最大值(分治法)

{1, -1, 3, -8, 10, 9}返回最大值10

#include<stdio.h>
/**
 * 递归求数组中的最大值
 * @param arr 对应数组
 * @param low 左下标
 * @param high 右下标
 * @return 返回数组中的最大值
 */
int max(int arr[], int low, int high){
	if(low == high){
		return arr[low];
	}
	int mid = (low + high) / 2;
	int leftMax = max(arr, low, mid);
	int rightMax = max(arr, mid + 1, high);
	return leftMax >= rightMax ? leftMax : rightMax;
}
int main(){
	int arr[] = {1, -1, 3, -8, 10, 9};

	int low = 0;
	int high = sizeof(arr) / sizeof(arr[0]) - 1;

	printf("max = %d\n", max(arr, low, high));
	return 0;
}

输出:

max = 10

Telegram MTproxy代理搭建完全指南

视频演示传送门
最近更新了最新版本的ios Telegram后,发现无法链接到服务器,一直处于Connectting状态,即使是开启了ss的全局模式也是没有任何作用,强制让Telegram去监听socks5的端口号,试了108010861087等一些列端口号都无果,最终的解决方案是通过Telegram MTProxy得以解决

编译源码

通过SSH链接到自己的服务器

更新软件包

yum update -y # For Debian/Ubuntu:
apt update -y # For On CentOS/RHEL:

安装对应的依赖包
Debian/Ubuntu:

apt install git curl build-essential libssl-dev zlib1g-dev

CentOS/RHEL

yum install openssl-devel zlib-devel
yum groupinstall "Development Tools"

获取MTProxy源代码

git clone https://github.com/TelegramMessenger/MTProxy
cd MTProxy # to source directory

编译源代码生成可以执行文件,这里使用make进行编译

make && cd objs/bin

如果编译失败,执行make clean 清理以下重试

运行

获取用于链接Telegram服务器的secret

curl -s https://core.telegram.org/getProxySecret -o proxy-secret

获取telegram配置文件

curl -s https://core.telegram.org/getProxyConfig -o proxy-multi.conf

生成一个32位16进制secret用于客服端链接

head -c 16 /dev/urandom | xxd -ps

运行mtproto-proxy

chmod +x mtproto-proxy
./mtproto-proxy -u nobody -p 8888 -H 443 -S <secret> --aes-pwd proxy-secret proxy-multi.conf -M 1

注意⚠️
请将-p 8888 -H 443 -S <secret>替换为自己的,分别为本地端口号,用于链接服务器的端口,32位16进制secret

Telegram客服端链接代理

IOS端设置如下
Setting > Data Storage > Use Proxy > + Add Proxy > MTProto
分别输入
Server:服务器ip地址
Port:端口号
Secret:32位16进制端口号

  • A Stack is a collection of elements, with two principle operations: push, which adds to the collection, and pop, which removes the most recently added element

  • Last in, first out data structure (LIFO): the most recently added object is the first to be removed

  • Time Complexity:

    • Access: O(n)
    • Search: O(n)
    • Insert: O(1)
    • Remove: O(1)
#include <stdio.h>
#include <stdbool.h>

#define MAX_SIZE 8
int stack[MAX_SIZE];
int top = -1;

bool isEmpty();

bool isFull();

int peek();

bool push(int data);

int pop();

int length();

void printStack();

int main() {
    pop(); //stack is empty now
    push(1);
    push(1);
    push(1);
    printf("stack length is = %d\n", length()); // should be is 3
    push(2);
    push(3);
    push(4);
    push(5);
    push(6);
    printStack();
    push(7); //stack is full
    printStack();
    return 0;
}


bool isEmpty() {
    return top == -1;
}

bool isFull() {
    return top == MAX_SIZE - 1;
}

int peek() {
    return stack[top];
}

bool push(int data) {
    if (isFull()) {
        perror("stack is full\n");
        return false;
    }
    stack[++top] = data;
    return true;
}

int pop() {
    if (isEmpty()) {
        perror("stack is empty\n");
        return -1;
    }
    return stack[top--];
}

int length(){
    return top + 1;
}

void printStack(){
    for (int i = 0; i < top; ++i) {
        printf("%d\t", stack[i]);
    }
    printf("\n");
}

Shadowsocks多用户管理 - 支持支付宝

Shadowsocks多用户管理 - 支持支付宝

脚本已经开源,欢迎 star

https://github.com/shellhub/shellhub

前期准备工作

  • 拥有一台国外服务器,比如我常用的vultr

  • 一个gmail邮箱,用于发送邮件给注册用户激活帐号

  • 一个开通面付功能的支付宝帐号(注:普通用户无法开通)

    • 付费开通支付宝面付功能(QQ:2279485992) 非诚勿扰^_^
  • 使用ssh或者putty链接服务器

教程开始

链接上购买的服务器后,输入以下命令开始运行安装

安装

wget -N --no-check-certificate https://raw.githubusercontent.com/shellhub/shellhub/master/ssmgr/ssmgr.sh && chmod +x ssmgr.sh && ./ssmgr.sh

接下来选择是否安装webgui管理平台,选择y安装,n只安装节点

"Install webgui(website) y/n?: y"

输入webgui管理密码

Input webgui manage password:123456

输入你的谷歌邮箱地址(gmail)

Input your email address:[email protected]

输入你的谷歌邮箱密码

Input your email password:your-password

配置支付宝部分

这部分是需要拥有企业支付宝帐号

输入支付宝面付appid

Input alipay appid:

输入支付宝私钥

Input alipay_private_key:

输入支付宝公钥匙

Input alipay_public_key:

等待安装完成...

配置管理员

第一个注册的用户即为管理员

注意:如果无法发送验证码,原因是谷歌帐号的安全性问题,点击下面链接可以解除这个问题

https://productforums.google.com/forum/#!topic/gmail/9KCgzXY4G_c

效果图

screenshot from 2018-11-25 21-02-13

screenshot from 2018-11-25 21-05-30

感谢

https://github.com/shadowsocks/shadowsocks
https://github.com/shadowsocks/shadowsocks-manager

双向链表

  • Doubly-linked list: linked list in which each node has two pointers, p and n, such that p points to the previous node and n points to the next node; the last node's n pointer points to null
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>

typedef struct node {
    char *key;
    int value;
    struct node *prev;
    struct node *next;
} node;

node *head = NULL;
node *last = NULL;

int length() {
    int count = 0;
    for (node *ptr = head; ptr != NULL; ptr = ptr->next) {
        count++;
    }
    return count;
}

bool isEmpty() {
    return head == NULL;
}

void displayForward() {
    node *ptr = head;
    while (ptr != NULL) {
        printf("key=%s->value=%d\n", ptr->key, ptr->value);
        ptr = ptr->next;
    }
}

void displayBackward() {
    node *ptr = last;
    while (ptr != NULL) {
        printf("key=%s->value=%d\n", ptr->key, ptr->value);
        ptr = ptr->prev;
    }
}

void insertFirst(char *key, int value) {
    node *link = malloc(sizeof(node));
    link->key = key;
    link->value = value;

    if (isEmpty()) {
        link->next = link->prev = NULL;
        head = last = link;
    } else {
        head->prev = link;
        link->next = head;
        link->prev = NULL;

        head = link;
    }
}

void insertEnd(char *key, int value) {
    node *link = malloc(sizeof(node));
    link->key = key;
    link->value = value;

    if (isEmpty()) {
        link->next = link->prev = NULL;
        head = last = link;
    } else {
        last->next = link;
        link->next = NULL;
        link->prev = last;

        last = link;
    }
}

void insertAt(int position, char *key, int value) {
    if (position <= 0 || position >= length() + 1) {
        perror("insert position is invalid\n");
        return;
    }

    if (position == 1) {
        insertFirst(key, value);
        return;
    }
    if (position == length() + 1) {
        insertEnd(key, value);
        return;
    }

    node *link = malloc(sizeof(node));
    link->key = key;
    link->value = value;

    int count = 0;
    node *ptr = head;
    while (ptr != NULL && ++count < position) {
        ptr = ptr->next;
    }

    printf("%s:%d\n", ptr->key, ptr->value);

    if (ptr != NULL) {
        ptr->prev->next = link;
        link->prev = ptr->prev;
        link->next = ptr;
    }
}


int main() {

    printf("current double link list is %d\n", length());
    insertFirst("key1", 1);
    insertFirst("key2", 2);
    printf("display forward\n");
    displayForward();
    printf("display backward\n");
    displayBackward();

    insertEnd("key3", 3);
    insertEnd("key4", 4);
    printf("display forward\n");
    displayForward();
    printf("display backward\n");
    displayBackward();


    //todo has bug
    insertAt(2, "new", 5);
    printf("-----------------\n");
    printf("display forward\n");
    displayForward();
    printf("display backward\n");
    displayBackward();


    printf("length is %d\n", length());

    return 0;
}

Windows平台如何使用科学上网

Windows系统如何科学上网

  • 前往官方主页下载Shadowsocks最新版本,展开Assets下载Shadowsocks-4.1.8.0.zip安装包

  • 下载后,解压后双击解压文件夹的Shadowsocks.exe

    注意:Windows 7用户需下载.net framework才可以打开,否则有可能失败

    打开后任务栏中会出现一个小飞机图标(一般隐藏在任务栏中)

  • 开始配置,对那个配置如下
    ss配置

Kotlin学习笔记

Kotlin学习笔记

Kotlin同样支持包的定义及导入,包的定义和java一样只能放第一行

package my.demo

import java.util.*

// ...

Kontlin中可以不适用分号作为结束,如果写上分号,IDEA会提示你没有必要加分号,但是不会报错

fun main(args: Array<String>) {
    println("hello world")
    print("hello kotlin");
}

Kotlin定义函数,kotlin中的变量类型位于变量后面,使用:分隔开,使用fun关键词定义方法,返回值类型位于后面

fun main(args: Array<String>) {
    print(sum(3, 4))
}

fun sum(a: Int, b: Int): Int {
    return a + b
}

输出结果

7

可以看到,我们使用的是Int而不是int,如果换成int则报错

我们还可以这样定义函数(使用表达式)

fun sum(a: Int, b: Int) = a + b

可以像shell脚本一样,使用$取出变量的值

fun sum(a: Int, b: Int): Int {
    println("$a add $b equals ${a+b}")
    return a + b
}

kotlin中变量的定义,可以像javascript那样使用var关键词定义变量

fun main() {
    val a: Int = 1  // immediate assignment
    val b = 2   // `Int` type is inferred
    val c: Int  // Type required when no initializer is provided
    c = 3       // deferred assignment
    println("a = $a, b = $b, c = $c")
}

局部变量的定义

fun main(args: Array<String>) {
    var x = 5 // `Int` type is inferred
    x += 1
    println("x = $x")
}

定义全局变量

val PI = 3.14
var x = 0

fun incrementX() {
    x += 1
}

fun main(args: Array<String>) {
    println("x = $x; PI = $PI")
    incrementX()
    println("incrementX()")
    println("x = $x; PI = $PI")
}

kotlin中的注释和java,js一样,支持单行注释和多行注释

// This is an end-of-line comment

/* This is a block comment
   on multiple lines. */

值得说一下,kotlin更加灵活,支持注释的嵌套

fun incrementX() {
    /**first comment
     * /**
     * second comment
     * */
     */
    x += 1
}

kotlin使用String的模板函数

fun main(args: Array<String>) {
    var a = 1
    // simple name in template:
    val s1 = "a is $a"

    a = 2
    // arbitrary expression in template:
    val s2 = "${s1.replace("is", "was")}, but now is $a"
    println(s2)
}

kotlin中的条件判断语句和java差不多,就是返回值的类型放置不一致

fun maxOf(a: Int, b: Int): Int {
    if (a > b) {
        return a
    } else {
        return b
    }
}
fun main(args :Array<String>) {
    println("max of 0 and 42 is ${maxOf(0, 42)}")
}

还可以使用表达式的条件判断语句,是代码更加简洁

fun maxOf(a: Int, b: Int) = if (a > b) a else b

fun main(args: Array<String>) {
    println("max of 0 and 42 is ${maxOf(0, 42)}")
}

检查控制(null)

fun parseInt(str: String): Int? {
    return str.toIntOrNull()
}

fun printProduct(arg1: String, arg2: String) {
    val x = parseInt(arg1)
    val y = parseInt(arg2)

    // Using `x * y` yields error because they may hold nulls.
    if (x != null && y != null) {
        // x and y are automatically cast to non-nullable after null check
        println(x * y)
    }
    else {
        println("either '$arg1' or '$arg2' is not a number")
    }
}


fun main(args :Array<String>) {
    printProduct("6", "7")
    printProduct("a", "7")
    printProduct("a", "b")
}

学习Python: 从零到大师

学习Python: 从零到大师

3994917-f60bdfe3afcba051

首先什么是Python?根据他的创造者-Guido van Rossum
Python是一种

Python是一种高级语言,它核心的设计哲学是代码的可读性以及它的语法能让程序员使用更少的代码表达概念

事实上对于我来说,第一个学习Python的原因是Python是一门优美的编程语言,使得编码更加简单,能更好的表达我的**
另一个原因我可以在其他很多方面使用Python,如数据科学,Web开发,机器学习等方面都能更好的发挥它的作用,Quora,Pinterest和Spotify都使用Python进行后端Web开发,以及国内的豆瓣平台,
全世界最大的YouTube平台。我们一起来了解一下这门编程语言吧。。。

欢迎订阅我的→YouTube

基础

1.变量

您可以将变量视为存储值的一个单词。就那么简单。
在Python中,定义变量并给变量赋值特别简单,想象一下你要把数值1存储到变量one中,如下

one = 1

很简单对吧,你刚刚已经把1赋值给了变量one

two = 2
some_number = 10000

并且您可以将任何其他值分配给您想要的任何其他变量。如上所示,变量two存储整数2some_number存储10,000
除了整数,我们还可以使用布尔值(True / False),字符串,float和许多其他数据类型。

# booleans
true_boolean = True
false_boolean = False

# string
my_name = "shellhub"

# float
book_price = 15.80

2.控制流程:条件语句

if使用表达式来评估语句是True还是False。如果为True,则执行if语句中的内容。例如:

if True:
  print("Hello Python If")

if 2 > 1:
  print("2 is greater than 1")

2大于1,因此执行print语句。

如果if表达式为false,则将执行else语句。

if 1 > 2:
  print("1 is greater than 2")
else:
  print("1 is not greater than 2")

1不大于2,因此将执行else语句中的代码。

您还可以使用elif语句:

if 1 > 2:
  print("1 is greater than 2")
elif 2 > 1:
  print("1 is not greater than 2")
else:
  print("1 is equal to 2")

3.循环/迭代器

在Python中,我们可以以不同的形式进行迭代。我将谈论两个:while和for。
循环时:当语句为True时,块内的代码将被执行。因此,此代码将打印从1到10的数字。

num = 1

while num <= 10:
    print(num)
    num += 1

while循环需要一个“循环条件”。如果它保持为True,它将继续迭代。在此示例中,当num为11时,循环条件等于False
另一个基本的代码来更好地理解while循环:

loop_condition = True

while loop_condition:
    print("Loop Condition keeps: %s" %(loop_condition))
    loop_condition = False

循环条件为True,因此它会继续迭代 - 直到我们将其设置为False
对于for循环:将变量num应用于块,for语句将为您迭代它。此代码将打印与while循环相同:从1到10。

for i in range(1, 11):
  print(i)

看到了么?这很简单。范围从1开始直到第11个元素(10是第10个元素)。

List: Collection(集合) | Array(数值) | Data Structure(数据结构)

想象一下,您希望将整数1存储在变量中。但也许你现在要存储2.而3,4,5 ......
我是否有另一种方法可以存储我想要的所有整数,但不能存储数百万个变量?你猜对了 - 确实存在另一种存储它们的方法。

List是一个集合,可用于存储值列表(如您想要的这些整数)。所以让我们用它:

my_integers = [1, 2, 3, 4, 5]

这很简单。我们创建了一个数组并将其存储在my_integer上。
但也许你在问:“我如何从这个数组my_integer中得到一个值?”
好问题。 List有一个名为index的概念。第一个元素获得索引0(零)。第二个得到1,依此类推。你应该明白了吧。

为了更清楚,我们可以用它的索引表示数组和每个元素。如下图:
1_remk6ngghlii20vpd6unea

使用Python语法,它也很容易理解:

my_integers = [5, 7, 1, 3, 4]
print(my_integers[0]) # 5
print(my_integers[1]) # 7
print(my_integers[4]) # 4

想象一下,你不想存储整数。您只想存储字符串,例如亲戚姓名列表。我的亲戚看起来像这样

relatives_names = [
  "Toshiaki",
  "Juliana",
  "Yuji",
  "Bruno",
  "Kaio"
]
print(relatives_names[4]) # Kaio

他的结果和integer一样,完美!!!

我们刚刚了解了Lists索引的工作原理。但我仍然需要向您展示如何向List数据结构添加元素(列表中的元素)。

向List添加新值的最常用方法是append。让我们看看它是如何工作的:

bookshelf = []
bookshelf.append("The Effective Engineer")
bookshelf.append("The 4 Hour Work Week")
print(bookshelf[0]) # The Effective Engineer
print(bookshelf[1]) # The 4 Hour Work Week

append是非常简单的。您只需要应用元素(例如“The Effective Engineer”)作为append参数。

嗯,关于List我们先了解到这儿吧。我们来谈谈另一种数据结构。

开始你的shell脚本编程

I love bash

对于我自己来说,学习新框架或技术的最佳方式是同时获得实践经验,在本文中,你将自己通过编写代码来学习shell脚本的基础知识!本文包含语法,shell脚本的基础知识到中级shell编程,通过这篇文章你可以学习shell的相关知识,并且通过shell来实现Unix/Linux之间的接口

1_qo-oir60xdhic3wjqpu64g

介绍

您可能已经多次遇到过“脚本”这个词,但脚本的的含义是什么意思呢?简单的来说,脚本是包含一系列要执行的命令。这些命令由解释器执行。一切你可以在命令行中输入的命令,你都可以把它放到脚本中。而且,脚本非常适合自动化任务。如果你发现自己频繁重复一些命令,你可以创建一个脚本来实现它!

welcome

The Linux philosophy is ‘Laugh in the face of danger’. Oops. Wrong One. ‘Do it yourself’. Yes, that’s it.
 — Linus Torvalds

我们的第一个脚本

script.sh

#!/bin/bash
echo "My First Script!"

运行脚本

$ chmod 755 script.sh # chmod +x script.sh
$ ./script.sh

1__0bdtxxmb9dowmvsztf6za

好流弊😯!你刚刚编写了你的第一个bash脚本。
我知道你不理解这个脚本,特别对于脚本中的第一行。不要担心我将在本文中详细介绍shell脚本,在进入任何主题之前,我总是建议在脑海中形成路线图或适当的内容索引,并明确我们将要学习的内容。
因此,以下是我们将在文章中讨论的一些要点。

  • 脚本解释器
  • 变量
  • 用户输入
  • 测试
  • 条件判断
  • 循环语句
  • 脚本参数传递
  • 退出状态码
  • 逻辑操作符
  • 函数
  • 通配符
  • 调试

所以,这将是我们讨论的顺序,在本文的最后,我相信你会有足够的信心编写自己的shell脚本:)
Happy Learning Guys

1_5w2fofcvl5fdk7oydjgihg

脚本解释器

你可以从上面脚本的第一行看到 #!/bin/bash 这行指定了你的程序将使用那个解释器,基本上是将路径引用到解释器。Linux/Unix中有很多解释器,其中一些是:bash,zsh,sh,csh和ksh等。

这里推荐一个玩命令行必须知道的一个开源项目oh-my-zsh

0_gp--jderqene-ood

All the best people in life seem to like LINUX. — Steve Wozniak

查看你的系统中有那些脚本解释器

cat /etc/shells

bash: #!/bin/bash
zsh: #!/bin/zsh
ksh: #!/bin/ksh
csh: #!/bin/csh
and so on…

注意⚠️
如果脚本不包含解释器,则使用你的默认shell执行命令,因此代码可能正常运行,虽然是这样,但是不推荐这样做,使用echo $SHELL可以知道你当前使用的解释器

注释
注释以#开始,#后面的内容会被解释器忽略,但是#!另当别论

变量
变量指向内存中的一块区域,变量有对应的变量名和值,可以存储一些可以在将来更改的数据,shell中定义变量不需要指定变量的类型

VARIABLE_NAME="Value"

当命名一个变量是你必须记得以下几点

  • 变量名是区分大小写的
  • 为了方便,变量名最好大写
  • 要使用变量,必须在变量前面加$符号

例子🌰

#!/bin/bash
MY_NAME="shellhub"
echo "Hello, I am $MY_NAME"

OR

#!/bin/bash
MY_NAME="shellhub"
echo "Hello, I am ${MY_NAME}"

提示: 可以把命令执行后的输入结果赋值给一个变量

LIST=$(ls)
SERVER_NAME=$(hostname)

0_as7e7vxyi4ruobdb

合法的变量名

THIS3VARIABLE=”ABC”
THIS_IS_VARIABLE=”ABC”
thisIsVariable=”ABC”

不合法的变量名

4Number=”NUM”
This-Is-Var=”VAR”
# No special character apart from underscore is allowed!

用户输入

read 命令接收键盘的输入,标准输入(Standard Input)

read -p "PROMPT MESSAGE" VARIABLE

其中PROMPT MESSAGE为提示用户的信息,变量VARIABLE可以保存用户的输入,可以在程序中使用该变量

#!/bin/bash
read -p "Please Enter You Name: " NAME
echo "Your Name Is: $NAME"

测试

测试主要用于条件判断。[ condition-to-test-for ] ,如[ -e /etc/passwd ],注意的是[]前后必须有空格,如[-e /etc/passwd]是错误的写法

  1. 文件测试操作
-d FILE_NAM  # True if FILE_NAM is a directory
-e FILE_NAM  # True if FILE_NAM exists
-f FILE_NAM  # True if FILE_NAM exists and is a regular file
-r FILE_NAM  # True if FILE_NAM is readable
-s FILE_NAM  # True if FILE_NAM exists and is not empty
-w FILE_NAM  # True if FILE_NAM has write permission
-x FILE_NAM  # True if FILE_NAM is executable
  1. 字符串测试操作
-z STRING  # True if STRING is empty
-n STRING  # True if STRING is not empty
STRING1 = STRIN2 # True if strings are equal
STRING1 != STRIN2 # True if strings are not equal
  1. 算术测试操作
var1 -eq var2  # True if var1 is equal to var2
var1 -ne var2  # True if var1 not equal to var2
var1 -lt var2  # True if var1 is less than var2
var1 -le var2  # True if var1 is less than or equal to var2
var1 -gt var2  # True if var1 is greater than var2
var1 -ge var2  # True if var1 is greater than or equal to var2

条件判断

和其他编程语言一样,shell脚本也能基于条件进行判断,我们可以使用if-elseif-elif-else

0_lk05kvg-mx5did9l

Avoid the Gates of Hell. Use Linux!

  1. if 语句
if [ condition-is-true ]
then
  command 1
  command 2
    ...
    ...
  command N
fi

if-else

if [ condition-is-true ]
then
  command 1
elif [ condition-is-true ]
then
  command 2
elif [ condition-is-true ]
then
  command 3
else
  command 4
fi
  1. case语句
    case可以实现和if一样的功能,但是当条件判断很多的时候,使用if不太方便,比如使用if进行值的比较
case "$VAR" in
  pattern_1)
    # commands when $VAR matches pattern 1
    ;;
  pattern_2)
    # commands when $VAR matches pattern 2
    ;;
  *)
    # This will run if $VAR doesnt match any of the given patterns
    ;;
esac

例子🌰

#!/bin/bash
read -p "Enter the answer in Y/N: " ANSWER
case "$ANSWER" in
  [yY] | [yY][eE][sS])
    echo "The Answer is Yes :)"
    ;;
  [nN] | [nN][oO])
    echo "The Answer is No :("
    ;;
  *)
    echo "Invalid Answer :/"
    ;;
esac

迭代语句 - 循环

可以通过循环执行同一个代码块很多次

  1. for循环
for VARIABLE_NAME in ITEM_1 ITEM_N
do
  command 1
  command 2
    ...
    ...
  command N
done

Example

#!/bin/bash
COLORS="red green blue"
for COLOR in $COLORS
do
  echo "The Color is: ${COLOR}"
done

Another Example

for (( VAR=1;VAR<N;VAR++ ))
do
  command 1
  command 2
    ...
    ...
  command N
done

在当前所有txt文件前面追加new实现重命名

#!/bin/bash
FILES=$(ls *txt)
NEW="new"
for FILE in $FILES
do
  echo "Renaming $FILE to new-$FILE"
  mv $FILE $NEW-$FILE
done
  1. while 循环
    当所给的条件为true时,循环执行while里面的代码块
while [ CONNDITION_IS_TRUE ]
do
  # Commands will change he entry condition
  command 1
  command 2
    ...
    ...
  command N
done

判断条件可以是任意的测试或者命令,如果测试或命令返回0,则表示条件成立,如果为非0则退出循环,如果一开始条件就不成立,则循环永远不会执行。
如果你不知道退出状态码是什么请不要担心,我后面会告诉你 :)

0_rs8u95tcmwukmaqm

例子 一行一行读取文件内容

#!/bin/bash
LINE=1
while read CURRENT_LINE
do
  echo "${LINE}: $CURRENT_LINE"
  ((LINE++))
done < /etc/passwd
# This script loops through the file /etc/passwd line by line

注意⚠️
continue 用于结束本次循环
break 用于结束整个循环

参数传递

当我们运行脚本的时候,可以传递参数供脚本内部使用$ ./script.sh param1 param2 param3 param4
这些参数将被存储在特殊的变量中

$0 -- "script.sh"
$1 -- "param1"
$2 -- "param2"
$3 -- "param3"
$4 -- "param4"
$@ -- array of all positional parameters

这些变量可以在脚本中的任何地方使用,就像其他全局变量一样

退出状态码

任何一个命令执行完成后都会产生一个退出状态码,范围0-255,状态码可以用来检查错误

  • 0 表示正确执行并正常退出
  • 非0表示执行过程中出错,没有正常退出

上一条命令执行后的退出状态码被保存在变量$?

例子 使用ping检查主机和服务器之间是否可以抵达

#!/bin/bash
HOST="google.com"
ping -c 1 $HOST     # -c is used for count, it will send the request, number of times mentioned
RETURN_CODE=$?
if [ "$RETURN_CODE" -eq "0" ]
then
  echo "$HOST reachable"
else
  echo "$HOST unreachable"
fi

自定义退出状态码
默认的状态码是上一条命令执行的结果,我们可以通过exit来自定义状态码

exit 0
exit 1
exit 2
  ...
  ...
exit 255

逻辑操作符

shell脚本支持逻辑与和逻辑或
逻辑与 &&
逻辑或 ||

Example
mkdir tempDir && cd tempDir && mkdir subTempDir
这个例子中,如果创建tempDir成功,执行后面的cd,继续创建subTempDir

函数

可以把一些列的命令或语句定义在一个函数内,从程序的其他地方调用

0_a9vgsszcprsdt9hx

注意⚠️

  • 函数包含了一些列你要重复调用的指令(函数必须先定义后调用)
  • 把函数定义在程序开始或主程序之前是一个最佳实践

语法

function function_name() {
    command 1
    command 2
    command 3
      ...
      ...
    command N
}

调用函数 简单的给出函数名字

#!/bin/bash
function myFunc () {
    echo "Shell Scripting Is Fun!"
}
myFunc # call

函数参数传递

和脚本一样,也可以给函数传递参数完成特殊的任务,第一个参数存储在变量$1中,第二个参数存储在变量$2中...,$@存储所有的参数,参数之间使用空格分割 myFunc param1 param2 param3 ...

变量的作用范围

全局变量: 默认情况下,shell中的变量都定义为全局变量,你可以从脚本中的任何位置使用变量,但是变量在使用之前必须先定义
本地变量: 本地变量只能在方法内部访问,可以通过local关键词定义一个本地变量,定义一个本地变量的最佳实践是在函数内部

通配符

使用通配符可以完成特定的匹配
一些常用的通配符
* 可以通配一个或多个任意字符

*.txt
hello.*
great*.md

?匹配一个字符

?.md
Hello?

[]匹配括号内部的任意一个字符

He[loym], [AIEOU]

[!]不匹配括号内的任何字符

`[!aeiou]`

预先定义的通配符

  • [[:alpha:]]
  • [[:alnum:]]
  • [[:space:]]
  • [[:upper:]]]
  • [[:lower:]]
  • [[:digit:]]

匹配通配符 有些情况下我们想匹配*?等特殊字符,可以使用转义字符
\*
\?

调试

0_tkeark2bzm7xxvsb

bash提供一些可选项帮助你调试程序
Some Options:

  1. -x option
    它在执行时打印命令和参数。它被称为打印调试,跟踪或x跟踪。我们可以通过修改第一行来使用它
  2. -e option
    它代表“出错”。如果命令以非零退出状态退出,这将导致脚本立即退出。
  3. -v option
    它在读取时打印shell命令/输入行。

**注意:**这些选项可以组合使用,一次可以使用多个选项!

#!/bin/bash-xe
#!/bin/bash-ex
#!/bin/bash-x-e
#!/bin/bash-e-x

尽管有许多工具可用于帮助调试,但只有在了解其工作原理的情况下才能调试代码。所以,确保花足够的时间实际练习编写脚本并自己调试它们,这样你就可以知道你可以犯错误的地方,这样你就不会再这样做:)
原文🔗

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.