JavaSE-高级特性

JavaSE-高级特性

一、单元测试

使用Junit框架

二、反射

反射可以破坏封装性,得到一个类的所有成分,最重要的途径是用来参与Java框架制作。
获取Class对象的三种方式

  • Class c1 = 类名.class
  • 调用Class提供方法: public static Class forName(String package);
  • Object提供的方法: public Class getClass(); Class c3 = 对象.getClass();

反射案例

需求:对于任意对象,将对象的字段名和对应的值保存到文件中去
假设我们有Teacher类和Student类,那么代码如下

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Demo01 {

private String dirPath = "src/main/resources/P13/";

@Test
public void test01() {

Student s1 = new Student("淇",22,'女',167.56,"swim");
Teacher t1 = new Teacher("柳",5433.2);

work(s1, dirPath+"s.txt");
work(t1, dirPath+"t.txt");
}

public void work(Object object, String filePath) {

//1.create fileIO
try (
// FileWriter fileWriter = new FileWriter(filePath);
PrintWriter printWriter = new PrintWriter(filePath);
) {

//2.get Object fields info
Class c = object.getClass();
Field[] declaredFields = c.getDeclaredFields();
printWriter.println("--------"+c.getSimpleName()+"--------");
for (Field f : declaredFields) {

f.setAccessible(true);
printWriter.println(f.getName()+" --> "+f.get(object));
}
} catch (Exception e) {

throw new RuntimeException(e);
}
}
}

三、注解

我的注解一如下

language-java
1
2
3
4
5
6
7
public @interface MyAnnotation {

String aaa();
boolean bbb() default true;
String[] ccc();
}

注解二如下

language-java
1
2
3
4
public @interface MyAnnotation2 {

String value();
}

注解使用如下

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.kmo.d08_Annotation.Test01;

import org.junit.Test;

@MyAnnotation(aaa="牛魔王", ccc={
"Python","Java"})
public class MyTest01 {

@MyAnnotation(aaa="铁山",bbb=false,ccc={
"C++","C"})
public void test1(){

}

@MyAnnotation2("孙悟空")
public void test2(){

}
}

解析注解案例

需求:
定义注解MyTest4。
定义一个Demo类,在类中定义一个test1方法,并在该类和其方法上使用MyTest4注解。
定义AnnotationTest3测试类,解析Demo类中的全部注解。

这是一个注解

