详解c++---list介绍

news2024/11/26 13:45:37

目录标题

  • list介绍
  • list定义
  • list遍历
  • list数据插入
    • push_back
    • push_front
    • insert
  • list删除
    • pop_back
    • pop_front
    • erase
  • list排序
  • list去重
  • list合并
  • list转移
  • list其他函数
    • empty
    • size
    • front
    • back
    • assign
    • swap
    • resize
    • clear
  • list排序效率问题

list介绍

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
  3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
  4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率 更好。
  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素).

list定义

首先得知道的一点就是list是一个类模板,在使用list来创建对象的时候我们必须得进行显示实例化:
在这里插入图片描述
那么这里的模板提供了两个类型,第一个类型是存储数据的类型,第二个类型是关于内存池的,既然这里提供了默认类型,那么我们这里就不需要多管。我们再来看看这个类型的构造函数:

在这里插入图片描述
这就是四种不同的list构造函数,大家可以发现一点就是这里跟vector一样都是由内存池来申请空间的,所以这里就会多一个参数

const allocator_type& alloc = allocator_type()

但是好在这里提供了缺省值不需要我们初学者来进行传参,但是如果未来我们的水平非常高了嫌弃库里面的内存池写的不好的话,我们可以自己写一个内存池并作为参数传递过去,那这里我们都是初学者就不要太管这个了,然后这个函数里面的size_type就是无符号整型的意思。
在这里插入图片描述
value_type的意思就是第一个模板参数的类型。
在这里插入图片描述
第一个形式:

explicit list (const allocator_type& alloc = allocator_type());

表示的意思是,无参构造也就是说通过这个构造函数创建出来的list对象没有任何内容,比如说下面的代码:

void test1()
{
	list<int> l1;
	list<int>::iterator it = l1.begin();
	while (it!=l1.end())
	{
		cout << *it << " ";
		++it;
	}
}

将这段代码运行一下就可以发现这里打印不出来任何的内容:
在这里插入图片描述
第二种形式:

explicit list (size_type n, const value_type& val = value_type(),
                const allocator_type& alloc = allocator_type());

用n个相同的数据来进行初始化,这里的第二个参数你可以传也可以不传不传的话这里就会调用该类型的默认构造函数来进行赋值,比如说下面的代码:

	list<int> l1(10, 3);
	list<int> l2(10);
	cout << "l1的内容为:";
	for (auto l : l1)
	{
	cout << l;
	}
	cout << endl;
	cout << "l2的内容为:" ;
	for (auto l : l2)
	{
		cout << l;
	}

代码的运行结果如下:
在这里插入图片描述
第三种形式:

template <class InputIterator>
  list (InputIterator first, InputIterator last,
         const allocator_type& alloc = allocator_type());

使用迭代器区间来进行初始化,比如说下面的代码:

	list<int>l1(5, 2);
	list<int>::iterator it1 = l1.begin();
	list<int>l2(++it1,--l1.end());
	cout << "l1的内容为:";
	for (auto l : l1)
	{
	cout << l;
	}
	cout << endl;
	cout << "l2的内容为:" ;
	for (auto l : l2)
	{	
	cout << l;
	}	

这里大家要注意的一点就是:list的迭代器不能像前面的string和vector的迭代器一样,加一个常数来指向对应的位置,只能通过前置和后置++和–来改变迭代器的位置,那么上面的代码运行的结果就如下:
在这里插入图片描述
第四种形式:

list (const list& x);

使用另外一个形式相同的list对象来初始化该对象,那么这里的使用方法就如下:

	list<int>l1(5, 2);
	list<int>l2(l1);
	cout << "l1的内容为:";
	for (auto l : l1)
	{
		cout << l;
	}
	cout << endl;
	cout << "l2的内容为:";
	for (auto l : l2)
	{
		cout << l;
	}

该代码运行结果如下:
在这里插入图片描述

list遍历

与前面的string和vector不一样的地方在于,我们这里的list不存在用[ ]遍历和修改数据的方式,因为string和vector都是在一块连续的空间存放的数据,而list不一样,它是在不同的地方存放数据,这些数据通过指针来进行相互的关联,那这里我们就可以通过数组来理解这里为什么不能用方括号,首先我们知道数组名是首元素的地址,而且数组中的元素在一块连续的区间,每个元素的地址之间相差为4:

int arr[10]={1,2,3,4,5,6,7,8,9,10};

这里的arr就是一个地址,该地址指向的是这个数组中的第一个元素也就是1,当我们使用这种形式来访问数据时:

int i=0;
cout<<arr[1]<<endl;

编译器会将这里的arr[1]转换成指针解引用的形式:*(arr + i) arr是首元素的地址该地址的类型是int*类型,当这里的i等于0时这里的地址就不会发生改变,从而得到下标为0的元素,当我们将这里i的值加一时,由于数据是int类型所以它会将这里的地址加4从使这里指针指向下一个元素,当我们再解引用的时候就可以得到第二个元素,所以问题就来了,list的元素并不在一块连续的空间,当我们使用[ ]来获取元素时,通过对里面的值加1减1能获取对应的元素吗?那很明显是不行的,所以对于list的元素遍历我们可以采用迭代器遍历,比如说下面的代码:

void test2()
{
	list<int> l1(10, 4);
	list<int>::iterator it1 = l1.begin();
	while (it1 != l1.end())
	{
		cout << *it1 << " ";
		++it1;
	}
}

这个代码的运行结果如下:
在这里插入图片描述
既然范围for的底层是迭代器实现的话,那么范围for也可以实现list的遍历:

void test2()
{
	list<int> l1(10, 4);
	list<int>::iterator it1 = l1.begin();
	while (it1 != l1.end())
	{
		cout << *it1 << " ";
		++it1;
	}
	cout << endl;
	for (auto l : l1)
	{
		cout << l << " ";
	}
}

代码的运行结果如下:
在这里插入图片描述
那么以上就是list遍历的内容。

list数据插入

push_back

在这里插入图片描述
这个函数的功能就是在list对象的尾部插入一个数据,可以通过下面的代码看看该函数的功能:

void test3()
{
	list<int> l1(5, 4);
	l1.push_back(2);
	for (auto l : l1)
	{
		cout << l << " ";
	}
}

该代码的运行结果如下:
在这里插入图片描述

push_front

由于list的数据不在一块连续的空间,所以当我们在对象的头部插入内容的时候就不会造成数据的挪动,所以该类型就提供了push_front函数:
在这里插入图片描述
该函数的使用形式如下:

void test3()
{
	list<int> l1(5, 4);
	l1.push_back(2);
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	l1.push_front(1);
	for (auto l : l1)
	{
		cout << l << " ";
	}
}

代码的运行结果如下:
在这里插入图片描述

insert

在这里插入图片描述
上面两个函数只能实现在对象的头部和尾部插入数据,并且一次只能插入一个数据,那么这里的insert函数就可以实现在任意位置插入一个数据,或者一次性插入n个相同的数据,或者一段来自于其他对象的数据,insert的使用得用到迭代器,而list的迭代器无法通过加减一个整数来指向指定的位置,所以要想很好的使用这里的迭代器我们这里就得使用库中的find函数:
在这里插入图片描述

该函数的返回类型是迭代器类型,所以我们可以使用该函数的返回值来初始化迭代器,进而更好的使用insert函数,那么这里的使用代码就如下:

void test4()
{
	list<int> l1(5,4);
	l1.push_back(3);
	list<int> ::iterator it1 = find(l1.begin(), l1.end(), 3);
	it1=l1.insert(it1, 2);//指定位置插入一个数据,并更新迭代器的位置
	cout << "对象中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	cout << "对象中的内容为:";
	l1.insert(it1,2, 1);//指定位置插入n个相同的数据
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	it1 = find(l1.begin(), l1.end(), 3);//再更新it1的值使其再指向原来的3
	list<int> l2(3, 7);
	cout << "对象中的内容为:";
	l1. insert(it1, l2.begin(), l2.end());//指定位置插入一段数据
	for (auto l : l1)
	{
		cout << l << " ";
	}
}

那么这段代码的运行结果就如下:
在这里插入图片描述

list删除

pop_back

这个函数的功能就是删除尾部的数据:
在这里插入图片描述
该代码的使用如下:

void test5()
{
	list<int> l1(5, 4);
	l1.push_back(3);
	l1.push_front(5);
	cout << "对象中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	cout << "删除尾部数据"<<endl;
	l1.pop_back();
	cout << "对象中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
}

代码的运行结果为:

在这里插入图片描述

pop_front

因为这里是链表,对头部删除数据不会挪动数据,所以就有了pop_front函数该函数的介绍如下:
在这里插入图片描述
代码的使用如下:

void test5()
{
	list<int> l1(5, 4);
	l1.push_back(3);
	l1.push_front(5);
	cout << "对象中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	cout << "删除尾部数据"<<endl;
	l1.pop_back();
	cout << "对象中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	cout << "删除头部数据" << endl;
	l1.pop_front();
	cout << "对象中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
}

代码的运行结果如下:
在这里插入图片描述

erase

erase函数能够实现任意位置的删除,我们来看看该函数的参数:
在这里插入图片描述
这个函数重载了两个不同的形式,第一个形式表示的意思是删除指定位置上的一个元素,第二个形式的意思就是删除对象中的一段数据,那么这里我们可以通过下面的代码来了解这个函数的使用:

void test6()
{
	list<int> l1(2, 4);
	l1.push_back(3);
	l1.push_back(2);
	l1.push_back(1);
	l1.push_back(0);
	cout << "对象中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	cout << "删除指定位置的一个元素" << endl;
	list<int>::iterator it1 = find(l1.begin(), l1.end(),3);
	l1.erase(it1);
	cout << "对象中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	cout << "删除一段数据" << endl ;
	cout << "对象中的内容为:";
	l1.erase(++l1.begin(), --l1.end());
	for (auto l : l1)
	{
		cout << l << " ";
	}
}

代码的运行结果如下:
在这里插入图片描述
那么这里大家要注意一个问题就是:当我们使用迭代器删除对象中的一个元素的时,该迭代器是会失效的,原因很简单迭代器指向了一个数据,当我们把这个数据删除之后改迭代器指向的那个空间就被操作系统回收了,这个时候的迭代器就相当于指针中的野指针,这里大家要注意一下。

list排序

c++本身就提供了一个sort函数用来对数据进行排序:

在这里插入图片描述

那为什么我们这里的list还得自己提供一个sort函数呢?
在这里插入图片描述
那要想解决这个问题我们就得来提提迭代器分类的问题,c++将迭代器分为了三类:单向迭代器,双向迭代器,随机迭代器。单向迭代器只能够执行++的功能使其迭代器指向下一个元素,单向链表中的迭代器就是单向迭代器;双向迭代器不仅能够执行++功能,而且还能够执行- -功能这种迭代器既可以通过++指向下一个元素,还可以通过使用 - -使其指向上一个元素,那么我们这里的list双向链表就是这种迭代器;随机迭代器在双向迭代器之上还可以通过 + 或者 - 来达到一下指向后n个或者前n个元素,那么vector和string中的迭代器就是随机迭代器,那我们这里再来看看系统中sort函数的参数类型是:RandomAccessIterator将其翻译一下就是随机迭代器,而我们list中的迭代器是双向迭代器,如果我们使用双向迭代器来调用sort函数的话,看看会发生什么样的情况,测试的代码如下:

void test7()
{
	list<int> l1(2, 4);
	l1.push_back(3);
	l1.push_back(2);
	l1.push_back(1);
	l1.push_back(0);
	cout << "对象中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	cout << "调用系统中的sort函数" << endl;
	sort(l1.begin(), l1.end());
	cout << "对象中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
}

我们将代码运行一下就可以看到这里报错:
在这里插入图片描述
我们将这里的swap修改一下,改成这样: l1.sort();这样的话我们调用的就是list库中的swap函数,我们再运行一下上面的代码就可以发现正常运行了:
在这里插入图片描述
那这是为什么呢?原因很简单,系统的sort函数在实现的过程中会将两个迭代器进行相减,然后用相减得到的结果结合快排从而实现数据的排序,我们这里传过去的迭代器是双向迭代器不支持两个迭代器相减,所以在使用的时候就会报错,这也是为什么list库要单独提供一个sort函数的原因,list中的sort函数采用的是归并排序而不是快速排序。

list去重

