月度归档: 2021 年 5 月

冒泡排序法

冒泡排序法

冒泡排序算法问题:
算法介绍:冒泡排序法是一种简单的排序算法,假设有某一数列 a 1 , a 2 , … a n a_1,a_2,…a_n a
1

,a
2

,…a
n

,算法过程如下。

step1: 首先比较 a 1 a_1 a
1

与 a 2 a_2 a
2

的大小,如果 a 1 > a 2 a_1>a_2 a
1

>a
2

,则令 a 1 a_1 a
1

与 a 2 a_2 a
2

相互交换,接着比较 a 2 a_2 a
2

与 a 3 a_3 a
3

的大小,同样,如果 a 2 > a 3 a_2>a_3 a
2

>a
3

,则令 a 2 a_2 a
2

与 a 3 a_3 a
3

相互交换,以此类推,直至比较 a n − 1 a_{n-1} a
n−1

与 a n a_n a
n

的大小,如果 a n − 1 > a n a_{n-1}>a_n a
n−1

>a
n

,则令 a n − 1 a_{n-1} a
n−1

与 a n a_n a
n

相互交换,经过上述操作, a n a_n a
n

就变成了该数列中*大的元素。

step2: 仍然从 a 1 a_1 a
1

与 a 2 a_2 a
2

开始比较,沿用*步中的方法,不同的是,只需比较到 a n − 2 a_{n-2} a
n−2

与 a n − 1 a_{n-1} a
n−1

,以保证 a n − 1 a_{n-1} a
n−1

是前 n − 1 n-1 n−1个元素中的*大数。

step3: 按照上述步骤step2,即可保证 a i a_i a
i

是前 i i i个元素中的*大数,其中 i = n − 2 , n − 1 , … 1 i=n-2,n-1,…1 i=n−2,n−1,…1,这样,*终所得到的新的数列 a 1 , a 2 , … a n a_1,a_2,…a_n a
1

,a
2

,…a
n

即为原始数列从小到大排列后的结果。

MATLAB程序实现

%Date:2019-11-11
%Writer:无名十三

%%本程序的目的是利用冒泡排序算法对已知某组数据进行排序
function y = bubble_sort(A) %参数A为向量或矩阵
if nargin==0
errordlg(‘未输入参数!’, ‘Error!’)
end
if ischar(A) == 1
errordlg(‘输入数据中含有字符或字符串,无法进行排序!’, ‘Error!’)
error %报错终止程序运行
end

m = size(A); %求出参数A的维数
if m(1)==1 | m(2)==1 %当参数A为向量不是矩阵时
n = length(A); %得到向量A布包含的元素个数
else %参数A为矩阵而非向量时
n = m(1)*m(2); %得到矩阵A中包含的元素个数
A = reshape(A, 1, n); %化矩阵A为向量
end

for i = 1:n-1
for j = 1:n-i
if A(j) > A(j+1)
temp = A(j); %核心代码
A(j) = A(j+1);
A(j+1) = temp;
end
end
end

fprintf(‘经冒泡排序算法排序后的结果如下:\n\n’) %输出排序结果
for i = 1:n
fprintf(‘%f ‘, A(i))
if mod(i,8) == 0
fprintf(‘\n\n’)
end
end
fprintf(‘\n’)
end
%%

示例1:对如下向量A进行排序
>> A = [1 6 9 8 13 6 9 5 20 15 36 27];
>> bubble_sort(A)
经冒泡排序算法排序后的结果如下:

1.000000 5.000000 6.000000 6.000000 8.000000 9.000000 9.000000 13.000000

15.000000 20.000000 27.000000 36.000000

示例2:对如下三阶方阵B进行排序
>> B = [2 3 8; 9 6 7; 15 32 17];
>> bubble_sort(B)
经冒泡排序算法排序后的结果如下:

2.000000 3.000000 6.000000 7.000000 8.000000 9.000000 15.000000 17.000000

32.000000

判断素数:

P判断素数:
说明:本程序首先定义一个判断素数并将其输出的函数 I s P r i m e ( ) IsPrime() IsPrime(),函数包含两个参数 a a a和 b b b,本程序的作用即输出 a a a和 b b b之间的所有素数。
Python程序实现
#Date:2019-11-12
#Writer:无名十三

def IsPrime(a, b): #定义一个判断素数的函数
list_Prime = [] #创建一个空列表,用于接收a与b之间的素数
for i in range(a, b+1):
for j in range(2, i+1):
if j < i:
if i % j == 0:
break
if j == i:
list_Prime.append(i) #将经判断得到的素数放入列表中

print(‘从{}至{}之间的素数如下:\n’.format(a, b))
for k in range(len(list_Prime)):
print(list_Prime[k], end = ‘ ‘)
if (k+1) % 6 == 0:
print(‘\n’)
print(‘\n\n共{}个素数.’.format(len(list_Prime)))

示例:输出1至100之间所有的素数如下:
IsPrime(1, 100) #调用函数
1
程序运行结果如下:
从1至100之间的素数如下:

2 3 5 7 11 13

17 19 23 29 31 37

41 43 47 53 59 61

67 71 73 79 83 89

97

共25个素数.

详解Java8 Collect收集Stream的方法

转载自:https://www.jb51.net/article/138519.htm

这篇文章主要介绍了Java8-Collect收集Stream的方法,提到了收集器的作用,连接收集器的方法,需要的朋友可以参考下

Collection, Collections, collect, Collector, Collectos

Collection是Java集合的祖先接口。
Collections是java.util包下的一个工具类,内涵各种处理集合的静态方法。
java.util.stream.Stream#collect(java.util.stream.Collector<? super T,A,R>)是Stream的一个函数,负责收集流。
java.util.stream.Collector 是一个收集函数的接口, 声明了一个收集器的功能。
java.util.Comparators则是一个收集器的工具类,内置了一系列收集器实现。

收集器的作用

你可以把Java8的流看做花哨又懒惰的数据集迭代器。他们支持两种类型的操作:中间操作(e.g. filter, map)和终端操作(如count, findFirst, forEach, reduce). 中间操作可以连接起来,将一个流转换为另一个流。这些操作不会消耗流,其目的是建立一个流水线。与此相反,终端操作会消耗类,产生一个*终结果。collect就是一个归约操作,就像reduce一样可以接受各种做法作为参数,将流中的元素累积成一个汇总结果。具体的做法是通过定义新的Collector接口来定义的。

预定义的收集器

下面简单演示基本的内置收集器。模拟数据源如下:

final ArrayList<Dish> dishes = Lists.newArrayList(
    new Dish("pork", false, 800, Type.MEAT),
    new Dish("beef", false, 700, Type.MEAT),
    new Dish("chicken", false, 400, Type.MEAT),
    new Dish("french fries", true, 530, Type.OTHER),
    new Dish("rice", true, 350, Type.OTHER),
    new Dish("season fruit", true, 120, Type.OTHER),
    new Dish("pizza", true, 550, Type.OTHER),
    new Dish("prawns", false, 300, Type.FISH),
    new Dish("salmon", false, 450, Type.FISH)
);

*大值,*小值,平均值

// 为啥返回Optional? 如果stream为null怎么办, 这时候Optinal就很有意义了
Optional<Dish> mostCalorieDish = dishes.stream().max(Comparator.comparingInt(Dish::getCalories));
Optional<Dish> minCalorieDish = dishes.stream().min(Comparator.comparingInt(Dish::getCalories));
Double avgCalories = dishes.stream().collect(Collectors.averagingInt(Dish::getCalories));
IntSummaryStatistics summaryStatistics = dishes.stream().collect(Collectors.summarizingInt(Dish::getCalories));
double average = summaryStatistics.getAverage();
long count = summaryStatistics.getCount();
int max = summaryStatistics.getMax();
int min = summaryStatistics.getMin();
long sum = summaryStatistics.getSum();