language-java
1
2
3
4
5
6
7
8
9
@Target({
ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest4 {

String value();
double aaa() default 100;
String[] bbb();
}

这是Demo类

language-java
1
2
3
4
5
6
7
8
9
10
11
12
@MyTest4(value = "vv",aaa = 99.9, bbb={
"b","bb","bbb"})
public class Demo {


@MyTest4(value = "我是至尊宝",aaa = 100.9, bbb = {
"小淇","爱你"})
public void test01(){


}
}

这是解析类,解析Demo类上的注解以及Demo类方法的注解

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class AnnotationTest3 {


@Test
public void test01(){

Class c = Demo.class;
if (c.isAnnotationPresent(MyTest4.class)){

MyTest4 myTest4 = (MyTest4) c.getDeclaredAnnotation(MyTest4.class);
System.out.println(myTest4.value());
System.out.println(myTest4.aaa());
System.out.println(Arrays.toString(myTest4.bbb()));
}
}
@Test
public void test02() throws Exception {

Class c = Demo.class;
Method method = c.getDeclaredMethod("test01");
if (method.isAnnotationPresent(MyTest4.class)){

MyTest4 myTest4 = (MyTest4) method.getDeclaredAnnotation(MyTest4.class);
System.out.println(myTest4.value());
System.out.println(myTest4.aaa());
System.out.println(Arrays.toString(myTest4.bbb()));
}
}
}

注解案例-简易Junit

这是一个注解

language-java
1
2
3
4
5
6
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {


}

这是运行代码

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class Demo {

@MyTest()
public void test01(){

System.out.println("===Test01====");
}

public void test02(){

System.out.println("===Test02====");
}

@MyTest
public void test03(){

System.out.println("===Test03====");
}
public void test04(){

System.out.println("===Test04====");
}

public static void main(String[] args) throws Exception {

Demo demo = new Demo();
Class c = Demo.class;
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {

if (method.isAnnotationPresent(MyTest.class)){

method.invoke(demo);
}
}
}
}

四、动态代理

相当于把相同的地方集合到一起了,只要写一遍,且灵活。

动态代理案例

需求:对一个类的所有方法前后都加上计时,当类的方法被调用时,输出时间。

一个UserService接口

language-java
1
2
3
4
5
6
public interface UserService {

void login(String loginName, String password) throws Exception;
void deleteUsers() throws Exception;
String[] selectUsers() throws Exception;
}

UserServicelmpl实现类

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class UserServiceImpl implements UserService {

@Override
public void login(String loginName, String passWord) throws Exception {

if ("admin".equals(loginName) && "123456".equals(passWord)) {

System.out.println("您登录成功,欢迎光临本系统~");
} else {

System.out.println("您登录失败,用户名或密码错误~");
}
Thread.sleep(1000);
}

@Override
public void deleteUsers() throws Exception {

System.out.println("成功删除了1万个用户~");
Thread.sleep(1500);
}

@Override
public String[] selectUsers() throws Exception {

System.out.println("查询出3个用户");
String[] names = {
"张全蛋","李二狗","牛爱花"};
Thread.sleep(500);
return names;
}
}

ProxyUtil代理类

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ProxyUtil {

public static UserService createProxy(UserService userService){

UserService userServiceProxy = (UserService) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(),
new Class[]{
UserService.class},
new InvocationHandler() {

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

if (method.getName().equals("login") || method.getName().equals("deleteUsers") || method.getName().equals("selectUsers")){

long startTime = System.currentTimeMillis();
Object res = method.invoke(userService, args);
long endTime = System.currentTimeMillis();
System.out.println(method.getName()+"方法执行耗时: " + (endTime - startTime) / 1000.0 + "s");
return res;
}else {

Object res = method.invoke(userService, args);
return res;
}
}
}
);

return userServiceProxy;
}
}

在这个代理中,被代理的类传入,Proxy.newProxyInstance中重写的invoke方法里,就是在重写被代理类的所有方法,可以理解为在调用(被代理的)类的方法时,其实是在调用这里的invoke。如果不想做更改,直接执行method.invoke就行。

运行代码

language-java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Tests {

public static void main(String[] args) throws Exception {

// UserService userService = new UserServiceImpl();
UserService userService = ProxyUtil.createProxy(new UserServiceImpl());

userService.login("admin","123456");
System.out.println("--------------------");
userService.deleteUsers();
System.out.println("--------------------");
String[] strings = userService.selectUsers();
System.out.println(Arrays.toString(strings));
System.out.println("--------------------");

}
}
JavaSE-日志文件

JavaSE-日志文件


日志接口有两类,

  • Commons Logging (JCL)
  • Simple Logging Facade for Java (SLF4J)

热门的Logback是基于SLF4J实现的。

​​

所以要使用Logback首先需要添加3个依赖,去maven仓库搜一下,用maven加载就好。

= = = = = = = = = = = = = = = = = = =
= = = = = = Maven仓库 = = = = = = =
= = = = = = = = = = = = = = = = = = =

然后我们可以通过一个logback.xml文件来配置logback一些东西,文件放在/src/main/resources下,这是一份样例。

language-xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!--
CONSOLE :表示当前的日志信息是可以输出到控制台的。
-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--输出流对象 默认 System.out 改为 System.err-->
<target>System.out</target>
<encoder>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度
%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %c [%thread] : %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
</appender>

<!-- File是输出的方向通向文件的 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
<charset>utf-8</charset>
</encoder>
<!--日志输出路径-->
<file>C:/Users/kmo/IdeaProjects/JAVA-DataSt/DataSt/src/main/resources/Log/data.log</file>
<!-- 指定日志文件拆分和压缩规则-->
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--通过指定压缩文件名称,来确定分割文件方式-->
<fileNamePattern>C:/Users/kmo/IdeaProjects/JAVA-DataSt/DataSt/src/main/resources/Log/log-%d{yyyy-MM-dd}.log%i.gz</fileNamePattern>
<!--文件拆分大小-->
<maxFileSize>1MB</maxFileSize>
</rollingPolicy>
</appender>