在这里插入图片描述
这个函数的功能是去除对象中重复的数据,比如说对象还有三个整型1和两个整型2,那么使用这个函数之后这个对象当中就只会有1个整型1和1个整型2,但是使用这个函数得有个前提,就是对象中的数据必须是有序的才行,比如说我们下面的代码:

void test8()
{
	list<int> l1(3, 2);
	l1.push_back(1);
	l1.push_back(2);
	l1.push_back(6);
	l1.push_back(2);
	l1.push_back(3);
	l1.push_back(4);
	l1.push_back(1);
	l1.push_back(2);
	cout << "对象中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	cout << "将对象的数据进行去重之后对象的内容为:";
	l1.unique();
	for (auto l : l1)
	{
		cout << l << " ";
	}
}

我们这里是个无序的数据,并且内部含有重复的数据但是我们将上面的代码运行一下就可以发现这里的去重函数并没有发挥作用:
在这里插入图片描述
虽然去除了一些内部重复的数据但是在该对象中依然含有重复的数据,那么这就可以证明一点当数据是无序的时候这里的去重函数会失效,我们在去重函数之前使用一下sort函数,将对象的数据变成有序的,再运行一下看看结果会是如何,那么下面是代码:

void test8()
{
	list<int> l1(3, 2);
	l1.push_back(1);
	l1.push_back(2);
	l1.push_back(6);
	l1.push_back(2);
	l1.push_back(3);
	l1.push_back(4);
	l1.push_back(1);
	l1.push_back(2);
	cout << "对象中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	cout << "将对象的内容变成有序的:" << endl;
	l1.sort();
	cout << "对象中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	cout << "将对象的数据进行去重" << endl;
	cout << "对象中的内容为:";
	l1.unique();
	for (auto l : l1)
	{
		cout << l << " ";
	}
}

代码的运行结果如下:
在这里插入图片描述
那么这里的结果就非常的明显了,使用这个函数之前我们必须得将对象的数据进行排序,这样才能发挥它的作用,那么这里大家肯定会有个疑问就是为为什么不直接在unique函数中直接帮我们排序呢?这样我们就不用自己调用函数了啊,那么为什么没有这么做的原因也非常的简单,因为如果我们对象的数据本来就是有序的话,那调用这个函数再进行一次排序的话不就会造成浪费了吗?所以在unique函数里面是不会对我们传过来的对象进行排序的,得我们使用者自己排序,那么者就是该函数的介绍。

list合并

将两个相同数据类型的list对象合并成一个list对象就得用到下面这个函数:
在这里插入图片描述
该函数有个特性就是当你给的两个对象的数据是有序的话,那么我们使用这个函数合并之后的结果依然也是有序的,我们可以看看下面的代码:

void test9()
{
	list<int> l1(3, 2);
	list<int> l2;
	l2.push_back(1);
	l2.push_back(2);
	l2.push_back(3);
	l2.push_back(4);
	l2.push_back(5);
	l1.merge(l2);
	cout << "对象l1中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	cout << "对象l2中的内容为:";
	for (auto l : l2)
	{
		cout << l << " ";
	}
}

这段代码的运行结果如下:
在这里插入图片描述
通过这个运行结果大家可以看到这个函数的使用特性就是哪个对象调用的这个函数,那么就会将另外一个对象的内容合并到这个对象里面去,并且另外一个对象的内容会被清空,那么这就是该函数的使用规则。

list转移

将一个list转移到另外一个list对象的话就可以用到下面这个函数:
在这里插入图片描述
这就是该函数的介绍,我们可以将一个对象的内容转移到另外一个对象的指定position位置,这就是第一种形式对应的指定方式:

void test10()
{
	list<int> l1(3, 2);
	list<int> l2;
	l2.push_back(1);
	l2.push_back(2);
	l2.push_back(3);
	l2.push_back(4);
	l2.push_back(5);
	cout << "对象l1中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	l1.splice(++l1.begin(), l2);
	cout << "对象l1中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	cout << "对象l2中的内容为:";
	for (auto l : l2)
	{
		cout << l << " ";
	}
}