这几个简单的统计指标都有Collectors内置的收集器函数,尤其是针对数字类型拆箱函数,将会比直接操作包装类型开销小很多。

连接收集器

想要把Stream的元素拼起来?

//直接连接
String join1 = dishes.stream().map(Dish::getName).collect(Collectors.joining());
//逗号
String join2 = dishes.stream().map(Dish::getName).collect(Collectors.joining(", "));

toList

List<String> names = dishes.stream().map(Dish::getName).collect(toList());

将原来的Stream映射为一个单元素流,然后收集为List。

toSet

Set<Type> types = dishes.stream().map(Dish::getType).collect(Collectors.toSet());

将Type收集为一个set,可以去重复。

toMap

Map<Type, Dish> byType = dishes.stream().collect(toMap(Dish::getType, d -> d));

有时候可能需要将一个数组转为map,做缓存,方便多次计算获取。toMap提供的方法k和v的生成函数。(注意,上述demo是一个坑,不可以这样用!!!, 请使用toMap(Function, Function, BinaryOperator))

上面几个几乎是*常用的收集器了,也基本够用了。但作为初学者来说,理解需要时间。想要真正明白为什么这样可以做到收集,就必须查看内部实现,可以看到,这几个收集器都是基于java.util.stream.Collectors.CollectorImpl,也就是开头提到过了Collector的一个实现类。后面自定义收集器会学习具体用法。

自定义归约reducing

前面几个都是reducing工厂方法定义的归约过程的特殊情况,其实可以用Collectors.reducing创建收集器。比如,求和

Integer totalCalories = dishes.stream().collect(reducing(0, Dish::getCalories, (i, j) -> i + j));
//使用内置函数代替箭头函数
Integer totalCalories2 = dishes.stream().collect(reducing(0, Dish::getCalories, Integer::sum));

当然也可以直接使用reduce

Optional<Integer> totalCalories3 = dishes.stream().map(Dish::getCalories).reduce(Integer::sum);

虽然都可以,但考量效率的话,还是要选择下面这种

int sum = dishes.stream().mapToInt(Dish::getCalories).sum();

根据情况选择*佳方案

上面的demo说明,函数式编程通常提供了多种方法来执行同一个操作,使用收集器collect比直接使用stream的api用起来更加复杂,好处是collect能提供更高水平的抽象和概括,也更容易重用和自定义。

我们的建议是,尽可能为手头的问题探索不同的解决方案,始终选择*专业的一个,无论从可读性还是性能来看,这一般都是*好的决定。

reducing除了接收一个初始值,还可以把*项当作初始值

Optional<Dish> mostCalorieDish = dishes.stream()
        .collect(reducing((d1, d2) -> d1.getCalories() > d2.getCalories() ? d1 : d2));

reducing

关于reducing的用法比较复杂,目标在于把两个值合并成一个值。

public static <T, U>
  Collector<T, ?, U> reducing(U identity,
                Function<? super T, ? extends U> mapper,
                BinaryOperator<U> op)

首先看到3个泛型,

U是返回值的类型,比如上述demo中计算热量的,U就是Integer。

关于T,T是Stream里的元素类型。由Function的函数可以知道,mapper的作用就是接收一个参数T,然后返回一个结果U。对应demo中Dish。

?在返回值Collector的泛型列表的中间,这个表示容器类型,一个收集器当然需要一个容器来存放数据。这里的?则表示容器类型不确定。事实上,在这里的容器就是U[]。

关于参数:

identity是返回值类型的初始值,可以理解为累加器的起点。

mapper则是map的作用,意义在于将Stream流转换成你想要的类型流。

op则是核心函数,作用是如何处理两个变量。其中,*个变量是累积值,可以理解为sum,第二个变量则是下一个要计算的元素。从而实现了累加。

reducing还有一个重载的方法,可以省略*个参数,意义在于把Stream里的*个参数当做初始值。

public static <T> Collector<T, ?, Optional<T>>
  reducing(BinaryOperator<T> op)

先看返回值的区别,T表示输入值和返回值类型,即输入值类型和输出值类型相同。还有不同的就是Optional了。这是因为没有初始值,而*个参数有可能是null,当Stream的元素是null的时候,返回Optional就很意义了。

再看参数列表,只剩下BinaryOperator。BinaryOperator是一个三元组函数接口,目标是将两个同类型参数做计算后返回同类型的值。可以按照1>2? 1:2来理解,即求两个数的*大值。求*大值是比较好理解的一种说法,你可以自定义lambda表达式来选择返回值。那么,在这里,就是接收两个Stream的元素类型T,返回T类型的返回值。用sum累加来理解也可以。

上述的demo中发现reduce和collect的作用几乎一样,都是返回一个*终的结果,比如,我们可以使用reduce实现toList效果:

//手动实现toListCollector --- 滥用reduce, 不可变的规约---不可以并行
List<Integer> calories = dishes.stream().map(Dish::getCalories)
    .reduce(new ArrayList<Integer>(),
        (List<Integer> l, Integer e) -> {
          l.add(e);
          return l;
        },
        (List<Integer> l1, List<Integer> l2) -> {
          l1.addAll(l2);
          return l1;
        }
    );

关于上述做法解释一下。

<U> U reduce(U identity,
         BiFunction<U, ? super T, U> accumulator,
         BinaryOperator<U> combiner);

U是返回值类型,这里就是List

BiFunction<U, ? super T, U> accumulator是是累加器,目标在于累加值和单个元素的计算规则。这里就是List和元素做运算,*终返回List。即,添加一个元素到list。

BinaryOperator<U> combiner是组合器,目标在于把两个返回值类型的变量合并成一个。这里就是两个list合并。
这个解决方案有两个问题:一个是语义问题,一个是实际问题。语义问题在于,reduce方法旨在把两个值结合起来生成一个新值,它是一个不可变归约。相反,collect方法的设计就是要改变容器,从而累积要输出的结果。这意味着,上面的代码片段是在滥用reduce方法,因为它在原地改变了作为累加器的List。错误的语义来使用reduce方法还会造成一个实际问题:这个归约不能并行工作,因为由多个线程并发修改同一个数据结构可能会破坏List本身。在这种情况下,如果你想要线程安全,就需要每次分配一个新的List,而对象分配又会影响性能。这就是collect适合表达可变容器上的归约的原因,更关键的是它适合并行操作。

总结:reduce适合不可变容器归约,collect适合可变容器归约。collect适合并行。

分组

数据库中经常遇到分组求和的需求,提供了group by原语。在Java里, 如果按照指令式风格(手动写循环)的方式,将会非常繁琐,容易出错。而Java8则提供了函数式解法。

比如,将dish按照type分组。和前面的toMap类似,但分组的value却不是一个dish,而是一个List。

Map<Type, List<Dish>> dishesByType = dishes.stream().collect(groupingBy(Dish::getType));

 

这里

public static <T, K> Collector<T, ?, Map<K, List<T>>>
  groupingBy(Function<? super T, ? extends K> classifier)

参数分类器为Function,旨在接收一个参数,转换为另一个类型。上面的demo就是把stream的元素dish转成类型Type,然后根据Type将stream分组。其内部是通过HashMap来实现分组的。groupingBy(classifier, HashMap::new, downstream);

除了按照stream元素自身的属性函数去分组,还可以自定义分组依据,比如根据热量范围分组。

既然已经知道groupingBy的参数为Function, 并且Function的参数类型为Dish,那么可以自定义分类器为:

private CaloricLevel getCaloricLevel(Dish d) {
  if (d.getCalories() <= 400) {
   return CaloricLevel.DIET;
  } else if (d.getCalories() <= 700) {
   return CaloricLevel.NORMAL;
  } else {
   return CaloricLevel.FAT;
  }
}

 

再传入参数即可

Map<CaloricLevel, List<Dish>> dishesByLevel = dishes.stream()
    .collect(groupingBy(this::getCaloricLevel));

多级分组

groupingBy还重载了其他几个方法,比如

public static <T, K, A, D>
  Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
                     Collector<? super T, A, D> downstream)

泛型多的恐怖。简单的认识一下。classifier还是分类器,就是接收stream的元素类型,返回一个你想要分组的依据,也就是提供分组依据的基数的。所以T表示stream当前的元素类型,K表示分组依据的元素类型。第二个参数downstream,下游是一个收集器Collector. 这个收集器元素类型是T的子类,容器类型container为A,reduction返回值类型为D。也就是说分组的K通过分类器提供,分组的value则通过第二个参数的收集器reduce出来。正好,上个demo的源码为:

public static <T, K> Collector<T, ?, Map<K, List<T>>>
  groupingBy(Function<? super T, ? extends K> classifier) {
    return groupingBy(classifier, toList());
  }

 

将toList当作reduce收集器,*终收集的结果是一个List<Dish>, 所以分组结束的value类型是List<Dish>。那么,可以类推value类型取决于reduce收集器,而reduce收集器则有千千万。比如,我想对value再次分组,分组也是一种reduce。

//多级分组
Map<Type, Map<CaloricLevel, List<Dish>>> byTypeAndCalory = dishes.stream().collect(
  groupingBy(Dish::getType, groupingBy(this::getCaloricLevel)));
byTypeAndCalory.forEach((type, byCalory) -> {
 System.out.println("----------------------------------");
 System.out.println(type);
 byCalory.forEach((level, dishList) -> {
  System.out.println("\t" + level);
  System.out.println("\t\t" + dishList);
 });
});

验证结果为:

----------------------------------
FISH
  DIET
    [Dish(name=prawns, vegetarian=false, calories=300, type=FISH)]
  NORMAL
    [Dish(name=salmon, vegetarian=false, calories=450, type=FISH)]
----------------------------------
MEAT
  FAT
    [Dish(name=pork, vegetarian=false, calories=800, type=MEAT)]
  DIET
    [Dish(name=chicken, vegetarian=false, calories=400, type=MEAT)]
  NORMAL
    [Dish(name=beef, vegetarian=false, calories=700, type=MEAT)]
----------------------------------
OTHER
  DIET
    [Dish(name=rice, vegetarian=true, calories=350, type=OTHER), Dish(name=season fruit, vegetarian=true, calories=120, type=OTHER)]
  NORMAL
    [Dish(name=french fries, vegetarian=true, calories=530, type=OTHER), Dish(name=pizza, vegetarian=true, calories=550, type=OTHER)]

总结:groupingBy的核心参数为K生成器,V生成器。V生成器可以是任意类型的收集器Collector。

比如,V生成器可以是计算数目的, 从而实现了sql语句中的select count(*) from table A group by Type

Map<Type, Long> typesCount = dishes.stream().collect(groupingBy(Dish::getType, counting()));
System.out.println(typesCount);
-----------
{FISH=2, MEAT=3, OTHER=4}

sql查找分组*高分select MAX(id) from table A group by Type

Map<Type, Optional<Dish>> mostCaloricByType = dishes.stream()
    .collect(groupingBy(Dish::getType, maxBy(Comparator.comparingInt(Dish::getCalories))));

这里的Optional没有意义,因为肯定不是null。那么只好取出来了。使用collectingAndThen

Map<Type, Dish> mostCaloricByType = dishes.stream()
  .collect(groupingBy(Dish::getType,
    collectingAndThen(maxBy(Comparator.comparingInt(Dish::getCalories)), Optional::get)));

到这里似乎结果出来了,但IDEA不同意,编译黄色报警,按提示修改后变为:

Map<Type, Dish> mostCaloricByType = dishes.stream()
  .collect(toMap(Dish::getType, Function.identity(),
    BinaryOperator.maxBy(comparingInt(Dish::getCalories))));

 

是的,groupingBy就变成toMap了,key还是Type,value还是Dish,但多了一个参数!!这里回应开头的坑,开头的toMap演示是为了容易理解,真那么用则会被搞死。我们知道把一个List重组为Map必然会面临k相同的问题。当K相同时,v是覆盖还是不管呢?前面的demo的做法是当k存在时,再次插入k则直接抛出异常:

java.lang.IllegalStateException: Duplicate key Dish(name=pork, vegetarian=false, calories=800, type=MEAT)
  at java.util.stream.Collectors.lambda$throwingMerger$0(Collectors.java:133)

 

正确的做法是提供处理冲突的函数,在本demo中,处理冲突的原则就是找出*大的,正好符合我们分组求*大的要求。(真的不想搞Java8函数式学习了,感觉到处都是性能问题的坑)

继续数据库sql映射,分组求和select sum(score) from table a group by Type

Map<Type, Integer> totalCaloriesByType = dishes.stream()
  .collect(groupingBy(Dish::getType, summingInt(Dish::getCalories)));

然而常常和groupingBy联合使用的另一个收集器是mapping方法生成的。这个方法接收两个参数:一个函数对流中的元素做变换,另一个则将变换的结果对象收集起来。其目的是在累加之前对每个输入元素应用一个映射函数,这样就可以让接收特定类型元素的收集器适应不同类型的对象。我么来看一个使用这个收集器的实际例子。比如你想得到,对于每种类型的Dish,菜单中都有哪些CaloricLevel。我们可以把groupingBy和mapping收集器结合起来,如下所示:

Map<Type, Set<CaloricLevel>> caloricLevelsByType = dishes.stream()
  .collect(groupingBy(Dish::getType, mapping(this::getCaloricLevel, toSet())));

这里的toSet默认采用的HashSet,也可以手动指定具体实现toCollection(HashSet::new)

分区

分区是分组的特殊情况:由一个谓词(返回一个布尔值的函数)作为分类函数,它称为分区函数。分区函数返回一个布尔值,这意味着得到的分组Map的键类型是Boolean,于是它*多可以分为两组:true or false. 例如,如果你是素食者,你可能想要把菜单按照素食和非素食分开:

Map<Boolean, List<Dish>> partitionedMenu = dishes.stream().collect(partitioningBy(Dish::isVegetarian));

当然,使用filter可以达到同样的效果:

List<Dish> vegetarianDishes = dishes.stream().filter(Dish::isVegetarian).collect(Collectors.toList());

 

分区相对来说,优势就是保存了两个副本,当你想要对一个list分类时挺有用的。同时,和groupingBy一样,partitioningBy一样有重载方法,可以指定分组value的类型。

Map<Boolean, Map<Type, List<Dish>>> vegetarianDishesByType = dishes.stream()
  .collect(partitioningBy(Dish::isVegetarian, groupingBy(Dish::getType)));
Map<Boolean, Integer> vegetarianDishesTotalCalories = dishes.stream()
  .collect(partitioningBy(Dish::isVegetarian, summingInt(Dish::getCalories)));
Map<Boolean, Dish> mostCaloricPartitionedByVegetarian = dishes.stream()
  .collect(partitioningBy(Dish::isVegetarian,
    collectingAndThen(maxBy(comparingInt(Dish::getCalories)), Optional::get)));