<!--
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR(只有大于等于这个级别的才会被记录) | ALL 和 OFF
, 默认debug
<root>可以包含零个或多个<appender-ref>元素,标识这个输出位置将会被本日志级别控制。
-->
<root level="ALL">
<!-- 注意:如果这里不配置关联打印位置,该位置将不会记录日志-->
<appender-ref ref = "CONSOLE"/>
<appender-ref ref="FILE" />
</root>
</configuration>

​接着这样在代码中引入logback日志输出器

language-java
1
2
3
4
5
6
7
8
9
10
11
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Test{

public static void main(String[] args) {

Logger logger = LoggerFactory.getLogger("");
logger.info("This a info message");
logger.warn("This a warning message");
}
}
JavaSE-IO

JavaSE-IO

IO流总体来看有四大类

  • 字节输入流
  • 字节输出流
  • 字符输入流
  • 字符输出流

IO的体系如下图

需要注意的是字节流适合完整的复制文件之类的操作,而字符流尤其适合纯文本文件。

第一行实现类,都是原始流,性能并不高。

第二行是Buffer缓冲流,自带8KB缓冲池,相当于缓存之于cpu和硬盘的感觉。

第三行是打印流,符合平常的println习惯。

第四行是数据流,适合保存读取基本类型数据,保存后的文件打开是乱码很正常。

第五行是序列流,适合保存读取对象,对象需实现Serializable接口,transient关键字可以设置某变量不参加序列化。

除了原始流,其他的都是高级流,高级流都封装了原始流,并且性能都比较好。

IO框架

Commons-IOhttps://www.w3schools.cn/apache_commons_io/同时Java自己后来也提供了IO框架库。

JavaSE-Stream流

JavaSE-Stream流

从流的出生,经过过滤,排序,去重等各种方法,变成最后的结果,就像水流一样,在代码层面也类似地简单明了,最大的优点是不用自己去保存中间状态,而是可以一直往后操作,直到遇到Stream的终结方法,也像水流一样,Stream流使用后就会改变,一般只用一遍。

1.获取Stream流

一般集合都提供了stream方法,只需要.stream()就可以获取。比如

1
2
Set<String> set = new HashSet<>();
Stream<String> stream = set.stream();

但Map除外,Map用以下方法获取流:

  1. keySet()获取Key的Set后stream或者values()获取Value的Set后stream
  2. entrySet()转化为包含Map.Entry的Set后stream

一般建议用第二种,毕竟Key+Value才是Map的精髓,下面代码里的Entry可以理解为Pair(一对)。

1
2
Map<String, Double> map = new HashMap<>();
Stream<Map.Entry<String, Double>> stream = map.entrySet().stream();

Arrays数组可以通过以下方式获得Stream

  1. Arrays.stream(YourArrays)
  2. Stream.of(YourArrays)

2.常用的Stream的中间方法

filter使用lambda式判断条件作为过滤,例如:

1
2
3
4
5
6
7
8
List<Double> scores = new ArrayList<>();
Collections.addAll(scores, 88.5, 108.8, 60.0, 99.0, 9.5, 99.6, 25.0);
//需求1:找出成绩人于等于60分的数据,并升序后,可输出。
List<Double> list1 = scores.stream()
.filter(s -> s > 60)
.sorted()
.collect(Collectors.toList());
System.out.println(list1);

sorted排序,limit限制数量,skip跳过,distinct去重,都比较简单。其中sorted需要重写排序规则,在集合体系已经讲过,distinct需要重写hashCode和equals,如果是Java自带的基础集合,都不用管这些。

map操作接收一个函数作为参数,支持lambda,比如

1
2
3
4
List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5); 
List<String> strList = numList.stream()
.map(n -> n.toString())
.collect(Collectors.toList());

concat合并流,后者接在前者后面,比如

1
2
3
4
Stream<String> stream1 = Stream.of("张三", "李四");
Stream<String> stream2 = Stream.of("张三2", "李四2", "王五");
Stream<String> streamAll = Stream.concat(stream1, stream2);
streamAll.forEach(s -> System.out.println(s));

