duyuanch / blog Goto Github PK
View Code? Open in Web Editor NEWBlog
License: Apache License 2.0
Blog
License: Apache License 2.0
Java中的Object类位于java.lang
包中,每一个Java类直接或者间接继承自Object,如果一个类没有继承任何类,那么该类默认直接继承Object,如果一个类继承了某一个类,那么Object间接继承了Object,因此所有Java对象都可以访问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
方法用于克隆一个和该对象完全一样的新对象
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);
}
int
或enum
类型,否则如float
等其他数据类型是无法通过的编译的,因为编译器需要switch后面的语句和case后面的值精确匹配,而计算机无法精确表达一个float
数据类型:
分隔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后面的语句
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;
}
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++;
}
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]);
}
}
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]);
}
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));
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);
}
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
Java单例模式是四中创建模式之一,本文我们讲从例子讲解单例模式的方方面面
Java限制了类的实例,保证了JVM中只能存在一个某个类的一个实例
单实例类必须提供一个访问该实例的全局方法(其实就是public static
)
单例模式使用在日志(logging),驱动(driver),缓存(caching),线程池(thread pool)
Singleton设计模式也用于其他设计模式,如Abstract Factory,Builder,Prototype,Facade等。
要实现单例子模式,我们有很多不同的实现方法,但很多地方是相同的
私有化构造器,防止从其他类中构造实例
私有化该静态单例
定义public static的get方法获取该单例,便于从其他类中获取该实例
在后面的部分中,我们将学习Singleton模式实现的不同方法以及设计实现的关注点。
急切初始化模式,顾名思义就是当类加载时就会创建实例对象,这是实现单例模式的最简单方法,但是这样存在一个缺点就是虽然创建了该实例,但是有可能该实例不会被其他类使用,并且类的加载会变慢,因为类加载时需要创建对象
举一个例子
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;
}
}
在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
}
}
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
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 ? get(i)==null : 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;
}
#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;
}
首先什么是Python?根据他的创造者-Guido van Rossum
Python是一种
Python是一种高级语言,它核心的设计哲学是代码的可读性以及它的语法能让程序员使用更少的代码表达概念
事实上对于我来说,第一个学习Python的原因是Python是一门优美的编程语言,使得编码更加简单,能更好的表达我的**
另一个原因我可以在其他很多方面使用Python,如数据科学,Web开发,机器学习等方面都能更好的发挥它的作用,Quora,Pinterest和Spotify都使用Python进行后端Web开发,以及国内的豆瓣平台,
全世界最大的YouTube平台。我们一起来了解一下这门编程语言吧。。。
欢迎订阅我的→YouTube
您可以将变量视为存储值的一个单词。就那么简单。
在Python中,定义变量并给变量赋值特别简单,想象一下你要把数值1
存储到变量one
中,如下
one = 1
很简单对吧,你刚刚已经把1
赋值给了变量one
two = 2
some_number = 10000
并且您可以将任何其他值分配给您想要的任何其他变量。如上所示,变量two
存储整数2
,some_number
存储10,000
。
除了整数,我们还可以使用布尔值(True / False),字符串,float和许多其他数据类型。
# booleans
true_boolean = True
false_boolean = False
# string
my_name = "shellhub"
# float
book_price = 15.80
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")
在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个元素)。
想象一下,您希望将整数1存储在变量中。但也许你现在要存储2.而3,4,5 ......
我是否有另一种方法可以存储我想要的所有整数,但不能存储数百万个变量?你猜对了 - 确实存在另一种存储它们的方法。
List
是一个集合,可用于存储值列表(如您想要的这些整数)。所以让我们用它:
my_integers = [1, 2, 3, 4, 5]
这很简单。我们创建了一个数组并将其存储在my_integer上。
但也许你在问:“我如何从这个数组my_integer
中得到一个值?”
好问题。 List
有一个名为index的概念。第一个元素获得索引0(零)。第二个得到1,依此类推。你应该明白了吧。
使用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
我们先了解到这儿吧。我们来谈谈另一种数据结构。
首先我们看看什么是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版本测试通过
按照编程的常规,我们也从地一个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!
更新中...
原文连接
lombok可以通过一个注解自动实现Getter
Setter
等方法.平时我们可以通过Eclipse
或者IntelliJ IDEA
快捷键生成Getter
Setter
方法,当我们需要加入一个新的属性或者修改某属性的变量名或者数据类型时,都需要手动修改Getter
Setter
方法,这样极为麻烦.有了lombok,使得代码更加简洁,同时节约编码时间。本文适用于IntelliJ IDEA
及Android Studio
。
我们可以直接下载jar
包,并添加到构建路径中,但是推荐你使用构建工具
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.2</version>
<scope>provided</scope>
</dependency>
</dependencies>
compileOnly 'org.projectlombok:lombok:1.18.2'
如果你是用的是其他构建工具,请参考
@Data
使用
我们可以直接在类名上使用该注解,lombok
会自动生成对应的Getter
Setter
toString
等方法
@Data public class DataExample {
private final String name;
private int age;
private double score;
private String[] tags;
}
@Getter
和Setter
可以设置访问成员变量的权限
@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 IDEA
和Android Studio
中安装lombok插件.
File
-> Settings
-> Plugins
-> Browse Repositories
点击右侧的Install按钮安装,安装完成后需要重启IDE
Lombok
是一个很优秀的开源库,通过该开源库你可以实现如Kotlin
一样无需手写Getter
和Setter
等方法,极为方便。
视频演示传送门
最近更新了最新版本的ios Telegram后,发现无法链接到服务器,一直处于Connectting状态,即使是开启了ss的全局模式也是没有任何作用,强制让Telegram去监听socks5的端口号,试了1080
,1086
,1087
等一些列端口号都无果,最终的解决方案是通过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
IOS端设置如下
Setting > Data Storage > Use Proxy > + Add Proxy > MTProto
分别输入
Server
:服务器ip地址
Port
:端口号
Secret
:32位16进制端口号
如使用 nohup sslocal -c config.json &
后台运行Shadowsocks,假如你的本地机器运行以下ip和port
127.0.0.1 1080
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
工具
ubuntu/debian
sudo apt-get install dnsutils
centos
sudo yum install bind-utils
前往Github官网https://github.com/shadowsocks/ShadowsocksX-NG/releases下载最新版的zip压缩包
双击下载下来的压缩包进行解压
双击打开解压后的Shadowsocks可执行文件,会弹出安全提示,选择Open
| 打开
确认打开
Servers
| 服务器
》 Servers Preferences
| 服务器配置
手动配置节点信息,点击左下脚的➕添加配置信息,从左往右,从下网上分别对应填写服务器地址
》端口号
》加密方式
》密码
》备注
,配置🌰点击Ok
| 确认
,访问以下任一站点测试
原文地址
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
用起来太舒服了
写了一个Shaodwsocks一键服务器搭建脚本
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
同时会生成二维码,客服端可以直接扫描
An HTTP+HTTP/2 client for Android and Java applications.
官方对okhttp的概括就是一个在Android平台和Java平台支持HTTP和HTTP2的网络请求库
现在开发主要使用项目构建工具如Maven
, Gradle
。我们也可以直接下载jar
包的形式
Maven依赖(主要在Java项目中使用)
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.11.0</version>
</dependency>
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
先介绍一下okhttp的优点
当网络中断或者延迟比较高时,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文档
想要使用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
可以从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();
}
如果你是一个程序员或者技术员,那么您肯定听说过Docker-一种用于在容器中打包,传输,并且运行的有用工具,Docker在过去一段时间到现在得到了很多开发人员和系统管理员的关注,甚至像Google,VMWare,Amazon等大公司也在建立支持他的服务。
我仍然认为了解“容器”是什么以及与虚拟机(VM)的比较的一些基本概念仍然很重要。尽管Internet上有很多关于Docker的出色使用指南,但我找不到很多对初学者友好的概念指南,尤其是关于容器的组成。所以,希望这篇文章能解决这个问题:)
容器和虚拟的目标是相似的,都是将应用和其依赖隔离到一个可以在任何地方运行的独立单元。
此外,容器和VM消除了对物理硬件的需求,从而在能源消耗和成本效率方面都可以更有效地利用计算资源。
容器和VM之间的主要区别在于它们的体系结构方法。让我们仔细看看。
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());
}
}
我们可以使用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"
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")
}
How I can run shadow socks manager automaticaly at startup on Linux CentOS 7?
原文连接
通过网页端登录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
ssh root@ip_address
原文链接
我们知道,在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;
^
这是警告提示,提示我们需要在struct
和union
数据类型定义的后面加上分号;
,这样的好处就是当我们需要再添加一个成员变量的时候,只需写上该成员变量的定义,而无需先敲;
,我太机智了,手动滑稽...
我们也可以定义一个空的结构体,有时候我们需要某一个结构体数据类型,但是暂时又不知道如何填充里面的成员变量,我们可以有如下定义
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;
}
有时候我们内存紧张的时候,我们可以使用位域定义结构体成员变量,比如当我们需要定义一个表示true
或false
的时候,如果想这样定义
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
如果你对循环特别熟悉,这个数列求和就显得特别简单,一个循环嵌套就搞定了,代码如下
#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;
}
对于我自己来说,学习新框架或技术的最佳方式是同时获得实践经验,在本文中,你将自己通过编写代码来学习shell脚本的基础知识!本文包含语法,shell脚本的基础知识到中级shell编程,通过这篇文章你可以学习shell的相关知识,并且通过shell来实现Unix/Linux之间的接口
您可能已经多次遇到过“脚本”这个词,但脚本的的含义是什么意思呢?简单的来说,脚本是包含一系列要执行的命令。这些命令由解释器执行。一切你可以在命令行中输入的命令,你都可以把它放到脚本中。而且,脚本非常适合自动化任务。如果你发现自己频繁重复一些命令,你可以创建一个脚本来实现它!
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
好流弊😯!你刚刚编写了你的第一个bash脚本。
我知道你不理解这个脚本,特别对于脚本中的第一行。不要担心我将在本文中详细介绍shell脚本,在进入任何主题之前,我总是建议在脑海中形成路线图或适当的内容索引,并明确我们将要学习的内容。
因此,以下是我们将在文章中讨论的一些要点。
所以,这将是我们讨论的顺序,在本文的最后,我相信你会有足够的信心编写自己的shell脚本:)
Happy Learning Guys
你可以从上面脚本的第一行看到 #!/bin/bash
这行指定了你的程序将使用那个解释器,基本上是将路径引用到解释器。Linux/Unix中有很多解释器,其中一些是:bash,zsh,sh,csh和ksh等。
这里推荐一个玩命令行必须知道的一个开源项目oh-my-zsh
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)
合法的变量名
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]
是错误的写法
-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
-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
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-else
或if-elif-else
Avoid the Gates of Hell. Use Linux!
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
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
可以通过循环执行同一个代码块很多次
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
while
循环true
时,循环执行while
里面的代码块while [ CONNDITION_IS_TRUE ]
do
# Commands will change he entry condition
command 1
command 2
...
...
command N
done
判断条件可以是任意的测试或者命令,如果测试或命令返回0,则表示条件成立,如果为非0则退出循环,如果一开始条件就不成立,则循环永远不会执行。
如果你不知道退出状态码是什么请不要担心,我后面会告诉你 :)
例子 一行一行读取文件内容
#!/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
,状态码可以用来检查错误
上一条命令执行后的退出状态码被保存在变量$?
中
例子 使用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
可以把一些列的命令或语句定义在一个函数内,从程序的其他地方调用
注意
语法
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]`
预先定义的通配符
匹配通配符 有些情况下我们想匹配*
或?
等特殊字符,可以使用转义字符
\*
\?
bash提供一些可选项帮助你调试程序
Some Options:
-x
option-e
option-v
option**注意:**这些选项可以组合使用,一次可以使用多个选项!
#!/bin/bash-xe
#!/bin/bash-ex
#!/bin/bash-x-e
#!/bin/bash-e-x
尽管有许多工具可用于帮助调试,但只有在了解其工作原理的情况下才能调试代码。所以,确保花足够的时间实际练习编写脚本并自己调试它们,这样你就可以知道你可以犯错误的地方,这样你就不会再这样做:)
原文🔗
原文链接
这篇主要介绍如何在Android中实现Parcelable
我们都知道,可以使用Bundle对象在Andriod各大组建之间传递数据,使用Bundle.putXXX(KEY, VALUE)
可以存放一些基本数据类型以及封装类型,以下是Bundle对象可以存储的数据类型
存储的时候使用键值的方式进行存储并传递
如果我们需要使用Bundle传递对象,我们可以
implements Serializable
,或者implements Parcelable
这种方式非常简单,我们直接实现Serializable
接口就行,这个接口是一个空的接口,所以我们不需要做任何操作,如果有必要我们可以定义一个serialVersionUID
public interface Serializable {
}
我们常见的File
类就实现了这个接口
public class File
implements Serializable, Comparable<File>
{
...
}
这个相比与 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 +
'}';
}
}
传递
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) {
public @NonNull Intent putExtra(String name, Parcelable[] value) {
对象中定义的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
脚本已经开源,欢迎 star
拥有一台国外服务器,比如我常用的vultr
一个gmail邮箱,用于发送邮件给注册用户激活帐号
一个开通面付功能的支付宝帐号(注:普通用户无法开通)
使用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
https://github.com/shadowsocks/shadowsocks
https://github.com/shadowsocks/shadowsocks-manager
前往官方主页下载Shadowsocks最新版本,展开Assets
下载Shadowsocks-4.1.8.0.zip
安装包
下载后,解压后双击解压文件夹的Shadowsocks.exe
注意:Windows 7用户需下载.net framework才可以打开,否则有可能失败
打开后任务栏中会出现一个小飞机图标(一般隐藏在任务栏中)
如: 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
如十进制数
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!)
如:
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
如:
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
链表的定义
链表(Linked list)是一种常见的基础数据结构,是一种线性表,但是并不会按线性的顺序存储数据,而是在每一个节点里存到下一个节点的指针(Pointer)。由于不必须按顺序存储,链表在插入的时候可以达到O(1)的复杂度,比另一种线性表顺序表快得多,但是查找一个节点或者访问特定编号的节点则需要O(n)的时间,而顺序表相应的时间复杂度分别是O(logn)和O(1)。
相对于线性表链表的优点
使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。
链表中最简单的一种是单向链表,它包含两个域,一个信息域和一个指针域。这个链接指向列表中的下一个节点,而最后一个节点则指向一个空值。
一个单向链表的节点被分成两个部分。第一个部分保存或者显示关于节点的信息,第二个部分存储下一个节点的地址。单向链表只可向一个方向遍历。
一种更复杂的链表是“双向链表”或“双面链表”。每个节点有两个连接:一个指向前一个节点,而另一个指向下一个节点
在一个 循环链表中, 首节点和末节点被连接在一起。
插入操作(insert)
删除操作(Deletion)
最近开发了一个网易云音乐播放器,有这么一个需求,需要展示搜索建议,历史搜索记录
项目地址: 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()));
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();
}
}
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)));
});
}
}
}
原文连接
我们在项目中经常需要用到设置(Setting),在安卓中主要使用 android.preference.PreferenceFragment
和 android.support.v7.preference.PreferenceFragmentCompat
,在Android API 28中PreferenceFragment
已经过时,推荐使用PreferenceFragmentCompat
This class was deprecated in API level 28.
Use PreferenceFragmentCompat
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
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中提供了SQLite数据库进行数据的持久化 ,并提供了对应API访问数据库,而Room框架提供了SQLite数据访问抽象层,为高效的数据库访问层带来便捷
APP可以缓存用户数据,当APP离线时便从SQLite读取数据,当重新连线时即可完成和服务器数据的同步
谷歌官方强烈推荐使用Room框架操作SQLite数据库
首先在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
属性对应数据库中的字段名,并实现该实体类的Getter
,Setter
方法
@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 + '\'' +
'}';
}
}
创建实体类对应的dao
层UserDao
,完成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...
最好使用设计模式中的单例模式获取数据库实例,因为每次获取数据库实例都很耗时并且耗内存,我们可以自定义一个类继承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;
}
}
在有的时候你可能会使用一些本地代码去(Native Code)如C/C++去解决Java性能问题,可以调用如C/C++等语言编写的库(library),这种技术我们叫做Java Native Interface(JNI)。
在使用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();
}
}
注意
-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
目录下,使用以下命令可以查看具体位置
find $(echo $JAVA_HOME) -name jni.h
cd <JAVA_HOME>\include\win32
Step 4. 编译C程序
不同平台有不同的编译器,待续...ing
求解最大公约数和最小公倍数
最大公因数,也称最大公约数、最大公因子,指两个或多个整数共有约数中最大的一个
两个或多个整数公有的倍数叫做它们的公倍数
#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;
}
在抽象类中可以写非抽象的方法,从而避免在子类中重复书写他们,这样可以提高代码的复用性,这是抽象类的优势;接口中只能有抽象的方法。
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;
}
抽象类中可以定义构造函数,但是不能实例化,至于接口的话之前说过,是不能有方法具体的实现的,故不能有构造函数
public abstract class Person {
String name;
String age;
Person(String name, int age) {
}
}
速度比较
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次的时间比较
最终胜利:自己判断吧,这点测试结果说明不了什么问题,这个问题不能很片面,因为有很多因素,下面贴一个SO上的回答
在抽象类中可以写非抽象的方法,从而避免在子类中重复书写他们,这样可以提高代码的复用性,这是抽象类的优势;接口中只能有抽象的方法。
抽象类中定义的成员变量默认不是public,接口中定义的方法默认是public,
一个类可以继承一个抽象类(JAVA的单继承),但是可以实现多个接口 (WTF这是区别么)
抽象类中的成员变量可以不初始化,接口中的成员变量必须初始化,默认为static, final
抽象类中可以定义构造函数,但是不能实例化,接口不能定义构造函数
速度比较(待定)
如果你往抽象类中添加新的方法,你可以给它提供默认的实现。因此你不需要改变你现在的代码。 如果你往接口中添加方法,那么你必须改变实现该接口的类。
想到在补充...
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:
#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");
}
#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;
}
JSON(JavaScript Object Notation)是一种由道格拉斯·克罗克福特构想和设计、轻量级的数据交换语言,该语言以易于让人阅读的文字为基础,用来传输由属性值或者序列性的值组成的数据对象。尽管JSON是JavaScript的一个子集,但JSON是独立于语言的文本格式,并且采用了类似于C语言家族的一些习惯。
JSON 数据格式与语言无关,脱胎于 JavaScript,但目前很多编程语言都支持 JSON 格式数据的生成和解析。JSON 的官方 MIME 类型是 application/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
}
配置文件用XML较多,如Android里面通过XML定义布局,主流框架(Spring家族)用XML较多
本例我们使用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}"
}
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
dependencies {
implementation 'com.google.code.gson:gson:2.8.5'
}
O(n)
O(n)
O(1)
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();
}
查找最晚入职员工的所有信息
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
写一个专门用于搭建Telegram代理MTProxy的脚本
https://github.com/shellhub/shellhub/blob/master/proxy/mt_proxy.sh
复制到服务器中自动编译安装
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
***************************************************
客服端链接到代理服务器
视频教程
CentOS(Community Enterprise Operating System)是Linux发行版之一,它是来自于Red Hat Enterprise Linux依照开放源代码规定发布的源代码所编译而成。由于出自同样的源代码,因此有些要求高度稳定性的服务器以CentOS替代商业版的Red Hat Enterprise Linux使用。两者的不同,在于CentOS并不包含封闭源代码软件。CentOS 对上游代码的主要修改是为了移除不能自由使用的商标。2014年,CentOS宣布与Red Hat合作,但CentOS将会在新的委员会下继续运作,并不受RHEL的影响
Shadowsocks可以指:一种基于Socks5代理方式的加密传输协议,也可以指实现这个协议的各种传输包。目前包使用Python、C、C++、C#、Go语言等编程语言开发,大部分主要实现(iOS平台的除外)采用Apache许可证、GPL、MIT许可证等多种自由软件许可协议开放源代码。shadowsocks分为服务器端和客户端,在使用之前,需要先将服务器端部署到服务器上面,然后通过客户端连接并创建本地代理。
在**大陆,本工具也被广泛用于突破防火长城(GFW),以浏览被封锁、屏蔽或干扰的内容。2015年8月22日,Shadowsocks原作者Clowwindy称受到了**政府的压力,宣布停止维护此计划(项目)并移除其个人页面所存储的源代码。因为移除之前就有大量的复制副本,所以事实上并未停止维护,而是转由其他贡献者们持续维护中。
虚拟专用服务器(英语: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主机用户可在服务器上自行安装程序,单独重启主机。
Secure Shell(安全外壳协议,简称SSH)是一种加密的网络传输协议,可在不安全的网络中为网络服务提供安全的传输环境。SSH通过在网络中创建安全隧道来实现SSH客户端与服务器之间的连接。虽然任何网络服务都可以通过SSH实现安全传输,SSH最常见的用途是远程登录系统,人们通常利用SSH来传输命令行界面和远程执行命令。使用频率最高的场合类Unix系统,但是Windows操作系统也能有限度地使用SSH。2015年,微软宣布将在未来的操作系统中提供原生SSH协议支持。
Google VPS提供商会有很多选择,或者使用我推荐的VPS(不强制要求),本教程推荐使用Vultr
注册完对应厂商的VPS账号并充值后,你就可以开始创建VPS服务器了,但是每个提供商的VPS控制面板不一样,创建要求选择操作系统类型,本教程选择CentOS 7,创建完成后你可以在控制面板累看到以下信息
IP Address
)如8.8.8.8
Username
)大部分情况是root
Password
)一般很复杂,比如a5X%![jnqsYr.1TW
Port
)一般是22
以上信息非常重要!!!是你能不能登陆一台VPS服务器的关键,有一个地方❌均无法远程连接到服务器
VPS服务器一般位于国外(国内的VPS是无法用于科学上网滴),我们需要使用SSH去连接VPS.我推荐几款各大平台的SSH客服端,类Unix操作系统自带SSH客服端
ssh [email protected] -p 22
介绍这里的几个参数
ssh [email protected]
执行上面的命令,输入服务器密码,如果都正确输入的话就可以远程控制服务器了
如果你的VPS软件包没有更新,执行以下命令
yum update -y && yum upgrade -y
yum install python-setuptools && easy_install pip && pip install shadowsocks
对以下涉及到的参数说明一下
这种方式运行的缺点就是一旦终端与服务器断开连接,进程就被杀了,无法继续科学上网,可以使用下面的后台运行
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
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监听端口号,或者直接关闭防火墙
打开端口号
sudo firewall-cmd --zone=public --add-port=443/tcp --permanent
sudo firewall-cmd --reload
关闭防火墙
systemctl stop firewalld
打开端口号
iptables -A INPUT -m state --state NEW -p tcp --dport 443 -j ACCEPT
/etc/init.d/iptables restart
关闭防火墙
service iptables stop
原文更新地址
Oh-My-Zsh!提高你CLI(Command-line interface
)的神奇工具 - Ubuntu教程
我是命令行界面的忠实粉丝......我不喜欢使用我的电脑鼠标!这促使我寻找出色的工具来增强我在CLI上的用户体验,一次偶然的机会机会在YouTube上观看了国外YouTuber使用该工具,促使我对他产生了兴趣.本教程基于Ubuntu Linux,其他操作系统差不多
by the way,关注我的YouTube频道呗
以下是oh-my-zsh部分功能
我在我的Linux Mint上执行此安装指南。为了向您展示Oh-My-Zsh的基本功能,我将安装Git插件(Git-core)。此插件提供有关项目的Git状态的可视反馈。
$ sudo apt install git-core 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 -)"
$ sudo apt install fonts-powerline
$ vim ~/.zshrc
想要看到修改后的主题结果,执行下面命令
$ source ~/.zshrc
zsh
而不是bash
$ chsh -s $(which zsh)
所有插件都列在Plugins,自定义插件可以安装在〜/.oh-my-zsh/custom/plugins
中。要使用插件,只需将其添加到〜/.zshrc
文件中的插件列表即可。明智地添加,因为太多的插件会减慢shell的启动速度。插件之间使用空格分割。
在这个例子中,我安装了一个有用的插件,为你的手册页提供颜色突出显。
另一个很棒的插件是shell的语法高亮。除此之外,此插件还能够验证命令的正确性
# 安装
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-syntax-highlighting
一样
$ git clone https://github.com/zsh-users/zsh-autosuggestions
然后添加zsh-syntax-highlighting到插件列表中(vim ~/.zshrc
)
autojump可以实现快速跳转到目标目录,如下所示
然后别忘记添加到~/.zshrc
配置文件中
vim ~/.zshrc
Oh-My-Zsh会自动记住您访问过的最后20个目录。您可以使用dirs -v
或d
来按时间顺序列出历史记录。
您可以使用cd +1
转到上一个目录,依此类推,如下图我们还可以直接输入数字进行跳转到对应的目录
/ -> 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:https://ohmyz.sh/
插件: https://github.com/robbyrussell/oh-my-zsh/tree/master/plugins
主题: https://github.com/robbyrussell/oh-my-zsh/tree/master/themes/
Oh-My-Zsh
太爽了
在计算机科学中,二分搜索(英语:binary search),也称折半搜索(英语:half-interval search)[1]、对数搜索(英语:logarithmic search)[2],是一种在有序数组中查找某一特定元素的搜索算法。搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束;如果某一特定元素大于或者小于中间元素,则在数组大于或小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较。如果在某一步骤数组为空,则代表找不到。这种搜索算法每一次比较都使搜索范围缩小一半。
二分搜索在情况下的复杂度是对数时间,进行 O(logn) 次比较操作 n 在此处是数组的元素数量, O 是大O记号,(log 是对数)。二分搜索使用常数空间,无论对任何大小的输入数据,算法使用的空间都是一样的。除非输入数据数量很少,否则二分搜索比线性搜索更快,但数组必须事先被排序。尽管特定的、为了快速搜索而设计的数据结构更有效(比如哈希表),二分搜索应用面更广。
二分搜索有许多中变种。比如分散层叠可以提升在多个数组中对同一个数值的搜索。分散层叠有效的解决了计算几何学和其他领域的许多搜索问题。指数搜索将二分搜索拓宽到无边界的列表。二分搜索树和B树数据结构就是基于二分搜索的。
下面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;
}
参考链接
工厂设计模式(Factory Design Pattern)属于创建模式之一,工厂设计模式在JDK,Spring,Stuts被广泛使用
当一个类或者接口有多个子类,并且基于输入返回特定的子类,此时会使用工厂设计模式。这种模式负责从客户端到工厂类的实例化。
让我们首先学习如何在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();
}
}
假设我们有两个子类PC
和Server
,具有以下实现。
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
java.util.Calendar
,ResourceBundle
和NumberFormat getInstanc()
方法使用Factory模式。valueOf()
方法,如Boolean
,Integer
等。原文托管在Github: #52
数据结构与算法是一个程序员必备的技能之一,而顺序表更是每个程序员在面试过程中要经常被问到的,如Java语言中的ArrayList
类的底层实现就是使用顺序表实现的,别把顺序表想的有多么高大上,其实就是使用数组实现的一种线性表
线性表(英语:Linear List)是由n(n≥0)个数据元素(结点)a[0],a[1],a[2]…,a[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
),分配太大浪费,造成内存的碎片化A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.