作为使用partitioningBy收集器的*后一个例子,我们把菜单数据模型放在一边,来看一个更加复杂也更为有趣的例子:将数组分为质数和非质数。

首先,定义个质数分区函数:

private boolean isPrime(int candidate) {
  int candidateRoot = (int) Math.sqrt((double) candidate);
  return IntStream.rangeClosed(2, candidateRoot).noneMatch(i -> candidate % i == 0);
}

 

然后找出1到100的质数和非质数

Map<Boolean, List<Integer>> partitionPrimes = IntStream.rangeClosed(2, 100).boxed()
  .collect(partitioningBy(this::isPrime));

服务器nas目录挂载

1. 重启后,如果df -h看不见挂载点,请尝试直接cd /nfsc/NAS卷名(autofs,无访问不显示);
2. 如果不能cd到挂载点里,请检查主机IP到NAS服务器2049端口是否联通, telnet NAS服务器IP 2049。
3. 如果可以telnet NAS服务器IP 2049通,进行showmount -e 加nas服务器ip 看nas卷授权情况
4. 如果端口也是通的,nas卷授权也存在,仍无法挂载成功。

NFSC挂载先查询

[appdeploy@CNSZ22PL0215:/]$df -h |grep ‘HRSS’
172.16.31.10:/HRSS_GPS_SIT
976M  2.0M  907M   1% /nfsc/HRSS_GPS

当前能访问的NFSC
[appdeploy@DockerHost_10_203_225_26 ~]$showmount -e 172.16.31.10 | grep ‘HRSS’
/HRSS_GPS_STG                 10.202.154.201,10.202.154.200
/HRSS_GPS_SIT                 10.202.74.201,10.202.74.200

10张图带你深入理解Docker容器和镜像

这篇文章希望能够帮助读者深入理解Docker的命令,还有容器(container)和镜像(image)之间的区别,并深入探讨容器和运行中的容器之间的区别。

当我对Docker技术还是一知半解的时候,我发现理解Docker的命令非常困难。于是,我花了几周的时间来学习Docker的工作原理,更确切地说,是关于Docker统一文件系统(the union file system)的知识,然后回过头来再看Docker的命令,一切变得顺理成章,简单*了。

题外话:就我个人而言,掌握一门技术并合理使用它的*好办法就是深入理解这项技术背后的工作原理。通常情况下,一项新技术的诞生常常会伴随着媒体的大肆宣传和炒作,这使得用户很难看清技术的本质。更确切地说,新技术总是会发明一些新的术语或者隐喻词来帮助宣传,这在初期是非常有帮助的,但是这给技术的原理蒙上了一层砂纸,不利于用户在后期掌握技术的真谛。

Git就是一个很好的例子。我之前不能够很好的使用Git,于是我花了一段时间去学习Git的原理,直到这时,我才真正明白了Git的用法。我坚信只有真正理解Git内部原理的人才能够掌握这个工具。

Image Definition

镜像(Image)就是一堆只读层(read-only layer)的统一视角,也许这个定义有些难以理解,下面的这张图能够帮助读者理解镜像的定义。

从左边我们看到了多个只读层,它们重叠在一起。除了*下面一层,其它层都会有一个指针指向下一层。这些层是Docker内部的实现细节,并且能够在主机(译者注:运行Docker的机器)的文件系统上访问到。统一文件系统(union file system)技术能够将不同的层整合成一个文件系统,为这些层提供了一个统一的视角,这样就隐藏了多层的存在,在用户的角度看来,只存在一个文件系统。我们可以在图片的右边看到这个视角的形式。

你可以在你的主机文件系统上找到有关这些层的文件。需要注意的是,在一个运行中的容器内部,这些层是不可见的。在我的主机上,我发现它们存在于/var/lib/docker/aufs目录下。


sudo tree -L 1 /var/lib/docker/

/var/lib/docker/├── aufs├── containers├── graph├── init├── linkgraph.db├── repositories-aufs├── tmp├── trust└── volumes7 directories, 2 files

 

Container Definition

容器(container)的定义和镜像(image)几乎一模一样,也是一堆层的统一视角,唯一区别在于容器的*上面那一层是可读可写的。

细心的读者可能会发现,容器的定义并没有提及容器是否在运行,没错,这是故意的。正是这个发现帮助我理解了很多困惑。

要点:容器 = 镜像 + 读写层。并且容器的定义并没有提及是否要运行容器。

接下来,我们将会讨论运行态容器。

Running Container Definition

一个运行态容器(running container)被定义为一个可读写的统一文件系统加上隔离的进程空间和包含其中的进程。下面这张图片展示了一个运行中的容器。

正是文件系统隔离技术使得Docker成为了一个前途无量的技术。一个容器中的进程可能会对文件进行修改、删除、创建,这些改变都将作用于可读写层(read-write layer)。下面这张图展示了这个行为。

我们可以通过运行以下命令来验证我们上面所说的:

docker run ubuntu touch happiness.txt

即便是这个ubuntu容器不再运行,我们依旧能够在主机的文件系统上找到这个新文件。


find / -name happiness.txt

/var/lib/docker/aufs/diff/860a7b…889/happiness.txt

 

Image Layer Definition

为了将零星的数据整合起来,我们提出了镜像层(image layer)这个概念。下面的这张图描述了一个镜像层,通过图片我们能够发现一个层并不仅仅包含文件系统的改变,它还能包含了其他重要信息。

元数据(metadata)就是关于这个层的额外信息,它不仅能够让Docker获取运行和构建时的信息,还包括父层的层次信息。需要注意,只读层和读写层都包含元数据。

除此之外,每一层都包括了一个指向父层的指针。如果一个层没有这个指针,说明它处于*底层。

Metadata Location:
我发现在我自己的主机上,镜像层(image layer)的元数据被保存在名为”json”的文件中,比如说:

/var/lib/docker/graph/e809f156dc985.../json

e809f156dc985…就是这层的id

一个容器的元数据好像是被分成了很多文件,但或多或少能够在/var/lib/docker/containers/<id>目录下找到,<id>就是一个可读层的id。这个目录下的文件大多是运行时的数据,比如说网络,日志等等。

全局理解(Tying It All Together)

现在,让我们结合上面提到的实现细节来理解Docker的命令。
docker create <image-id>

docker create 命令为指定的镜像(image)添加了一个可读写层,构成了一个新的容器。注意,这个容器并没有运行。

 

docker start <container-id>

Docker start命令为容器文件系统创建了一个进程隔离空间。注意,每一个容器只能够有一个进程隔离空间。
docker run <image-id>

看到这个命令,读者通常会有一个疑问:docker start 和 docker run命令有什么区别。

从图片可以看出,docker run 命令先是利用镜像创建了一个容器,然后运行这个容器。这个命令非常的方便,并且隐藏了两个命令的细节,但从另一方面来看,这容易让用户产生误解。

题外话:继续我们之前有关于Git的话题,我认为docker run命令类似于git pull命令。git pull命令就是git fetch 和 git merge两个命令的组合,同样的,docker run就是docker create和docker start两个命令的组合。
docker ps

docker ps 命令会列出所有运行中的容器。这隐藏了非运行态容器的存在,如果想要找出这些容器,我们需要使用下面这个命令。
docker ps –a

docker ps –a命令会列出所有的容器,不管是运行的,还是停止的。
docker images

docker images命令会列出了所有顶层(top-level)镜像。实际上,在这里我们没有办法区分一个镜像和一个只读层,所以我们提出了top-level镜像。只有创建容器时使用的镜像或者是直接pull下来的镜像能被称为顶层(top-level)镜像,并且每一个顶层镜像下面都隐藏了多个镜像层。
docker images –a