3.常用的终结方法

collect里面填诸如Collectors.toList(),Collectors.toSet(),to什么就用什么接收结果。

1
2
.collect(Collectors.toList());
.collect(Collectors.toMap(Student::getName, Student::getHeight))
JavaSE-集合

JavaSE-集合

Java基础集合分为两大类,Collection和Map,前者单列集合,后者双列集合。

Collection

下图为Collection集合体系树

Collection下有List和Set两小类集合,List和Set都只是接口。

List的具体实现有ArrayList和LinkedList,Set的具体实现有HashSet,LinkedHashSet,TreeSet。

它们的特点如下表

|——|—————|————|
| | | |
| List | ArrayList | 有序,可重复,有索引 |
| List | LinkedList | 有序,可重复,有索引 |
| Set | HashSet | 无序,不重复,无索引 |
| Set | LinkedHashSet | 有序,不重复,无索引 |
| Set | TreeSet | 排序,不重复,无索引 |

Map

下图为Map集合体系树

其中具体的实现分别是HashMap,LinkedHashMap,TreeMap,特点如下表

|—–|—————|————|
| | | |
| Map | HashMap | 无序,不重复,无索引 |
| Map | LinkedHashMap | 有序,不重复,无索引 |
| Map | TreeMap | 排序,不重复,无索引 |

需要注意的是Map的”序”都是基于Key的

性能

ArrayList

ArrayList行为和数组无异,插入删除会设计复制覆盖,性能不高,但是因为索引所以涉及索引的操作性能格外的好,比如读取,末尾增删改。同时内存占用很小,除了数据本身没有太多其他的东西。

LinkedList

LinkedList在ArrayList基础上,使用”链”链接前后元素,取缔了ArrayList因为复制覆盖造成的低效行为,在增删改性能都很好,但是因为使用了链所以数据在内存中不是连续的,故而失去了索引一步读取的优势,需要遍历才能锁定元素,因此涉及索引的操作都受到一定性能下滑。内存占用比ArrayList多了”链”。

请使用Iterator迭代器,通过Iterator迭代List永远是效率最高的

1
2
3
List<String> list = new ArrayList<String>();
//后期还可直接替换成LinkedList
list = new LinkedList<String>();
HashSet

HashSet是基于哈希表实现的,哈希表是一种增删改查性能都较好的数据结构

目前的(jdk8+)哈希表是基于数组+链表+红黑树实现的。

我们可以肯定LinkedList的链能够一步增删元素的优势,但是却没法一步定位到元素,但是Hash可以逼近,Hash将元素的哈希值取余后的到的结果作为索引放入数组对应位置,如果多个元素位置相同就用链连接,这就是哈希表的雏形=数组+链表,虽然看起来漏洞百出。使用哈希表定位元素只需对哈希值取余得到索引,锁定数组位置,然后遍历链表,锁定元素。只需要保证哈希表取余和遍历都不会花上太多时间,那么哈希表在锁定元素上就也是成功的。

庆幸的是,哈希表的取余操作十分高效,这得益于它的设计,哈希表的数组长度保持为2的幂,用二进制数表示就是一个1+多个0,比如10=2,100=4,1000=8,那么对于随便一个二进制数,和这个2幂进行”按位与”运算就可以得到余数的二进制数。

如果我们保证一个数组位置上的元素不会太多,那么遍历链表也是开销极小的。哈希表默认的加载因子为0.75,意味着元素数超过数组长度*0.75时,哈希表会进行扩容,也就是将数组长度*2 ,这就保证了一个数组位置上的链表不会太长。此时又会产生一个问题,扩容后所有元素都需要重新取余计算位置吗?不用,哈希表也有巧妙的办法,基本上只需要移动一半的元素就可以。

同时,在红黑树的加持下,如果太过幸运,数组某位置上的链表过长(>8),那么哈希表会把这个链表变成红黑树,进一步为遍历加速。

因此,HashSet的性能是均衡地较好。

LinkedHashSet

就是在HashSet的基础上,给元素间加上了链,确定了它们的顺序。

TreeSet

