纯数据结构Java实现(1/11)(动态数组)
我怕说这部分内容太简单后,突然蹦出来一个大佬把我虐到哭,还是悠着点,踏实写
大致内容有: 增删改查,泛型支持,扩容支持,复杂度分析。(铺垫: Java语言中的数组)
基础铺垫
其实没啥好介绍的,顺序存储,(非受限的)线性结构。查询O(1),因为支持随机存取。
Java中的数组
Java 中的数组操作,大致如下:
int[] arr = new int[10]
String[] strArr = new String[9]
// 定义的时候就有初始值 --- 自动感知数组长度
int[] scores = new int[]{100, 99, 88}
//获取数组的长度
arr.length
//for each 循环 (数组是可迭代对象)
for(int s in arr){
System.out.println(s)
}
但不同于 Python 这类动态脚本语言,Java中的数组只能存储相同类型的元素。
- Java比较奇葩,
长度
一会儿是 length, 一会儿是 length(),一会儿是 size() - 可以大致按照数组-属性,字符串-方法,其他容器-size() 来记忆
自己实现
先别管动态扩容的部分,现在就看数组这个容器,如何实现增删改查
大体思路
其实就是内部封装一个 int[]
数组,为了方便,顺便需要一个长度属性。
package array;
public class MyArray {
//先声明一下相应的变量, 不暴露给外部 (内部维护保持两者一致)
private int[] data; //capacity 就是 data 的 length
private int size;
// 构造函数,传入数组容量
public MyArray(int capacity) {
data = new int[capacity];
size = 0; //初始情况下,实际有 0 个元素
}
//提供一个默认的吧, 不需要用户手动提供 capacity, 无参
public MyArray() {
this(10);
}
//获取数组元素个数
public int getSize() {
return size;
}
//返回动态数组的容量
public int getCapacity() {
return data.length;
}
//数组此刻是否为空
public boolean isEmpty() {
return size == 0;
}
}
- capacity 容量,数组实际装了多少,用 size 表示
- capacity 可以用内部数组的长度表示,及 data.length
上面就是整体的框架。(还没有涉及动态扩容)
增删改查
因为赶时间,所以增删改查一起来。
添加元素
此时 size 应该指向的是,要添加元素的下一个位置。即尾部添加时 size++
。
简单想: 一开始没有元素,size 在 0 位置,即第一个元素的位置。
尾部添加:
public void append(int elem) {
//TODO: 考虑一下是否超过容量
if (size == data.length) {
throw new IllegalArgumentException("append failed");
}
this.data[size++] = elem;
}
//如果实现了 insert(key, elem) 方法,可以直接写成
//末尾添加
public void append(int elem) {
insert(size, elem);
}
指定位置添加:
//指定位置插入
public void insert(int index, int elem) {
//TODO: 考虑一下是否超过容量
if (size == data.length) {
throw new IllegalArgumentException("append failed");
}
//检查 index 是合法的
if(index < 0 || index > size){
throw new IllegalArgumentException("insert failed, require: index >=0 and index <= size ");
}
//先移动元素,从后面开始 (size-1 移动到 size --> index 移动到 index+1;腾出 index)
for(int i = size-1; i>=index; i--){
data[i+1] = data[i];
}
data[index] = elem; //覆盖原来的 index 处的内容
size++;
}
注意一下,要维护 size 。
头部添加:
此时就可以复用 insert 方法了:
//头部添加
public void presert(int elem){
insert(0, elem);
}
遍历元素
其实就是遍历内部封装的数组,找到相应的元素。
获取整体: (覆写 toString 这个方法)
//获取数组整体,即打印时需要显示的信息
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("MyArray: size=%d, capacity=%d\n", size, data.length));
res.append("[");
//只遍历现有元素,而不是容量
for (int i = 0; i < size; i++) {
res.append(data[i]);
if(i != size-1){
res.append(", ");
}
}
res.append("]");
return res.toString();
}
测试看看:
import array.MyArray;
public class Main {
public static void main(String[] args) {
MyArray arr = new MyArray(20); //容量20
//放入 10 个元素
for(int i=0; i< 10; i++){
arr.append(i);
}
System.out.println(arr);
arr.insert(1, 100);
System.out.println(arr);
}
}
//输入结果如下:
MyArray: size=10, capacity=20
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
MyArray: size=11, capacity=20
[0, 100, 1, 2, 3, 4, 5, 6, 7, 8, 9]
取出具体元素 (getter)
按照索引取。
//获取某个具体元素: 通过封装,加入自定义 index 检查(保证获取数据安全)
public int get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Get failed, Index is illegal");
}
return data[index];
}
修改元素 (setter)
按照索引修改。
//更新元素
public void set(int index, int elem) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Get failed, Index is illegal");
}
data[index] = elem;
}
是否包含
线性遍历一遍,看看是否存在
//是否包含
public boolean contains(int elem) {
for (int i = 0; i < size; i++) {
if (data[i] == elem) {
return true;
}
}
return false;
}
搜索元素
还是线性搜索一下,找到就返回下标
public int find(int elem){
for(int i=0; i<size; i++){
if(data[i] == elem){
return i;
}
}
return -1; //没有找到返回 -1
}
删除元素
删除就是覆盖,大致分为两类: 按照索引删除,按照元素删除。
从 index 开始到 size-1,不断往前赋值,最后维护一下整体的 size-—
。
(size的元素用户是拿不到的,所以data[size]值不必担心)
//删除元素 (通常要返回相应的元素)
public int remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Remove failed, Index is illegal");
}
//先把要返回的值存储起来
int ret = data[index];
//覆盖删除: 把 index+1 的值移动到 index --> 把 size-1 的值移动到 size-2
for (int i = index + 1; i < size; i++) {
data[i - 1] = data[i];
}
size--;
//data[size] 不必清空,因为用户取不到 data[size]
return ret;
}
当然也可以补充一些快捷方法:
//快捷删除尾部元素
public int pop(){
return remove(size-1);
}
//快捷删除头部元素
public int popLeft(){
return remove(0);
}
按照元素删除: 先查找,然后删除
//删除指定元素 (不必返回,因为用户已经知道 elem)
public void removeElem(int elem){
int index = find(elem);
if(-1 == index) {
throw new IllegalArgumentException("Remove failed, cannot find this elem");
}
//成功了什么都不提示,出错才提示
remove(index);
}
- 默认都是查找,删除第一个找到的元素
简单测试一下,删除:
//[0, 100, 1, 2, 3, 4, 5, 6, 7, 8, 9]
//删除试试
arr.remove(1);
System.out.println(arr); //[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
//删除末尾试试
arr.pop();
System.out.println(arr); //[0, 1, 2, 3, 4, 5, 6, 7, 8]
//删除头部试试
arr.popLeft();
System.out.println(arr); //[1, 2, 3, 4, 5, 6, 7, 8]
//删除 4 这个数字
arr.removeElem(4);
System.out.println(arr); //[1, 2, 3, 5, 6, 7, 8]
泛型支持
语法限制
由于泛型的限制(不能放入基本类型,只能放入其包装类),内部封装的数组初始化的时候需要强制类型转换一下。
也就是说,可以用于声明,但不能用于定义。
data = new E[capacity]; //报错,历史遗留问题,不支持该语法
//正确的写法 (借助强制类型转换)
data = (E[])new Object[capacity];
完整代码
特别注意一下:
- 对象值的比较,需要改成
equals
- 删除的时候,因为存储的是对象的引用,所以应该把移动后的
data[size]
位置设置为NULL
- 但是这一条是非必须,因为
闲逛对象 != memory leak
- 但是这一条是非必须,因为
还是贴一下,支持泛型的完整代码吧:
package array;
public class AdvanceArray<E> {
//先声明一下相应的变量, 不暴露给外部 (内部维护保持两者一致)
private E[] data; //capacity 就是 data 的 length
private int size;
// 构造函数,传入数组容量
public AdvanceArray(int capacity) {
data = (E[])new Object[capacity];
size = 0; //初始情况下,实际有 0 个元素
}
//提供一个默认的吧, 不需要用户手动提供 capacity, 无参
public AdvanceArray() {
this(10);
}
//获取数组元素个数
public int getSize() {
return size;
}
//返回动态数组的容量
public int getCapacity() {
return data.length;
}
//数组此刻是否为空
public boolean isEmpty() {
return size == 0;
}
//末尾添加
public void append(E elem) {
insert(size, elem);
}
//头部添加
public void presert(E elem) {
insert(0, elem);
}
//指定位置插入
public void insert(int index, E elem) {
//TODO: 考虑一下是否超过容量
if (size == data.length) {
throw new IllegalArgumentException("append failed");
}
//检查 index 是合法的
if (index < 0 || index > size) {
throw new IllegalArgumentException("insert failed, require: index >=0 and index <= size ");
}
//先移动元素,从后面开始 (size-1 移动到 size --> index 移动到 index+1;腾出 index)
for (int i = size - 1; i >= index; i--) {
data[i + 1] = data[i];
}
data[index] = elem; //覆盖原来的 index 处的内容
size++;
}
//获取数组整体,即打印时需要显示的信息
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("MyArray: size=%d, capacity=%d\n", size, data.length));
res.append("[");
//只遍历现有元素,而不是容量
for (int i = 0; i < size; i++) {
res.append(data[i]);
if (i != size - 1) {
res.append(", ");
}
}
res.append("]");
return res.toString();
}
//获取某个具体元素: 通过封装,加入自定义 index 检查(保证获取数据安全)
public E get(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Get failed, Index is illegal");
}
return data[index];
}
//更新元素
public void set(int index, E elem) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Get failed, Index is illegal");
}
data[index] = elem;
}
//是否包含
public boolean contains(E elem) {
for (int i = 0; i < size; i++) {
if (data[i].equals(elem)) {
return true;
}
}
return false;
}
//搜索元素
public int find(E elem) {
for (int i = 0; i < size; i++) {
if (data[i].equals(elem)) {
return i;
}
}
return -1; //没有找到返回 -1
}
//删除元素 (通常要返回相应的元素)
public E remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Remove failed, Index is illegal");
}
//先把要返回的值存储起来
E ret = data[index];
//覆盖删除: 把 index+1 的值移动到 index --> 把 size-1 的值移动到 size-2
for (int i = index + 1; i < size; i++) {
data[i - 1] = data[i];
}
size--;
//data[size] 不必清空,因为用户取不到 data[size]
//当使用泛型,对对象元素支持时
data[size] = null; //方便 java 回收 loitering objects (非必须,闲逛对象 != memory leak)
return ret;
}
//快捷删除尾部元素
public E pop() {
return remove(size - 1);
}
//快捷删除头部元素
public E popLeft() {
return remove(0);
}
//删除指定元素 (不必返回,因为用户已经知道 elem)
public void removeElem(E elem) {
int index = find(elem);
if (-1 == index) {
throw new IllegalArgumentException("Remove failed, cannot find this elem");
}
//成功了什么都不提示,出错才提示
remove(index);
}
}
测试代码如下:
//这个类主要用于 AdvanceArray 做泛型测试
public class Student {
private String name;
private int score;
public Student(String studentName, int studentScore){
name = studentName;
score = studentScore;
}
@Override
public String toString() {
return String.format("Student(name: %s, score: %d)", name, score);
}
}
// main.java 中:
public class Main {
private static void test_2(){
//泛型不支持基本类型,所以这里写 Integer;使用时会自动box,unbox
AdvanceArray<Integer> arr = new AdvanceArray<>(20); //容量20
//放入 10 个元素
for (int i = 0; i < 10; i++) {
arr.append(i);
}
System.out.println(arr);
//插入试试
arr.insert(1, 100);
System.out.println(arr);
//[0, 100, 1, 2, 3, 4, 5, 6, 7, 8, 9]
//删除试试
arr.remove(1);
System.out.println(arr); //[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
//删除末尾试试
arr.pop();
System.out.println(arr); //[0, 1, 2, 3, 4, 5, 6, 7, 8]
//删除头部试试
arr.popLeft();
System.out.println(arr); //[1, 2, 3, 4, 5, 6, 7, 8]
//删除 4 这个数字
arr.removeElem(4);
System.out.println(arr); //[1, 2, 3, 5, 6, 7, 8]
}
private static void test_3(){
AdvanceArray<Student> arr = new AdvanceArray<>(); //默认容量是 10
//添加测试数据
arr.append(new Student("AAA", 100));
arr.append(new Student("BBB", 90));
arr.append(new Student("CCC", 60));
System.out.println(arr);
}
}
// 测试结果
MyArray: size=3, capacity=10
[Student(name: AAA, score: 100), Student(name: BBB, score: 90), Student(name: CCC, score: 60)]
动态支持
上面其实没有支持动态扩容,也就不能称为一个
动态数组
上面的做法是,一旦检测到索引异常,基本都抛异常结束流程了;正常索引就插入,但没有考虑插入之后超过容量了怎么办?(在容量满的时候就应该检测到了)
解决方案,最基本的就是:
- 1.开辟新空间
- 2.把元素复制过去
但这只是扩容支持,如果元素缩减到一定程度,那么也应该减少容量。
增加扩容
//主要就是修改 insert 函数
public void insert(int index, E elem) {
//检查 index 是合法的
if (index < 0 || index > size) {
throw new IllegalArgumentException("insert failed, need: index >=0 and index <= size ");
}
//满了,那么就扩容吧 -- 这里不再是抛异常对象了
if (size == data.length) {
resize(2 * data.length); //java arraylist 选择的是 1.5 倍
}
//先移动元素,从后面开始 (size-1 移动到 size --> index 移动到 index+1;腾出 index)
for (int i = size - 1; i >= index; i--) {
data[i + 1] = data[i];
}
data[index] = elem; //覆盖原来的 index 处的内容
size++;
}
private void resize(int newCapacity) {
E[] newData = (E[]) new Object[newCapacity];
//复制旧元素过去
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
data = newData;
}
简单测试一下:
//初始容量为 10,先初始化 10 个元素
AdvanceDynamicArray<Integer> arr = new AdvanceDynamicArray<>(); //初始容量默认为 10
for (int i = 0; i < 10; i++) {
arr.append(i);
}
System.out.println(arr);
//再添加一个元素,试试
arr.append(100);
System.out.println(arr);
//输出结果:
MyArray: size=10, capacity=10
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
MyArray: size=11, capacity=20
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100]
删除缩减
删除元素的之后,如果空间小到一定程度,自动缩减空间
- 只要就是在删除后,做一下 size 和 capacity 的判断,然后 resize
(即删除后,size 为 capacity 的一半就可以缩容了)
//初始容量为 10,先初始化 10 个元素
AdvanceDynamicArray<Integer> arr = new AdvanceDynamicArray<>(); //初始容量默认为 10
for (int i = 0; i < 10; i++) {
arr.append(i);
}
System.out.println(arr);
//再添加一个元素,试试 -- 此时容量变为 20,实际占用 11
arr.presert(100);
System.out.println(arr);
//pop一个,容量立马缩减为 10
arr.pop();
System.out.println(arr);
//运行结果为:
MyArray: size=10, capacity=10
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
MyArray: size=11, capacity=20
[100, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
MyArray: size=10, capacity=10
[100, 0, 1, 2, 3, 4, 5, 6, 7, 8]
最后,扩容或者删除容量,对于 client 而言,它并不知道。(扩容策略也可以在细致些)
复杂度分析
O(1) -> O(logn)优良 —> O(n) 一般 —> O(nlogn) 一般般,其实和O(n)差不太多 —> O(n^2) 差 —>O(C^n) 超差
一般谈起来,会说和运行规模成: 常数关系,对数关系,线性关系,nlogn,平方关系,指数关系。简单理解就是,级大一级压死人。
详细分析
添加操作:
末尾插入 append: O(1)
头添加 presert: O(n)
任意位置插入 insert(index): 在 O(1) 和 O(n) 之间,假设 index 的取值概率一样,index 靠前的时候越慢,越靠后越快,整体需要靠具体需要移动多少个元素,n + (n-1) + … + 0,n 种情况,平均需要移动的元素 n/2,所以复杂度是 O(n/2),即 O(n)
resize 的操作也是 O(n)。
(尾部操作也可能是 O(n),但几率比较小,不可能每次都触发 resize)
综上所述,以最差来考虑,即 O(n)。 (一般考虑的是概率最高的情况)
删除操作: (其实分析方法类似,核心因素还是元素的移动)
- 末尾删除 pop(): O(1)
- 头删除 popLeft(): O(n) 因为元素要往前面移动
- 任意位置,平均 remove(index): O(n/2),即 O(n)
resize 的操作也是 O(n)。
(头部操作也可能是 O(n),但几率比较小,不可能每次都触发 resize)
综上所述,以最差来考虑,即 O(n)。
修改操作: 因为可以支持随机访问(按地址,索引),所以复杂度 O(1)
查找操作:
- get(index): O(1)
- contains(e): O(n) 线性遍历
- find(e): O(n) 线性遍历
从上面也可以看出,时间复杂度和顺序存储情况一致。
均摊复杂度
也就是仔细考虑一下包含这个 扩容
&缩容
耗时操作的 remove/insert 到底应该算作 O(1) 还是 O(n)。
比如 addLast()
本来是 O(1),加入动态支持后,此时还是 O(1) 么。
显而易见,容易得证:
假设 capacity = n,那么进行 n+1 次 addLast,触发扩容,此时包括移动元素,总共操作 2n+1 次,也就是说平均每次 addLast 触发 (2n+1)/(n+1) 即 2 次操作,即常量次操作,所以 addLast() 算均摊,也是 O(1)。
震荡优化
刚添加一个元素,扩容;立马删除一个元素,又缩容
连环的 addLast 和 removeLast 导致最近的操作都是 O(n)。(常规而言是O(1)的)
此时就要延迟处理(lazy)缩容,即真实元素为 1/4 capacity时才容量减半。
代码如下:
//删除元素 (通常要返回相应的元素)
public E remove(int index) {
if (index < 0 || index >= size) {
throw new IllegalArgumentException("Remove failed, Index is illegal");
}
//先把要返回的值存储起来
E ret = data[index];
//覆盖删除: 把 index+1 的值移动到 index --> 把 size-1 的值移动到 size-2
for (int i = index + 1; i < size; i++) {
data[i - 1] = data[i];
}
size--;
//data[size] 不必清空,因为用户取不到 data[size]
//当使用泛型,对对象元素支持时
data[size] = null; //方便 java 回收 loitering objects (非必须,闲逛对象 != memory leak)
//缩减 (只在一个特定的时机缩减,不能用 < 或者 >,可能会存在多次缩减)
//注意一下,延迟缩减,即 1/4 时,才缩减为一半 (还有一半是空的)
if(size == data.length/4 && data.length/2 != 0) {
resize(data.length/2); //那么就缩减为一半
}
return ret;
}
上面有个技巧或者编程语言问题, data.length/2 != 0
,因为 java 的除法默认是截断的,即可能出现 capacity 为 0 的情况。比如,当前capacity = 1 即 data.length 为 1,即便 size=0,也不能再缩减了(否则执行缩减, capacity 就为 0了)。
总结
顺序存储的结构,不管是不是线性的,基本都有这些操作特征,只不过上层接口隐藏了实现细节,比如 顺序栈
这类受限的结构,底层就是封装了动态数组(而非原始数组了)。
实现的时候注意移动操作从哪里开始,索引合法性判断以及记得维护 size。
深怕有人需要这些粗鄙的代码,我还是把它放在了 gayhub 上,供您参考。
如有不足之处,万望批评指正,虾虾侬。
纯数据结构Java实现(1/11)(动态数组)的更多相关文章
- 纯数据结构Java实现(5/11)(Set&Map)
纯数据结构Java实现(5/11)(Set&Map) Set 和 Map 都是抽象或者高级数据结构,至于底层是采用树还是散列则根据需要而定. 可以细想一下 TreeMap/HashMap, T ...
- 纯数据结构Java实现(0/11)(开篇)
为嘛要写 本来按照我的风格,其实很不喜欢去写这些细节的东西,因为笔记上直接带过了. 本来按照我的风格,如果要写,那也是直接上来就干,根本不解释这些大纲,参考依据. 本来按照我的风格,不想太显山露水,但 ...
- 纯数据结构Java实现(3/11)(链表)
题外话: 篇幅停了一下,特意去看看其他人写的类似的内容:然后发现类似博主喜欢画图,喜欢讲解原理. (于是我就在想了,理解数据结构的确需要画图,但我的文章写给懂得人看,只配少量图即可,省事儿) 下面正题 ...
- 纯数据结构Java实现(2/11)(栈与队列)
栈和队列的应用非常多,但是起实现嘛,其实很少人关心. 但问题是,虽然苹果一直宣传什么最小年龄的编程者,它试图把编程大众化,弱智化,但真正的复杂问题,需要抽丝剥茧的时候,还是要 PRO 人士出场,所以知 ...
- 纯数据结构Java实现(6/11)(二叉堆&优先队列)
堆其实也是树结构(或者说基于树结构),一般可以用堆实现优先队列. 二叉堆 堆可以用于实现其他高层数据结构,比如优先队列 而要实现一个堆,可以借助二叉树,其实现称为: 二叉堆 (使用二叉树表示的堆). ...
- 纯数据结构Java实现(4/11)(BST)
个人感觉,BST(二叉查找树)应该是众多常见树的爸爸,而不是弟弟,尽管相比较而言,它比较简单. 二叉树基础 理论定义,代码定义,满,完全等定义 不同于线性结构,树结构用于存储的话,通常操作效率更高.就 ...
- 纯数据结构Java实现(10/11)(2-3树&红黑树)
欢迎访问我的自建博客: CH-YK Blog.
- 纯数据结构Java实现(9/11)(AVL)
欢迎访问我的自建博客: CH-YK Blog.
- 纯数据结构Java实现(8/11)(Trie)
欢迎访问我的自建博客: CH-YK Blog.
随机推荐
- 【排序函数讲解】sort-C++
c++标准库里的排序函数,用于对给定区间所有元素进行排序.头 文件是#include 使用 Sort()在具体实现中规避了经典快速排序可能出现的.会导 致实际复杂度退化到 o(n²)的极端情况.它根据 ...
- C#中对EXCEL保存的SAVEAS方法说明
这两天做的导出报表的项目中,因为出现了一些问题所以对于excel一些方法参数有了一些认识, 首先:开始生成的是.xls格式的excel文件,但是某个sheet发现我本来dataTable的数据有8万多 ...
- linux 反弹shell
Linux下反弹shell笔记 0x00 NC命令详解 在介绍如何反弹shell之前,先了解相关知识要点. nc全称为netcat,所做的就是在两台电脑之间建立链接,并返回两个数据流 可运行在TCP或 ...
- 个人永久性免费-Excel催化剂功能第68波-父子结构表转换之父子关系BOM表拆分篇
Excel中制造业行业中,有一个非常刚需的需求是对BOM(成品物料清单)的拆解,一般系统导出的BOM表,是经过压缩处理的,由父子表结构的方式存储数据.对某些有能力使用SAP等专业ERP软件的工厂来说, ...
- c语言进阶9-值传递与地址传递
一. 函数的值传递 函数的值传递是指参数为基本类型时,如整型.浮点型.字符型(特指单字符型)时,参数传递时是从实参拷贝一份值传给形参,形参的变化不会影响实参的值. 1. 基本类型做参数 ...
- [HDOJ] 1753.大明A+B (大数加法)
Problem Description 话说,经过了漫长的一个多月,小明已经成长了许多,所以他改了一个名字叫"大明". 这时他已经不是那个只会做100以内加法的那个"小明 ...
- 总结暨JAVAWEB学习开篇(一)
匆匆,距上一篇博客已经过去7月有余,遂作文一篇总结暨JAVAWEB学习开篇. 1. 啃英文新概念.在多方讨教英语大佬后改变学习方式,通过背诵英文书籍以及多听英文录音来学习,效果还不错(等真正有成效了跟 ...
- 浅谈hosts文件
1.什么是hosts文件?这个文件在哪? hosts文件(域名解析文件)是将主机名映射到IP地址的一个纯文本文件,原始名称是HOSTS.TXT(IP,Internet Protocol,Interne ...
- windos10专业版激活(可用)
电脑提示Windows许可证即将到期,于是自己就在网上找了一些教程,但是并没有激活成功,反而由即将到期变为了通知状态,尝试了各种密钥都不行,也下载了激活工具如暴风激活工具,KMS都不管用,尝试了好多方 ...
- java练习---1
//程序员:罗元昊 2017.9.6public class Ap{ public static void main(String[] args){ System.out.println(" ...