代码的运行结果如下:
在这里插入图片描述
当我们以这种形式使用这个函数之后对象l2中的内容就完全没有了,全部都转移到l1的指定位置当然我们还可以将l2的部分内容转移到l1里面,那这里就得用到第二和第三种形式,第二种形式就是将位置i的元素进行转移,第三种是将first到end之间的内容进行转移,我们来看看下面的代码,这是第二种形式对应的代码:

void test11()
{
	list<int> l1(3, 2);
	list<int> l2;
	l2.push_back(1);
	l2.push_back(2);
	l2.push_back(3);
	l2.push_back(4);
	l2.push_back(5);
	cout << "对象l1中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	l1.splice(++l1.begin(),l2, ++l2.begin());
	cout << "对象l1中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	cout << "对象l2中的内容为:";
	for (auto l : l2)
	{
		cout << l << " ";
	}
}

将这段代码运行一下就可以看到这里只转移了一个元素:
在这里插入图片描述
下面是第三种形式的代码:

void test12()
{
	list<int> l1(3, 2);
	list<int> l2;
	l2.push_back(1);
	l2.push_back(2);
	l2.push_back(3);
	l2.push_back(4);
	l2.push_back(5);
	cout << "对象l1中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	l1.splice(++l1.begin(), l2, ++l2.begin(),--l2.end());
	cout << "对象l1中的内容为:";
	for (auto l : l1)
	{
		cout << l << " ";
	}
	cout << endl;
	cout << "对象l2中的内容为:";
	for (auto l : l2)
	{
		cout << l << " ";
	}
}

代码的运行结果如下:
在这里插入图片描述
我们可以看到这里的l2除了第一个元素和最后一个元素其他元素都转移到了l1的第二个元素上了,那么这就是该函数的使用方法。

list其他函数

empty

用来返回该对象的内容是否为空。
在这里插入图片描述

size

返回list对象的长度
在这里插入图片描述

front

返回list对象的第一个元素
在这里插入图片描述

back

返回list对象的最后一个元素
在这里插入图片描述

assign

将list对象的空间进行清空,然后用新的内容来进行填充。
在这里插入图片描述

swap

交换两个list对象的内容。
在这里插入图片描述
当然list中也提供了两个不同参数的swap,以防止使用者写错从而调用了效率较低的库中的swap
在这里插入图片描述

resize

修改对象中的长度,如果修改的长度超过原来的长度则将会用参数中的内容来进行填充
在这里插入图片描述

clear

将对象中的内容全部清空。
在这里插入图片描述

list排序效率问题

即便list中提供了sort函数,但是在实际的使用情况中我们使用该函数的地方依旧很少因为这个函数的效率太低了,比如说下面的代码,我们将list的sort函数与vector的sort函数来进行一下对比,首先先生成100w个随机数,并将这些随机数尾插到两个对象里面:

void test14()
{
	srand((unsigned int)time(0));
	const int N = 1000000;
	vector<int> v;
	list<int> it1;
	for (int i = 0; i < N; i++)
	{
		auto e = rand();
		v.push_back(e);
		it1.push_back(e);
	}
}

然后我们在使用clock函数来进行计时并打印其最后的结果:

void test14()
{
	srand((unsigned int)time(0));
	const int N = 1000000;
	vector<int> v;
	list<int> it1;
	for (int i = 0; i < N; i++)
	{
		auto e = rand();
		v.push_back(e);
		it1.push_back(e);
	}
	int begin1 = clock();
	sort(v.begin(),v.end());
	int end1 = clock();
	int begin2 = clock();
	it1.sort();
	int end2 = clock();
	printf("vector sort:%d\n", end1 - begin1);
	printf("list sort:%d\n", end2 - begin2);
}

我们在release环境下跑一下这段代码就可以发现这两个排序的效率差别挺大的:
在这里插入图片描述
所以当我们想对list数据进行排序的话,我们采用的方法一般都是先将,list的数据拷贝到vector中,再对vector进行排序,最后再将排序后的结果拷贝的list里面从而实现list的数据拷贝,那么下面的代码就是上面的排序的改进性:

void test13()
{
	srand((unsigned int)time(0));
	const int N = 1000000;
	vector<int> v;
	v.reserve(N);

	list<int> lt1;
	list<int> lt2;
	for (int i = 0; i < N; ++i)
	{
		auto e = rand();
		//v.push_back(e);
		lt1.push_back(e);
		lt2.push_back(e);
	}

	// 拷贝到vector排序,排完以后再拷贝回来
	for (auto e : lt1)
	{
		v.push_back(e);
	}
	int begin1 = clock();
	sort(v.begin(), v.end());
	int end1 = clock();
	size_t i = 0;
	for (auto& e : lt1)
	{
		e = v[i++];
	}

	int begin2 = clock();
	// sort(lt.begin(), lt.end());
	lt2.sort();
	int end2 = clock();

	printf("vector sort:%d\n", end1 - begin1);
	printf("list sort:%d\n", end2 - begin2);
}

我们将这段代码运行一下发现就,尽管我们这样折腾但是它的效率依然比list单独排序的效率要高:
在这里插入图片描述
那么这就是list排序的效率问题,大家理解就行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/480077.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java笔试强训 22】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525;小易的升…

【Java笔试强训 21】

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点! 欢迎志同道合的朋友一起加油喔&#x1f93a;&#x1f93a;&#x1f93a; 目录 一、选择题 二、编程题 &#x1f525; 洗牌 &…

免费搭建Plex家庭影音中心 - 打造超级多媒体中心【公网远程访问】

文章目录 1.前言2. Plex网站搭建2.1 Plex下载和安装2.2 Plex网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1.前言 用手机或者平板电脑看视频&#xff0c;已经算是生活中稀松平常的场景了&#xff0c;特别是各…

java+jsp企业物流货运快递管理系统servlet

功能需求具体描述&#xff1a; (1)用户功能模块包括用户登录注册&#xff0c;用户信息的修改&#xff0c;用户发布货物信息&#xff0c;给客服人员留言&#xff0c;对运输公司进行评价。 (2)企业功能模块包括企业注册登录&#xff0c;企业信息的修改&#xff0c;受理用户发布的…

c#笔记-代码格式

格式 为了让编译器能看懂。我们编写的源码必须符合一定的规范。 区分大小写 c#是大小写敏感语言。A1和a1是不同的东西。 不区分空白字符 c#对空白字符&#xff08;空格&#xff0c;制表符&#xff0c;换行&#xff09;不敏感。只要不截断单词&#xff0c;可以任意地使用空…

Java 基础进阶篇(二)—— 面向对象的三大特征之二:继承

文章目录 一、继承概述二、内存运行原理 ★三、继承的特点四、继承后&#xff1a;成员变量和方法的访问特点五、继承后&#xff1a;方法重写六、继承后&#xff1a;子类构造器的特点七、继承后&#xff1a;子类构造器访问父类有参构造器八、this、super 总结 一、继承概述 Jav…

TiDB实战篇-TiDB集群常用的监控指标

TiDB Server相关指标 Duration(延迟) 总体延迟 SQL不同的类型延迟 不同实例延迟 QPS&#xff08;每秒钟查询次数&#xff09; 总览 CPS&#xff08;一次请求多个sql,上面的QPS每一次就是一个记录&#xff09; 事务相关 延迟 影响性能的大事务 CPU 内存&#xff08;下图…

面试官:谈谈你对死锁的理解

1. 什么是死锁 比如上一次讲到 synchronized 的时候&#xff0c;一个线程&#xff0c;对同一个对象连续加锁两次&#xff0c;如果出现阻塞等待&#xff0c;代表这个锁是不可重入锁&#xff0c;这样的线程&#xff0c;也就称为死锁&#xff01; 一旦程序进入死锁了就会导致线程僵…

PromQL,让你轻松实现监控可视化!快来了解一下吧!

Prometheus 中的一些关键设计&#xff0c;比如注重标准和生态、监控目标动态发现机制、PromQL等。 PromQL 是 Prometheus 的查询语言&#xff0c;使用灵活方便&#xff0c;但很多人不知道如何更好利用它&#xff0c;发挥不出优势。 PromQL主要用于时序数据的查询和二次计算场…

【Docker】4、Docker 数据卷

目录 一、数据卷介绍二、数据卷相关命令三、创建一个数据卷&#xff0c;并查看数据卷在宿主机的目录位置四、数据卷挂载到容器五、数据卷挂载练习&#xff08;MySQL&#xff09;(1) 加载 MySQL 镜像(2) 根据镜像创建容器 一、数据卷介绍 &#x1f384; 数据卷&#xff08;volum…