底层不是哈希表了,而是红黑树,一种自平衡二叉树。性能感觉起来是更优雅的均衡较好。

随着元素数量增加,二叉树优势越为明显,如果数组+链表的结构能在低量元素时能比红黑树快,那么哈希表就比单纯的红黑树快。

Map

Map和Set是一样的,更准确地说,Set底层用的就是Map,忽略了value只保留key而已。


其他问题

HashSet不能对内容一样的两个不同对象去重,为什么?怎么办?

比如自定义Student类

1
2
3
4
5
public class Student {
private String name;
private int age;
private double height;
}
1
2
Student s1 = new Student("张三", 22, 189.5);
Student s2 = new Student("张三", 22, 189.5);

因为第一,哈希表需要对象的哈希值确定位置,而hashCode()默认是地址的比较,两个不同对象地址明显不同,使用哈希表会错误地把两个对象也放进去。解决办法是重写对象地hashCode()方法。

因为第二,如果两个对象hashCode()一致,在数组同一位置,此时会进行equals比较,如果是false,后者就会当作新元素。解决办法就是重写equals方法。

在Java中==对基本数据类型是值的比较,对对象是地址比较。

equals方法(一般不对基本数据类型比较),对对象是地址比较,但是对象的equals方法可以重写。这很常见,就像String一样是重写过的,使用equals实际上是在对值进行比较。

所以想要去重,就要重写对象的hashCode和equals方法。

idea可以快捷插入hashCode和equals,默认是值一样就相等。

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StudentComp student = (StudentComp) o;
return age == student.age && Double.compare(height, student.height) == 0 && Objects.equals(name, student.name);
}

@Override
public int hashCode() {
return Objects.hash(name, age, height);
}
如果TreeSet的键是自定义对象,怎么定义排序规则?

两个办法

  • Student类继承Comparable接口,重写compareTo方法
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Student implements Comparable<Student> {
private String name;
@Override
public int compareTo(Student o) {
if (this.age!=o.age){
return Integer.compare(this.age, o.age);
}else if (this.height!=o.height){
return Double.compare(this.height, o.height);
}else{
return this.name.compareTo(o.name);
}
}
}

  • 使用TreeSet时提供参数构造器的Comparator
1
2
3
4
5
6
7
8
9
10
11
12
Set<Student> set2 = new TreeSet<>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
if (o1.age!=o2.age){
return Integer.compare(o1.age, o2.age);
}else if (o1.height!=o2.height){
return Double.compare(o1.height, o2.height);
}else{
return o1.name.compareTo(o2.name);
}
}
});
1
2
3
4
5
6
7
8
9
Set<Student> set2 = new TreeSet<>((o1, o2)->{
if (o1.age!=o2.age){
return Integer.compare(o1.age, o2.age);
}else if (o1.height!=o2.height){
return Double.compare(o1.height, o2.height);
}else{
return o1.name.compareTo(o2.name);
}
});

Linux+Docker部署基于Ambari的Hadoop集群

Linux+Docker部署基于Ambari的Hadoop集群

建议文档和视频一起食用。

文档链接

文档https://1138882663s-organization.gitbook.io/between-code-and-words/bigdata/ambari/da-jian-xu-ni-ji-ji-qun-yi-ji-an-zhuang-ambari

视频链接视频https://b23.tv/YdYulVJ

docker因为容器化技术和虚拟化技术的区别,部署hadoop会存在很多bug。建议使用虚拟机而不是容器。

https://www.bilibili.com/video/BV1Az4y1v7Zw/https://www.bilibili.com/video/BV1Az4y1v7Zw/

优雅地给Docker容器添加新端口

优雅地给Docker容器添加新端口

一共分为三步,停止容器和docker服务,修改配置文件,重启服务和容器。

这里只讲如何修改配置文件。

如果你是Linux环境

容器配置文件hostconfig.json 通常位于 /var/lib/docker/containers/[hash_of_the_container]/hostconfig.json 或者 /var/snap/docker/common/var-lib-docker/containers/[hash_of_the_container]/hostconfig.json