docker images –a命令列出了所有的镜像,也可以说是列出了所有的可读层。如果你想要查看某一个image-id下的所有层,可以使用docker history来查看。
docker stop <container-id>

docker stop命令会向运行中的容器发送一个SIGTERM的信号,然后停止所有的进程。
docker kill <container-id>

docker kill 命令向所有运行在容器中的进程发送了一个不友好的SIGKILL信号。
docker pause <container-id>

docker stop和docker kill命令会发送UNIX的信号给运行中的进程,docker pause命令则不一样,它利用了cgroups的特性将运行中的进程空间暂停。具体的内部原理你可以在这里找到:https://www.kernel.org/doc/Doc … m.txt,但是这种方式的不足之处在于发送一个SIGTSTP信号对于进程来说不够简单易懂,以至于不能够让所有进程暂停。
docker rm <container-id>

docker rm命令会移除构成容器的可读写层。注意,这个命令只能对非运行态容器执行。
docker rmi <image-id>

docker rmi 命令会移除构成镜像的一个只读层。你只能够使用docker rmi来移除*顶层(top level layer)(也可以说是镜像),你也可以使用-f参数来强制删除中间的只读层。
docker commit <container-id>

docker commit命令将容器的可读写层转换为一个只读层,这样就把一个容器转换成了不可变的镜像。

 

docker build

docker build命令非常有趣,它会反复的执行多个命令。

我们从上图可以看到,build命令根据Dockerfile文件中的FROM指令获取到镜像,然后重复地1)run(create和start)、2)修改、3)commit。在循环中的每一步都会生成一个新的层,因此许多新的层会被创建。
docker exec <running-container-id>

docker exec 命令会在运行中的容器执行一个新进程。
docker inspect <container-id> or <image-id>

docker inspect命令会提取出容器或者镜像*顶层的元数据。
docker save <image-id>

docker save命令会创建一个镜像的压缩文件,这个文件能够在另外一个主机的Docker上使用。和export命令不同,这个命令为每一个层都保存了它们的元数据。这个命令只能对镜像生效。
docker export <container-id>

docker export命令创建一个tar文件,并且移除了元数据和不必要的层,将多个层整合成了一个层,只保存了当前统一视角看到的内容(译者注:expoxt后的容器再import到Docker中,通过docker images –tree命令只能看到一个镜像;而save后的镜像则不同,它能够看到这个镜像的历史镜像)。
docker history <image-id>

docker history命令递归地输出指定镜像的历史镜像。

不错的MySQL重要知识点

转载自:https://mp.weixin.qq.com/s/S9jiO_e-_CKRgNnzAU5Z0Q

标题有点标题党的意思,但希望你在看了文章之后不会有这个想法——这篇文章是作者对之前总结的 MySQL 知识点做了完善后的产物,可以用来回顾MySQL基础知识以及备战MySQL常见面试问题。

 

什么是MySQL?

 

MySQL 是一种关系型数据库,在Java企业级开发中非常常用,因为 MySQL 是开源免费的,并且方便扩展。阿里巴巴数据库系统也大量用到了 MySQL,因此它的稳定性是有保障的。MySQL是开放源代码的,因此任何人都可以在 GPL(General Public License) 的许可下下载并根据个性化的需要对其进行修改。MySQL的默认端口号是3306

 

事务相关

 

什么是事务?

 

事务是逻辑上的一组操作,要么都执行,要么都不执行。

 

事务*经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。

事物的四大特性(ACID)介绍一下?

  • 原子性: 事务是*小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
  • 一致性: 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
  • 隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
  • 持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

并发事务带来哪些问题?

 

在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题:

 

  • 脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
  • 失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在*个事务中修改了这个数据后,第二个事务也修改了这个数据。这样*个事务内的修改结果就被丢失,因此称为丢失修改。例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,*终结果A=19,事务1的修改被丢失。
  • 不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在*个事务中的两次读数据之间,由于第二个事务的修改导致*个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
  • 幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,*个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

 

不可重复度和幻读区别:

 

不可重复读的重点是修改,幻读的重点在于新增或者删除。

 

例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。

 

例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。

事务隔离级别有哪些?MySQL的默认隔离级别是?

 

SQL 标准定义了四个隔离级别:

  • READ-UNCOMMITTED(读取未提交): *低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
  • READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
  • REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
  • SERIALIZABLE(可串行化): *高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。

 

 

隔离级别 脏读 不可重复读 幻影读
READ-UNCOMMITTED
READ-COMMITTED ×
REPEATABLE-READ × ×
SERIALIZABLE × × ×

 

MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)。我们可以通过SELECT @@tx_isolation;命令来查看

mysqlSELECT @@tx_isolation;+-----------------+| @@tx_isolation  |+-----------------+| REPEATABLE-READ |+-----------------+

这里需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 REPEATABLE-READ(可重读)事务隔离级别下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要求,即达到了 SQL标准的SERIALIZABLE(可串行化)隔离级别。

 

因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 REPEATABLE-READ(可重读)并不会有任何性能损失。

 

InnoDB 存储引擎在 分布式事务 的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。

 

索引相关

为什么索引能提高查询速度

以下内容整理自:《数据库两大神器【索引和锁】》作者 :Java3y

先从 MySQL 的基本存储结构说起

MySQL的基本存储结构是页 (记录都存在页里边) :

  • 各个数据页可以组成一个双向链表
  • 每个数据页中的记录又可以组成一个单向链表
- 每个数据页都会为存储在它里边儿的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录- 以其他列(非主键)作为搜索条件:只能从*小记录开始依次遍历单链表中的每条记录。

所以说,如果我们写select * from user where indexname = ‘xxx’这样没有进行任何优化的sql语句,默认会这样做:

  1. 定位到记录所在的页:需要遍历双向链表,找到所在的页
  2. 从所在的页内中查找相应的记录:由于不是根据主键查询,只能遍历所在页的单链表了

很明显,在数据量很大的情况下这样查找会很慢!这样的时间复杂度为O(n)。

 

索引做了些什么可以让我们查询加快速度呢?其实就是将无序的数据变成有序(相对):

 

 

要找到id为8的记录简要步骤:

很明显的是:没有用索引我们是需要遍历双向链表来定位对应的页,现在通过 “目录” 就可以很快地定位到对应的页上了!(二分查找,时间复杂度近似为O(logn))

其实底层结构就是B+树,B+树作为树的一种实现,能够让我们很快地查找出对应的记录。

以下内容整理自:《Java工程师修炼之道》

什么是*左前缀原则?

 

MySQL中的索引可以以一定顺序引用多列,这种索引叫作联合索引。如User表的name和city加联合索引就是(name,city),而*左前缀原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列或几列,则此列就可以被用到。如下:

select * from user where name=xx and city=xx ; //可以命中索引select * from user where name=xx ; // 可以命中索引select * from user where city=xx ; // 无法命中索引

这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如 city= xx and name =xx,那么现在的查询引擎会自动优化为匹配联合索引的顺序,这样是能够命中索引的。

 

由于*左前缀原则,在创建联合索引时,索引字段的顺序需要考虑字段值去重之后的个数,较多的放前面。ORDER BY子句也遵循此规则。

注意避免冗余索引

 

冗余索引指的是索引的功能相同,能够命中就肯定能命中 ,那么 就是冗余索引如(name,city )和(name )这两个索引就是冗余索引,能够命中后者的查询肯定是能够命中前者的 在大多数情况下,都应该尽量扩展已有的索引而不是创建新索引。

 

MySQLS.7 版本后,可以通过查询 sys 库的 schema_redundant_indexes 表来查看冗余索引

Mysql如何为表字段添加索引?

 