Lychee图床 - 本地配置属于自己的相册管理系统并远程访问

文章目录 1.前言2. Lychee网站搭建2.1. Lychee下载和安装2.2 Lychee网页测试2.3 cpolar的安装和注册 3.本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4.公网访问测试5.结语 1.前言 图床作为图片集中存放的服务网站&#xff0c;可以看做是云存储的一部分&#xff0c;既可…

HCIA-RS实验-路由配置-配置RIPv1 和RIPv2

书接上回。。。这篇主要以实验为主&#xff0c;实验的主要目标也是理解RIP路由协议的防环机制 &#xff0c;掌握RIPv1和v2的配置方法&#xff1b;大致拓扑图如下&#xff1a; 拓扑图&#xff1a; 配置对应的IP&#xff1a; R1.2.3 测试R1与R2间的连通性。 配置IP后测试连通性…

设计模式——设计模式简介和七大原则

导航&#xff1a; 【黑马Java笔记踩坑汇总】JavaSEJavaWebSSMSpringBoot瑞吉外卖SpringCloud黑马旅游谷粒商城学成在线设计模式牛客面试题 目录 一、通过经典面试题掌握重点 二、设计模式的目的和核心原则 三、设计模式七大原则 3.1 单一职责原则&#xff08;Single Respo…

Linux 安装 NFS 实现文件目录共享

一、背景介绍 项目中&#xff0c;之前是单节点部署服务&#xff0c;关于文件的上传和下载&#xff0c;只要配置好路径即可。 而当集群化部署后&#xff0c;就会有问题。比如&#xff1a;文件上传的时候&#xff0c;访问的是主机1&#xff0c;而想要现在文件的时候&#xff0c…

【MST】ABC235 E - MST + 1

一开始想的是分类讨论&#xff0c;看那条边加了之后成不成环&#xff0c;如果不成环且权值在前n-1&#xff0c;则一定在MST里&#xff0c;不在前n-1则不在MST里&#xff1b;如果成环了&#xff0c;如果权值不在前n-1&#xff0c;则不在MST里&#xff0c;如果权值在前n-1&#x…

spark 数据的加载和保存(Parquet、JSON、CSV、MySql)

spark数据的加载和保存 SparkSQL 默认读取和保存的文件格式为 parquet 1.加载数据 spark.read.load 是加载数据的通用方法 scala> spark.read. csv format jdbc json load option options orc parquet schema table text textFile 如果读取不同格式的数据&#xff0c;可以…

docker安装fastdfs

1 拉取镜像 docker pull morunchang/fastdfs如果网速下载慢&#xff0c;可以参考资料文件夹中给大家导出的镜像包上传到 Linux服务器上&#xff0c;通过docker load -i my_fdfs.tar 加载镜像。 使用 docker images查看是否成功 2 运行tracker docker run -d --name tracker -…

MySQL高阶——索引设计的推演

前言 MySQL在我们工作中都会用到&#xff0c;那么我们最常接触的就是增删改查&#xff0c;而对于增删改查来说&#xff0c;我们更多的是查询。但是面试中&#xff0c;面试官又不会问你什么查询是怎么写的&#xff0c;都是问一些索引啊&#xff0c;事务啊&#xff0c; 底层结构…

C. Multiplicity(DP + 分解因数)

Problem - C - Codeforces 给定一个整数数组a1&#xff0c;a2&#xff0c;...&#xff0c;an。 如果可以从a中删除一些元素得到b&#xff0c;则称数组b为a的子序列。 当且仅当对于每个i&#xff08;1≤i≤k&#xff09;&#xff0c;bi是i的倍数时&#xff0c;数组b1&#xff…

Spring Data JPA 快速上手

一、JPA的概述 JPA的全称是Java Persisitence API&#xff0c;即JAVA持久化API&#xff0c;是sum公司退出的一套基于ORM的规范&#xff0c;内部是由一些列的接口和抽象类构成。JPA通过JDK5.0注解描述对象-关系表的映射关系&#xff0c;并将运行期的实体对象持久化到数据库中。…