找到PortBindings字段,以下是一个端口的格式例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
"PortBindings": {
"8080/tcp": [
{
"HostIp": "",
"HostPort": "8080"
}
],
"8088/tcp": [
{
"HostIp": "",
"HostPort": "8088"
}
]
},

如果不起作用,建议同时修改下面提到的config.v2.json

如果你是windws+wsl2环境

那么你需要修改两个文件,hostconfig.jsonconfig.v2.json,它们都位于/mnt/wsl/docker-desktop-data/data/docker/<containerID>下。

hostconfig.json文件修改和linux的一样。

config.v2.json需要修改以下两个字段

1
2
3
"ExposedPorts":{"8080/tcp":{},"8088/tcp":{}}

"Ports":{"8080/tcp":[{"HostIp":"0.0.0.0","HostPort":"8080"}],"8088/tcp":[{"HostIp":"0.0.0.0","HostPort":"8088"}]}

参考资料

How do I assign a port mapping to an existing Docker container? - Stack Overflowhttps://stackoverflow.com/questions/19335444/how-do-i-assign-a-port-mapping-to-an-existing-docker-containerAdding new exposed ports to existing docker container (Windows/WSL 2) | by LifeOnKeyboardBlog | Mediumhttps://medium.com/@lifeonkeyboardblog/adding-new-exposed-ports-to-existing-docker-container-windows-wsl-2-3cfe58d551e

Docker容器中删除文件后,大小不减反增怎么办?

Docker容器中删除文件后,大小不减反增怎么办?

Docker的镜像是由多层存储结构组成的,每一层都是只读的,所以您无法删除之前层的操作。

但是可以通过以下步骤达到一样的效果。

假设你从原始镜像a中创建了容器b。

现在你在容器b中删除了一些东西。

您可以使用docker export命令将容器的文件系统导出为一个tar文件:

1
docker export -o my.tar containerID

然后,使用docker import命令从这个tar文件导入为一个新的镜像:

1
docker import my.tar new-image

此时这个new-image镜像就摆脱了旧镜像的历史层,大小也会相应大大减少。

但是你可能会失去一些东西:环境变量、端口映射、镜像的历史记录和层级结构、镜像的标签和版本号、镜像的创建时间和作者、容器的启动命令和参数等等。

基数排序和快速排序谁快(随机数测试)?

基数排序和快速排序谁快(随机数测试)?


前言

什么?你说归并???
开了个1百万的数据量,差点以为是鲁大师点烟。

一、测试结果对比

使用的是win10的子系统ubuntu编译运行

计算时间使用的是< cstdlib>里的clock()函数,因为数量很小,所以最后结果没除CLOCKS_PER_SEC

基数排序会快一些,但是消耗空间接近2倍更多。
2倍,才2倍?
我参考了这个优化—链接

二、附上代码,大家可以自己手动测试测试

language-cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include<iostream>
#include<cstdio>
#include<ctime>
#include<algorithm>
#include<cstdlib>
using namespace std;

int getMax(int* p,int len)
{

int m=p[0];
for(int i=1;i<len;++i)
{
if(p[i]>m){
m=p[i];}}
return m;
}

void count_sort(int* p,int len,int cot)
{

int temp[len];//不报错???
int buckets[10]={
0};
for(int i=0;i<len;++i)
{
buckets[(p[i]/cot)%10]++;}
for(int i=1;i<10;++i)
{
buckets[i]+=buckets[i-1];}
for(int i=len-1;i>=0;--i)
{

temp[buckets[(p[i]/cot)%10]-1]=p[i];
buckets[(p[i]/cot)%10]--;
}
for(int i=0;i<len;++i)
{
p[i]=temp[i];}
}


int main()
{

srand((unsigned)time(NULL));
clock_t start,end;
int arr[100];
int len=100;
for(int i=0;i<len;++i)
{
arr[i]=rand()%len;}

start=clock();

//count_sort
int maxA=getMax(arr,len);
for(int cot=1;maxA/cot>0;cot*=10)
{
count_sort(arr,len,cot);}

//sort(arr,arr+len);

end=clock();
printf("\ntime=%.6lf\n",(double)(end-start));//CLOCKS_PER_SEC
// for(int i=0;i<len;++i)
// {printf("%d ",arr[i]);}

return 0;
}