1.添加PRIMARY KEY(主键索引)

ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` )

2.添加UNIQUE(唯一索引)

ALTER TABLE `table_name` ADD UNIQUE ( `column` )

3.添加INDEX(普通索引)

ALTER TABLE `table_name` ADD INDEX index_name ( `column` )

4.添加FULLTEXT(全文索引)

ALTER TABLE `table_name` ADD FULLTEXT ( `column`)

5.添加多列索引

ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` )

 

存储引擎

一些常用命令

 

查看MySQL提供的所有存储引擎

mysql> show engines;

 

从上图我们可以查看出 MySQL 当前默认的存储引擎是InnoDB,并且在5.7版本所有的存储引擎中只有 InnoDB 是事务性存储引擎,也就是说只有 InnoDB 支持事务。

 

查看MySQL当前默认的存储引擎

 

我们也可以通过下面的命令查看默认的存储引擎。

mysql> show variables like '%storage_engine%';

 

查看表的存储引擎

show table status like "table_name" ;

MyISAM和InnoDB区别

 

MyISAM是MySQL的默认数据库引擎(5.5版之前)。虽然性能*佳,而且提供了大量的特性,包括全文索引、压缩、空间函数等,但MyISAM不支持事务和行级锁,而且*大的缺陷就是崩溃后无法安全恢复。不过,5.5版本之后,MySQL引入了InnoDB(事务性数据库引擎),MySQL 5.5版本后默认的存储引擎为InnoDB。

 

大多数时候我们使用的都是 InnoDB 存储引擎,但是在某些情况下使用 MyISAM 也是合适的比如读密集的情况下。(如果你不介意 MyISAM 崩溃回复问题的话)。

两者的对比:

  1. 是否支持行级锁 : MyISAM 只有表级锁(table-level locking),而InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁。
  2. 是否支持事务和崩溃后的安全恢复:MyISAM 强调的是性能,每次查询具有原子性,其执行比InnoDB类型更快,但是不提供事务支持。但是InnoDB 提供事务支持事务,外部键等高级数据库功能。具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
  3. 是否支持外键: MyISAM不支持,而InnoDB支持。
  4. 是否支持MVCC :仅 InnoDB 支持。应对高并发事务, MVCC比单纯的加锁更高效;MVCC只在 READ COMMITTED 和 REPEATABLE READ 两个隔离级别下工作;MVCC可以使用 乐观(optimistic)锁 和 悲观(pessimistic)锁来实现;各数据库中MVCC实现并不统一。
  5. ……

 

《MySQL高性能》上面有一句话这样写到:

不要轻易相信“MyISAM比InnoDB快”之类的经验之谈,这个结论往往不是*对的。在很多我们已知场景中,InnoDB的速度都可以让MyISAM望尘莫及,尤其是用到了聚簇索引,或者需要访问的数据都可以放入内存的应用。

一般情况下我们选择 InnoDB 都是没有问题的,但是某事情况下你并不在乎可扩展能力和并发能力,也不需要事务支持,也不在乎崩溃后的安全恢复问题的话,选择MyISAM也是一个不错的选择。但是一般情况下,我们都是需要考虑到这些问题的。

 

 

乐观锁与悲观锁的区别

悲观锁

 

总是假设*坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronizedReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁

 

总是假设*好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

两种锁的使用场景

 

从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

 

乐观锁常见的两种实现方式

乐观锁一般会使用版本号机制或CAS算法实现。

1. 版本号机制

 

一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。

 

举一个简单的例子: 假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。

 

  1. 操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。
  2. 在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20 )。
  3. 操作员 A 完成了修改工作,将数据版本号加一( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
  4. 操作员 B 完成了操作,也将版本号加一( version=2 )试图向数据库提交数据( balance=$80 ),但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,数据库记录当前版本也为 2 ,不满足 “ 提交版本必须大于记录当前版本才能执行更新 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。

 

这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。

2. CAS算法

 

compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数

  • 需要读写的内存值 V
  • 进行比较的值 A
  • 拟写入的新值 B

 

当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试

 

乐观锁的缺点

ABA 问题是乐观锁一个常见的问题

1 ABA 问题

 

如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 “ABA”问题。

 

JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

2 循环时间长开销大

 

自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,*它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

3 只能保证一个共享变量的原子操作

 

CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。

 

锁机制与InnoDB锁算法

 

MyISAM和InnoDB存储引擎使用的锁:

  • MyISAM 采用表级锁(table-level locking)。
  • InnoDB 支持行级锁(row-level locking)和表级锁,默认为行级锁

 

表级锁和行级锁对比:

  • 表级锁: Mysql中锁定 粒度*大 的一种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度*大,触发锁冲突的概率*高,并发度*低,MyISAM和 InnoDB引擎都支持表级锁。
  • 行级锁: Mysql中锁定 粒度*小 的一种锁,只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度*小,并发度高,但加锁的开销也*大,加锁慢,会出现死锁。

 

InnoDB存储引擎的锁的算法有三种:

  • Record lock:单个行记录上的锁
  • Gap lock:间隙锁,锁定一个范围,不包括记录本身
  • Next-key lock:record+gap 锁定一个范围,包含记录本身

 

相关知识点:

  • innodb对于行的查询使用next-key lock
  • Next-locking keying为了解决Phantom Problem幻读问题
  • 当查询的索引含有唯一属性时,将next-key lock降级为record key
  • Gap锁设计的目的是为了阻止多个事务将记录插入到同一范围内,而这会导致幻读问题的产生
  • 有两种方式显式关闭gap锁:(除了外键约束和唯一性检查外,其余情况仅使用record lock) A. 将事务隔离级别设置为RC B. 将参数innodb_locks_unsafe_for_binlog设置为1

大表优化

 

当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:

 

1. 限定数据的范围

务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内;

 

2. 读/写分离

经典的数据库拆分方案,主库负责写,从库负责读;

 

3. 垂直分区

根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。

简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。 如下图所示,这样来说大家应该就更容易理解了。

  • 垂直拆分的优点: 可以使得列数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。
  • 垂直拆分的缺点: 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂;

4. 水平分区

 

保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。水平拆分可以支撑非常大的数据量。

 

水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。

水平拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 水平拆分*好分库 。

 

水平拆分能够 支持非常大的数据量存储,应用端改造也少,但 分片事务难以解决 ,跨节点Join性能较差,逻辑复杂。《Java工程师修炼之道》的作者推荐 尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度 ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。

 

下面补充一下数据库分片的两种常见方案:

 

  • 客户端代理: 分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。 当当网的 Sharding-JDBC 、阿里的TDDL是两种比较常用的实现。
  • 中间件代理: 在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。 我们现在谈的 Mycat 、360的Atlas、网易的DDB等等都是这种架构的实现。

基础教程

基础教程

 

Python 是一种解释型、面向对象、动态数据类型的高级程序设计语言。
Python 由 Guido van Rossum 于 1989 年底发明,*个公开发行版发行于 1991 年。
像 Perl 语言一样, Python 源代码同样遵循 GPL(GNU General Public License) 协议。
官方宣布,2020 年 1 月 1 日, 停止 Python 2 的更新。
Python 2.7 被确定为*后一个 Python 2.x 版本。

在继续本教程之前,你应该了解一些基本的计算机编程术语。如果你学习过 PHP,ASP 等编程语言,将有助于你更快的了解 Python 编程。

执行Python程序
对于大多数程序语言,*个入门编程代码便是 “Hello World!”,以下代码为使用 Python 输出 “Hello World!”:

#!/usr/bin/python

print(“Hello, World!”)

Python 3.0+ 版本已经把 print 作为一个内置函数,输出 “Hello World!” 代码如下:

#!/usr/bin/python3

print(“Hello, World!”)

NFS与NAS

NAS是存储的一种方式 NAS可以是一个设备.也可以是一种存储网络的架构.

而NFS是一种协议.

两者有本质上的区别

 

NFS:
      NFS是Network File System的简写,即网络文件系统.
  网络文件系统是FreeBSD支持的文件系统中的一种,也被称为NFS. NFS允许一个系统在网络上与他人共享目录和文件。通过使用NFS,用户和程序可以像访问本地文件一样访问远端系统上的文件。
  NFS、DAS、NAS、SAN、iSCSI飞客数据恢复中心提供

以下是NFS*显而易见的好处:

1.本地工作站使用更少的磁盘空间,因为通常的数据可以存放在一台机器上而且可以通过网络访问到。

2.用户不必在每个网络上机器里头都有一个home目录。Home目录 可以被放在NFS服务器上并且在网络上处处可用。

3.诸如软驱,CDROM,和 Zip(是指一种高储存密度的磁盘驱动器与磁盘)之类的存储设备可以在网络上面被别的机器使用。这可以减少整个网络上的可移动介质设备的数量。

DAS:
    开放系统的直连式存储(Direct-Attached Storage,简称DAS)已经有近四十年的使用历史,随着用户数据的不断增长,尤其是数百GB以上时,其在备份、恢复、扩展、灾备等方面的问题变得日益困扰系统管理员。
    直连式存储依赖服务器主机操作系统进行数据的IO读写和存储维护管理,数据备份和恢复要求占用服务器主机资源(包括CPU、系统IO等),数据流需要回流主机再到服务器连接着的磁带机(库),数据备份通常占用服务器主机资源20-30%,因此许多企业用户的日常数据备份常常在深夜或业务系统不繁忙时进行,以免影响正常业务系统的运行。直连式存储的数据量越大,备份和恢复的时间就越长,对服务器硬件的依赖性和影响就越大。

直连式存储与服务器主机之间的连接通道通常采用SCSI连接,带宽为10MB/s、20MB/s、40MB/s、80MB/s等,随着服务器CPU的处理能力越来越强,存储硬盘空间越来越大,阵列的硬盘数量越来越多,SCSI通道将会成为IO瓶颈;服务器主机SCSI ID资源有限,能够建立的SCSI通道连接有限。

  无论直连式存储还是服务器主机的扩展,从一台服务器扩展为多台服务器组成的群集(Cluster),或存储阵列容量的扩展,都会造成业务系统的停机,从而给企业带来经济损失,对于银行、电信、传媒等行业7×24小时服务的关键业务系统,这是不可接受的。并且直连式存储或服务器主机的升级扩展,只能由原设备厂商提供,往往受原设备厂商限制。
 
NAS:
    NAS(Network Attached Storage:网络附属存储)是一种将分布、独立的数据整合为大型、集中化管理的数据中心,以便于对不同主机和应用服务器进行访问的技术。按字面简单说就是连接在网络上, 具备资料存储功能的装置,因此也称为“网络存储器”。它是一种专用数据存储服务器。它以数据为中心,将存储设备与服务器彻底分离,集中管理数据,从而释放带宽、提高性能、降低总拥有成本、保护投资。其成本远远低于使用服务器存储,而效率却远远高于后者。 目前国际著名的NAS企业有Netapp、EMC、OUO等。国内尚无有竞争力的NAS企业。

               NAS被定义为一种特殊的专用数据存储服务器,包括存储器件(例如磁盘阵列、CD/DVD驱动器、磁带驱动器或可移动的存储介质)和内嵌系统软件,可提供跨平台文件共享功能。NAS通常在一个LAN上占有自己的节点,无需应用服务器的干预,允许用户在网络上存取数据,在这种配置中,NAS集中管理和处理网络上的所有数据,将负载从应用或企业服务器上卸载下来,有效降低总拥有成本,保护用户投资。

  NAS本身能够支持多种协议(如NFS、CIFS、FTP、HTTP等),而且能够支持各种操作系统。通过任何一台工作站,采用IE或Netscape浏览器就可以对NAS设备进行直观方便的管理。
SAN:
      SAN全称叫做存储域网络(Storage Area Network)。
       SAN是一个专有的、集中管理的信息基础结构,它支持服务器和存储之间任意的点到点的连接,SAN集中体现了功

能分拆的思想,提高了系统的灵活性和数据的安全性。SAN以数据存储为中心,采用可伸缩的网络拓扑结构,通过具有较高传输速率的光通道连接方式,提供SAN内部任意节点之间的多路可选择的数据交换,并且将数据存储管理集中在相对独立的存储区域网内。在多种光通道传输协议逐渐走向标准化并且跨平台群集文件系统投入使用后,SAN*终将实现在多种操作系统下,*大限度的数据共享和数据优化管理,以及系统的无缝扩充。SAN是独立出一个数据存储网络,网络内部的数据传输率很快,但操作系统仍停留在服务器端,用户不是在直接访问SAN的网络,因此这就造成SAN在异构环境下不能实现文件共享。为了很好的理解SAN,我们可以通过图来看其结构。

我们可以看到,SAN的特点是将数据的存储移到了后端,采用了一个专门的系统来完成,并进行了RAID数据保护。

iSCSI:
    iSCSI:Internet 小型计算机系统接口 (iSCSI:Internet Small Computer System Interface)。
    iSCSI技术是一种由IBM公司研究开发的,是一个供硬件设备使用的可以在IP协议的上层运行的SCSI指令集,这种指令集合可以实现在IP网络上运行SCSI协议,使其能够在诸如高速千兆以太网上进行路由选择。iSCSI技术是一种新储存技术,该技术是将现有SCSI接口与以太网络(Ethernet)技术结合,使服务器可与使用IP网络的储存装置互相交换资料。
SAN 和NAS的区别:

SAN是一种网络,NAS产品是一个专有文件服务器或一个只读文件访问设备。

SAN是在服务器和存储器之间用作I/ O路径 的专用网络。

SAN包括面向块(SCIS)和面向文件(NAS)的存储产品。

NAS产品能通过SAN连接到存储设备

NAS的外观

NAS是功能单一的精简型电脑,因此在架构上不像个人电脑那么复杂,像键盘、鼠标、荧幕、音效卡、喇叭、扩充漕、各式连接口等都不需要;在外观上就像家电产品,只需电源与简单的控制钮。NAS在架构上与个人电脑相似,但因功能单纯,可移除许多不必要的连接器、控制晶片、电子回路,如键盘、鼠标、USB、VGA等。

NAS数据存储的优点

*,NAS适用于那些需要通过网络将文件数据传送到多台客户机上的用户。NAS设备在数据必须长距离传送的环境中可以很好地发挥作用。

第二,NAS设备非常易于部署。可以使NAS主机、客户机和其他设备广泛分布在整个企业的网络环境中。NAS可以提供可靠的文件级数据整合,因为文件锁定是由设备自身来处理的。

第三,NAS应用于高效的文件共享任务中,不同的主机与客户端通过文件共享协定存取NAS上的资料,实现文件共享功能,例如UNIX中的NFS和Windows NT中的CIFS,其中基于网络的文件级锁定提供了高级并发访问保护的功能。

中文编码

中文编码

订阅专栏
前面章节中我们已经学会了如何用 Python 输出 “Hello, World!”,英文没有问题,但是如果你输出中文字符 “你好,世界” 就有可能会碰到中文编码问题。

Python 文件中如果未指定编码,在执行过程会出现报错:

#!/usr/bin/python

print (“你好,世界”)

以上程序执行输出结果为:

File “test.py”, line 2
SyntaxError: Non-ASCII character ‘\xe4’ in file test.py on line 2, but no encoding declared; see http://www.python.org/peps/pep-0263.html for details

Python中默认的编码格式是 ASCII 格式,在没修改编码格式时无法正确打印汉字,所以在读取中文时会报错。

解决方法为只要在文件开头加入 # — coding: UTF-8 — 或者 # coding=utf-8 就行了

注意:# coding=utf-8 的 = 号两边不要空格。

实例(Python 2.0+)
#!/usr/bin/python
# -*- coding: UTF-8 -*-

print( “你好,世界” )

输出结果为:

你好,世界

所以如果大家在学习过程中,代码中包含中文,就需要在头部指定编码。

**注意:**Python3.X 源码文件默认使用utf-8编码,所以可以正常解析中文,无需指定 UTF-8 编码。

**注意:**如果你使用编辑器,同时需要设置 py 文件存储的格式为 UTF-8,否则会出现类似以下错误信息:

SyntaxError: (unicode error) ‘utf-8’ codec can’t decode byte 0xc4 in position 0:
invalid continuation byte

Pycharm 设置步骤:

进入 file > Settings,在输入框搜索 encoding。
找到 Editor > File encodings,将 IDE Encoding 和 Project Encoding 设置为utf-8。

Python语言及其特点简介

Python语言及其特点简介

订阅专栏
虽然软件产业的历史相对于人类历史只是白驹过隙,但世界上却存在非常多的编程语言,「Python」 就是其中之一。

Python 语言算得上一门“古老”的编程语言,Python 流行这么久,必然有它的独到之处,本节我们简单介绍 Python 的相关情况。

Python 简史
Python 由 Guido van Rossum 于 1989 年年底出于某种娱乐目的而开发, Python 语言是基于 ABC 教学语言的,而 ABC 这种语言非常强大,是专门为非专业程序员设计的。但 ABC 语言并没有获得广泛的应用, Guido 认为是非开放造成的。

Python 的“出身”部分影响了它的流行,Python 上手非常简单,它的语法非常像自然语言,对非软件专业人士而言,选择 Python 的成本*低,因此某些医学甚至艺术专业背景的人,往往会选择 Python 作为编程语言。

Guido 在 Python 中避免了 ABC 不够开放的劣势,Guido 加强了 Python 和其他语言如 C、C++ 和 Java 的结合性。此外,Python 还实现了许多 ABC 中未曾实现的东西,这些因素大大提高了 Python 的流行程度。

2008 年 12 月,Python 发布了 3.0 版本(也常常被称为 Python 3000,或简称 Py3k)。Python 3.0 是一次重大的升级,为了避免引入历史包袱,Python 3.0 没有考虑与 Python 2.x 的兼容。这样导致很长时间以来,Python 2.x 的用户不愿意升级到 Python 3.0,这种割裂一度影响了 Python 的应用。

毕竟大势不可抵挡,开发者逐渐发现 Python 3.x 更简洁、更方便。现在,*大部分开发者已经从 Python 2.x 转移到 Python 3.x,但有些早期的 Python 程序可能依然使用了 Python 2.x 语法。

2009 年 6 月,Python 发布了 3.1 版本。
2011 年 2 月,Python 发布了 3.2 版本。
2012 年 9 月,Python 发布了 3.3 版本。
2014 年 3 月,Python 发布了 3.4 版本。
2015 年 9 月,Python 发布了 3.5 版本。
2016 年 12 月,Python 发布了 3.6 版本。

本教程将以 Python 3.x 来介绍 Python 编程,也会简单对比 Python2.x 与 Python 3.x 的语法差异。

目前,由于大数据、人工智能(AI)的流行,Python 变得比以往更加流行。在*新的 TIOBE 编程语言排行榜上, Python 己经迅速上升到第 4 位,仅次于 Java、C、C++。

Java 占据了世界上*大部分电商、全融、通信等服务端应用开发,而 C、C++ 占据了世界上*大部分贴近操作系统的硬件编程,这三门语言的地位太难动摇了。

Python 的特点
Python 是一种面向对象、解释型、弱类型的脚本语言,它也是一种功能强大而完善的通用型语言。

相比其他编程语言(比如 Java),Python 代码非常简单,上手非常容易。比如我们要完成某个功能,如果用 Java 需要 100 行代码,但用 Python 可能只需要 20 行代码,这是 Python 具有巨大吸引力的一大特点。

Python 的两大特色是清晰的语法和可扩展性:

Python 的语法非常清晰,它甚至不是一种格式自由的语言。例如,它要求 if 语句的下一行必须向右缩进,否则不能通过编译。
Python 的可扩展性体现为它的模块,Python 具有脚本语言中*丰富和强大的类库(这些类库被形象地称为“batteries included ,内置电池”),这些类库覆盖了文件 I/O、GUI、网络编程、数据库访问、文本操作等*大部分应用场景。
此外,Python 的社区也很发达,即使一些小众的应用场景,Python 往往也有对应的开源模块来提供解决方案。

Python 作为一门解释型的语言,它天生具有跨平台的特征,只要为平台提供了相应的 Python 解释器,Python 就可以在该平台上运行。

解释型语言几乎天然是跨平台的。

Python 自然也具有解释型语言的一些弱点:

速度慢:Python 程序比 Java、C、C++ 等程序的运行效率都要慢。
源代码加密困难:不像编译型语言的源程序会被编译成目标程序,Python 直接运行源程序,因此对源代码加密比较困难。
上面两个问题其实不是什么大问题,关于*个问题,由于目前计算机的硬件速度越来越快,软件工程往往更关注开发过程的效率和可靠性,而不是软件的运行效率;至于第二个问题,则更不是问题了,现在软件行业的大势本来就是开源,就像 Java 程序同样很容易反编译,但丝毫不会影响它的流行。

友情链接: SITEMAP | 旋风加速器官网 | 旋风软件中心 | textarea | 黑洞加速器 | jiaohess | 老王加速器 | 烧饼哥加速器 | 小蓝鸟 | tiktok加速器 | 旋风加速度器 | 旋风加速 | quickq加速器 | 飞驰加速器 | 飞鸟加速器 | 狗急加速器 | hammer加速器 | trafficace | 原子加速器 | 葫芦加速器 | 麦旋风 | 油管加速器 | anycastly | INS加速器 | INS加速器免费版 | 免费vqn加速外网 | 旋风加速器 | 快橙加速器 | 啊哈加速器 | 迷雾通 | 优途加速器 | 海外播 | 坚果加速器 | 海外vqn加速 | 蘑菇加速器 | 毛豆加速器 | 接码平台 | 接码S | 西柚加速器 | 快柠檬加速器 | 黑洞加速 | falemon | 快橙加速器 | anycast加速器 | ibaidu | moneytreeblog | 坚果加速器 | 派币加速器 | 飞鸟加速器 | 毛豆APP | PIKPAK | 安卓vqn免费 | 一元机场加速器 | 一元机场 | 老王加速器 | 黑洞加速器 | 白石山 | 小牛加速器 | 黑洞加速 | 迷雾通官网 | 迷雾通 | 迷雾通加速器 | 十大免费加速神器 | 猎豹加速器 | 蚂蚁加速器 | 坚果加速器 | 黑洞加速 | 银河加速器 | 猎豹加速器 | 海鸥加速器 | 芒果加速器 | 小牛加速器 | 极光加速器 | 黑洞加速 | movabletype中文网 | 猎豹加速器官网 | 烧饼哥加速器官网 | 旋风加速器度器 | 哔咔漫画 | PicACG | 雷霆加速