Java 集合框架 04
集合框架·Map 和 Collections集合工具类
Map集合的概述和特点
* A:Map接口概述
* 查看API可知:
* 将键映射到值的对象
* 一个映射不能包含重复的键
* 每个键最多只能映射到一个值
* B:Map接口和Collection接口的不同
* Map是双列的,Collection是单列的
* Map的键唯一,Collection的子体系Set是唯一的
* Map集合的数据结构值针对键有效,跟值无关;Collection集合的数据结构是针对元素有效
* Set底层依赖的是Map
Map集合的功能概述
* A:Map集合的功能概述
* a:添加功能
* V put (K key, V value) ;添加元素
* 如果键是第一次存储,就直接存储元素,返回null
* 如果键不是第一次存储,就用替换原来的值,返回以前的值
* b:删除功能
* void clear() :移除所有的键值对元素
* V remove(Object key) :根据键删除键值对的元素,并把值返回
* c:判断功能
* boolean containsKey(Object key) :判断集合是否包含指定的键
* boolean containsValue(Object value) :判断集合是否包含指定的值
* boolean isEmpty() :判断集合是否为空
* d:获取功能
* Set<Map.Entry<K, V>> entrySet()
* V get(Object key) :根据键获取值
* Set<K> keySet() :获取集合中所有键的集合
* Collection<V> values() :获取集合中所有值的集合
* e:长度功能
* int size() :返回集合中的键值对个数
package com.heima.map; import java.util.Collection;
import java.util.HashMap;
import java.util.Map; public class Demo1_Map { public static void main(String[] args) {
// demo1(); // 添加元素
// demo2(); // 删除元素
// demo3();
} public static void demo3() {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("张三", 23);
map.put("李四", 24);
map.put("王五", 25);
map.put("赵六", 26); Collection<Integer> c = map.values();
System.out.println(c); System.out.println(map.size()); // 返回元素的值,一个键值对算作一个元素
} public static void demo2() {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("张三", 23);
map.put("李四", 24);
map.put("王五", 25);
map.put("赵六", 26); Integer integer = map.remove("张三"); // 根据键删除元素,并且返回键对应的值
System.out.println(map);
System.out.println(integer);
System.out.println(map.containsKey("张三")); // 判断是否包含指定键
System.out.println(map.containsValue(24)); // 判断是否包含指定值
} public static void demo1() {
Map<String, Integer> map = new HashMap<String, Integer>(); // 创建双链集合对象
Integer v1 = map.put("张三", 23); // 返回被覆盖的值,原先如果没有值,就返回null
Integer v2 = map.put("李四", 24);
Integer v3 = map.put("王五", 25);
Integer v5 = map.put("张三", 26); // 相同的键不存储,替换值并返回原值 System.out.println(map); // System.out.println(v1); // null
System.out.println(v2); // null
System.out.println(v3); // null
System.out.println(v5); // 23
} }
Map
Map集合的遍历之根据键找值
* A:键找值的思路
* 获取所有键的集合
* 遍历键的集合,获取到每一个键
* 根据键找值
* B:案例演示
* Map集合的遍历之键找值
* C:Map集合不能直接迭代
package com.heima.map; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set; public class Demo2_Iterator {
// Map集合不能直接得带
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("张三", 23);
map.put("李四", 24);
map.put("王五", 25);
map.put("赵六", 26); Integer i = map.get("张三"); // 根据键获取值
System.out.println(i); demo1(map);
System.out.println("---------");
demo2(map);
} public static void demo2(Map<String, Integer> map) {
// 使用增强for循环
for (String key : map.keySet()) { // map.KeySet() 是所有键的集合
System.out.println(key + "=" + map.get(key));
}
} public static void demo1(Map<String, Integer> map) {
// 获取所有键, 对键的集合迭代 Set<String> keySet = map.keySet(); // 获取所有键的集合
// System.out.println(keySet);
Iterator<String> it = keySet.iterator(); // 获取键的集合的迭代器
while (it.hasNext()) { // 判断集合中是否有元素
String string = (String) it.next(); // 获取每一个键
Integer valueInteger = map.get(string);
System.out.println(string + "=" + valueInteger);
}
}
}
Map
Map集合的遍历之通过键值对对象找键和值
* A:键值对对象找键和值的思路
* 获取所有键值对对象的集合
* 遍历键值对对象的集合,获取到每一个键值对对象
* 根据键值对对象找键和值
* B:案例演示
* Map集合的遍历之键值对对象中键和值
package com.heima.map; public class Demo4_MapEntry { public static void main(String[] args) { }
} interface Inter {
interface Inter2 {
public void show();
}
} class Demo implements Inter.Inter2 { // 实现 接口内的接口
@Override
public void show() { // 重写抽象方法
System.out.println("show");
} }
类似Entry的定义
package com.heima.map; import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set; public class Demo3_Iterator {
// 根据键值对对象获取键和值
public static void main(String[] args) {
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("张三", 23);
map.put("李四", 24);
map.put("王五", 25);
map.put("赵六", 26); // demo1(map);
// demo2(map);
} public static void demo2(Map<String, Integer> map) { // 开发中推荐使用
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
} public static void demo1(Map<String, Integer> map) {
// Map.Entry 说明 Entry是Map接口内的接口,将 键值对 封装成Entry对象,并存储在Set集合中 //Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
//Iterator<Map.Entry<String, Integer>> it = entrySet.iterator;
Iterator<Map.Entry<String, Integer>> it = map.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Integer> entry = it.next();
String key = entry.getKey(); // 根据键值对对象 获取键
Integer value = entry.getValue(); // 根据键值对对象 获取值 System.out.println(key + "=" + value);
}
}
}
Entry
HashMap集合键是Student,值是String的案例
* A:案例分析
* HashMap集合键是Student,值是String的案例
package com.heima.map; import java.util.HashMap; import com.heima.bean.Student; public class Demo5_HashMap { public static void main(String[] args) {
HashMap<Student, String> hm = new HashMap<Student, String>();
// 键是学生对象,代表每一个学生
// 值是字符串对象,代表学生归属地
hm.put(new Student("张三", 23), "北京");
hm.put(new Student("张三", 23), "杭州"); // 自定义类的对象需要重写hashCode和equals
hm.put(new Student("李四", 24), "上海");
hm.put(new Student("王五", 25), "广州");
hm.put(new Student("赵六", 26), "深圳"); System.out.println(hm);
}
}
HashMap
LinkedHashMap的概述和使用
* A:案例演示
* LinkedHashMap的特点
* 底层是链表实现的可以保证怎么存就怎么取
package com.heima.map; import java.util.LinkedHashMap; public class Demo6_LinkedHashMap { public static void main(String[] args) {
LinkedHashMap<String, Integer> lhm = new LinkedHashMap<String, Integer>();
lhm.put("张三", 23);
lhm.put("李四", 24);
lhm.put("赵六", 26);
lhm.put("王五", 25); System.out.println(lhm);
}
}
LinkedHashMap
TreeMap集合键是Student,值是String的案例
* A:案例演示
* TreeMap集合键是Student,值是String的案例
package com.heima.map; import java.util.Comparator;
import java.util.TreeMap; import com.heima.bean.Student; public class Demo7_TreeMap { public static void main(String[] args) {
// demo1(); // 类中实现comparable接口,add方法调用compareTo方法
// demo2(); // 集合的构造方法中使用比较器comparator,add方法调用compare方法
} public static void demo2() {
TreeMap<Student, String> tm = new TreeMap<Student, String>(new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
int num = o1.getName().compareTo(o2.getName());
return num == 0 ? o1.getAge() - o2.getAge() : num;
}
});
tm.put(new Student("张三", 23), "北京");
tm.put(new Student("李四", 24), "上海");
tm.put(new Student("赵六", 26), "深圳");
tm.put(new Student("王五", 25), "广州"); System.out.println(tm);
} public static void demo1() {
TreeMap<Student, String> tm = new TreeMap<Student, String>();
tm.put(new Student("张三", 23), "北京");
tm.put(new Student("李四", 24), "上海");
tm.put(new Student("赵六", 26), "深圳");
tm.put(new Student("王五", 25), "广州"); System.out.println(tm);
}
}
TreeMap
统计字符串中每个字符出现的次数
* A:案例演示
* 需求:统计字符串中每个字符出现的次数
package com.heima.test; import java.util.HashMap;
import java.util.Scanner; public class Test1 {
/*
* 分析:
* 1、创建键盘录入对象
* 2、录入字符串
* 3、将字符串转换为字符数组
* 4、定义双列集合,存储字符串中的字符以及字符出现的次数
* 5、遍历字符数组,获取每一个字符,并将字符存储在双列集合中
* 6、存储过程中做判断,如果集合中不包含这个键,就把这个字符作为键,值存储为1;如果包含,就将值加1存储
* 7、打印双列集合
*/
public static void main(String[] args) {
Scanner sc = new Scanner(System.in); // 创建键盘录入对象
String line = sc.nextLine(); // 录入字符串
char[] arr = line.toCharArray();// 将字符串转换为字符数组
HashMap<Character, Integer> map = new HashMap<Character, Integer>();// 没有特殊要求就用HashMap,因为它是所有Map中效率最高的
for (char c : arr) { // 遍历字符数组,获取每一个字符
/*if (!map.containsKey(c)) { // 如果不包含
map.put(c, 1); // 键为字符,值为1
} else { // 如果包含
map.put(c, map.get(c) + 1); // 键为字符,值为原值加一
}*/
map.put(c, !map.containsKey(c) ? 1 : map.get(c) + 1); // 做判断,并存储
}
System.out.println(map); // 打印双列集合
}
}
Test1
集合框架之HashMap嵌套HashMap
* A:案例演示
* 集合嵌套之HashMap嵌套HashMap
package com.heima.map; import java.util.HashMap; import com.heima.bean.Student; public class Demo8_HashMap { public static void main(String[] args) {
// 定义88期基础班
HashMap<Student, String> hm88 = new HashMap<Student, String>();
hm88.put(new Student("张三", 23), "北京");
hm88.put(new Student("李四", 24), "北京");
hm88.put(new Student("王五", 25), "上海");
hm88.put(new Student("赵六", 26), "广州");
// 定义99期基础班
HashMap<Student, String> hm99 = new HashMap<Student, String>();
hm99.put(new Student("唐僧", 1023), "花果山");
hm99.put(new Student("悟空", 1024), "长安城");
hm99.put(new Student("八戒", 1025), "高老庄");
hm99.put(new Student("沙僧", 1026), "流沙河");
// 定义双元课堂
HashMap<HashMap<Student, String>, String> hm = new HashMap<HashMap<Student, String>, String>();
hm.put(hm88, "第88期基础班");
hm.put(hm99, "第99期基础班"); for (HashMap<Student, String> hMap : hm.keySet()) { // hm.KeySet()代表的是双链集合中键的集合
String valString = hm.get(hMap); // get(hMap)根据键对象获取值对象
for (Student s : hMap.keySet()) { // 遍历键的双列集合对象,hMap.KeySet()获取集合中所有的学生键对象
String val2String = hMap.get(s);
System.out.println(s + "=" + val2String + "=" + valString);
}
System.out.println("---------------------------");
}
}
}
HashMap
HashMap和Hashtable的区别
* A: 面试题
* HashMap和Hashtable的区别
* Hashtable是JDK1.0版本出现的,是线程安全的,效率低;HashMap是JDK1.2版本出现的,是线程不安全的,效率高
* Hashtable不可以存储null键和null值,HashMap可以存储null键和null值
* B:案例演示
* HashMap和Hashtable的区别
package com.heima.map; import java.util.HashMap;
import java.util.Hashtable; public class Demo9_HashTable {
/* 面试题
* HashMap和Hashtable的区别:
* 共同点:底层都是哈希算法,都是双列集合
* 区别:前者线程不安全,效率高,JDK1.2版本出现;后者线程安全,效率相对较低,JDK1.0版本出现
* 前者可以存储null键和null值;后者不可以
*/
public static void main(String[] args) {
HashMap<String, Integer> hm = new HashMap<String, Integer>();
hm.put(null, null);
hm.put("张三", null);
System.out.println(hm); Hashtable<String, Integer> ht = new Hashtable<String, Integer>();
//ht.put(null, 23); // NullPointerException
//ht.put("张三", null);
}
}
Differences
Collections工具类的概述和常见方法讲解
* A:Collections类概述
* 针对集合操作的工具类
* B:Collections成员方法
public static <T> void sort(List<T>list)
public static <T> int binarySearch(List<?> list, T key)
public static <T> T max(Collection<?> coll)
public static void reverse(List<?> list)
public static void shuffle(List<?> list)
package com.heima.collections; import java.util.ArrayList;
import java.util.Collections; public class Demo1_Collections { public static void main(String[] args) {
// demo1();
// demo2();
// demo3();
} public static void demo3() {
ArrayList<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
list.add("g"); System.out.println(Collections.max(list)); // 底层会先排序,因此即使是无序的也能max
Collections.reverse(list); // 反转集合
Collections.shuffle(list); // 将元素打乱,可以用来洗牌
System.out.println(list);
} public static void demo2() {
ArrayList<String> list = new ArrayList<String>();
list.add("a");
list.add("b");
list.add("c");
list.add("d");
list.add("e");
list.add("f");
list.add("g"); System.out.println(Collections.binarySearch(list, "s"));
} public static void demo1() {
ArrayList<String> list = new ArrayList<String>();
list.add("a");
list.add("a");
list.add("b");
list.add("c");
list.add("a");
list.add("c");
list.add("a"); System.out.println(list);
Collections.sort(list); // 排序,底层调用compareTo或compare方法,重复的保留
System.out.println(list);
} }
Collections
模拟斗地主洗牌和发牌
* A:案例演示
* 模拟斗地主洗牌和发牌,牌没有顺序
package com.heima.test; import java.util.ArrayList;
import java.util.Collections; public class Test2 {
/*
* 分析:
* 1、买一副扑克,其实就是自己创建一个集合对象,将扑克牌存储进去
* 2、洗牌,调用Collections.shuffle方法
* 3、发牌,每个人17张,
* 4、看牌
*/
public static void main(String[] args) {
String[] num = { "A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K" }; // 定义牌的大小
String[] color = { "红桃", "黑桃", "方块", "梅花" }; // 定义牌的花色
ArrayList<String> poker = new ArrayList<String>(); // 定义扑克牌集合
// 拼接花色和数字
for (String s1 : color) { // 遍历花色数组
for (String s2 : num) { // 遍历牌面数组
poker.add(s1.concat(s2)); // 字符串拼接
}
}
poker.add("大王");
poker.add("小王"); Collections.shuffle(poker); // 洗牌 ArrayList<String> gaojin = new ArrayList<String>();
ArrayList<String> longwu = new ArrayList<String>();
ArrayList<String> me = new ArrayList<String>();
ArrayList<String> dipai = new ArrayList<String>(); for (int i = 0; i < poker.size(); i++) { // 发牌
if (i >= poker.size() - 3) {
dipai.add(poker.get(i)); // 将三张底牌存储在底牌集合中
} else if (i % 3 == 0) {
gaojin.add(poker.get(i));
} else if (i % 3 == 1) {
longwu.add(poker.get(i));
} else {
me.add(poker.get(i));
} } System.out.println(dipai); // 看牌
System.out.println(gaojin);
System.out.println(longwu);
System.out.println(me);
}
}
Test2
* 模拟斗地主洗牌和发牌,牌有顺序
package com.heima.test; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.TreeSet; public class Test3 {
/*
* 分析:
*/
public static void main(String[] args) {
String[] num = { "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2" }; // 定义牌面大小
String[] color = { "红桃", "黑桃", "方块", "梅花" }; // 定义花色
HashMap<Integer, String> hm = new HashMap<Integer, String>(); // 定义双链集合,存储索引和扑克牌
ArrayList<Integer> list = new ArrayList<Integer>(); // 存储索引,方便洗牌
int index = 0; // 定义索引
for (String s1 : num) {
for (String s2 : color) {
hm.put(index, s2.concat(s1)); // 拼接扑克牌,并将索引和扑克牌 存储在双链集合中
list.add(index); // 存储索引值
index++; // 索引值加加
}
} hm.put(index, "小王"); // 添加小王
list.add(index++); // 先添加小王索引,再加加
hm.put(index, "大王"); // 添加大王
list.add(index); // 添加大王索引 Collections.shuffle(list); // 洗牌 TreeSet<Integer> gaojin = new TreeSet<Integer>(); // 定义TreeSet使牌自动排序
TreeSet<Integer> longwu = new TreeSet<Integer>();
TreeSet<Integer> me = new TreeSet<Integer>();
TreeSet<Integer> dipai= new TreeSet<Integer>(); for (int i = 0; i < list.size(); i++) { //发牌
if (i >= list.size() - 3) {
dipai.add(list.get(i)); // 将三张底牌存储在底牌集合中
} else if (i % 3 == 0) {
gaojin.add(list.get(i));
} else if (i % 3 == 1) {
longwu.add(list.get(i));
} else {
me.add(list.get(i));
}
} kanpai(hm, dipai); // 看牌
kanpai(hm, gaojin);
kanpai(hm, longwu);
kanpai(hm, me);
}
/*
* 看牌:
* 1、返回值类型是void
* 2、参数列表HashMap键值关系,TreeMap牌的索引
*/
public static void kanpai(HashMap<Integer, String> hm, TreeSet<Integer> dipai) {
for (Integer integer : dipai) {
System.out.print(hm.get(integer)+" ");
}
System.out.println();
}
}
Test3
泛型固定下边界
* ?super E
package com.heima.collections; import java.util.ArrayList;
import java.util.Comparator;
import java.util.TreeSet; import com.heima.bean.BaseStudent;
import com.heima.bean.Student; public class Demo2_Genric {
/*
* ? super E 泛型固定下边界 儿子可以调用父亲的,存储子类的集合可以调用父类的comparator comparator
* ? extends E 泛型固定上边界 儿子的是父亲的,存储子类的集合可以添加到存储父类的集合中中 addAll
*/
public static void main(String[] args) {
// demo1();
TreeSet<Student> ts1 = new TreeSet<Student>(new CompareByName());
ts1.add(new Student("张三", 23));
ts1.add(new Student("李四", 13));
ts1.add(new Student("王五", 23));
ts1.add(new Student("赵六", 43)); System.out.println(ts1); TreeSet<BaseStudent> ts2 = new TreeSet<BaseStudent>(new CompareByName());
ts2.add(new BaseStudent("张三", 23));
ts2.add(new BaseStudent("李四", 13));
ts2.add(new BaseStudent("王五", 23));
ts2.add(new BaseStudent("赵六", 43)); System.out.println(ts2);
} public static void demo1() {
// ? extends E 泛型固定上边界
ArrayList<Student> list1 = new ArrayList<Student>();
list1.add(new Student("张三", 23));
list1.add(new Student("李四", 24)); ArrayList<BaseStudent> list2 = new ArrayList<>();
list2.add(new BaseStudent("张三", 23));
list2.add(new BaseStudent("李四", 24)); list1.addAll(list2); // 把子类对象添加到父类中
}
} class CompareByName implements Comparator<Student> { @Override
public int compare(Student o1, Student o2) {
int num = o1.getName().compareTo(o2.getName());
return num == 0 ? o1.getAge() - o2.getAge() : num;
} }
generic
Java 集合框架 04的更多相关文章
- java集合框架04——LinkedList和源码分析
上一章学习了ArrayList,并分析了其源码,这一章我们将对LinkedList的具体实现进行详细的学习.依然遵循上一章的步骤,先对LinkedList有个整体的认识,然后学习它的源码,深入剖析Li ...
- Java 集合系列 04 LinkedList详细介绍(源码解析)和使用示例
java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...
- Java集合框架Collection
转自:http://www.cdtarena.com/javapx/201306/8891.html [plain] view plaincopyprint?01.在 Java2中,有一套设计优良的接 ...
- Java集合框架之四大接口、常用实现类
Java集合框架 <Java集合框架的四大接口> Collection:存储无序的.不唯一的数据:其下有List和Set两大接口. List:存储有序的.不唯一的数据: Set:存储无序的 ...
- 【转载】Java 集合框架
http://wangkuiwu.github.io/2012/02/03/collection-03-arraylist/ 网上比较全的Java集合框架教程. 注:transient是Java语言的 ...
- Java集合框架之LinkedList浅析
Java集合框架之LinkedList浅析 一.LinkedList综述: 1.1LinkedList简介 同ArrayList一样,位于java.util包下的LinkedList是Java集合框架 ...
- Java集合框架List,Map,Set等全面介绍
Java集合框架的基本接口/类层次结构: java.util.Collection [I]+--java.util.List [I] +--java.util.ArrayList [C] +- ...
- Java集合框架练习-计算表达式的值
最近在看<算法>这本书,正好看到一个计算表达式的问题,于是就打算写一下,也正好熟悉一下Java集合框架的使用,大致测试了一下,没啥问题. import java.util.*; /* * ...
- 【集合框架】Java集合框架综述
一.前言 现笔者打算做关于Java集合框架的教程,具体是打算分析Java源码,因为平时在写程序的过程中用Java集合特别频繁,但是对于里面一些具体的原理还没有进行很好的梳理,所以拟从源码的角度去熟悉梳 ...
随机推荐
- fzu2218 Simple String Problem
Accept: 2 Submit: 16 Time Limit: 2000 mSec Memory Limit : 32768 KB Problem Description Recent ...
- 牛客小白月赛17 A 小sun的假期
传送门 题意: 第一行两个数n,m,代表总共有n天,m个安排.接下来有m行,每行是一个安排l,r,代表从第l天到第r天,小sun有安排了.安排可能会重复. 小 sun 非常喜欢放假,尤其是那种连在一起 ...
- Codeforces Round #643 (Div. 2) B. Young Explorers (思维,贪心)
题意:给你一组人\(a\),现在要将这些人进行分组,对于\(i\),只有某一组的人数\(\ge a_{i}\)时,\(i\)才可以加入这个组,问最多能够有多少组,(不必将所有人都选用). 题解:我们将 ...
- CS144学习(1)Lab 0: networking warmup
CS144的实验就是要实现一个用户态TCP协议,对于提升C++的水平以及更加深入学习计算机网络还是有很大帮助的. 第一个Lab是环境配置和热身,环境按照文档里的配置就行了,前面两个小实验就是按照步骤来 ...
- 牛客网多校第7场 J Sudoku Subrectangles 【构造】
题目:戳这里 题意:给一个n*m的矩阵,里面由a~z及A~Z构成,问有多少个子矩阵满足任意一行或一列中都没有相同的字母. 解题思路:左上角和右下角两点可以确定一个矩阵.可以先预处理出来每个点作为一个矩 ...
- mybatis(五)mybatis工作流程
转载:https://www.cnblogs.com/wuzhenzhao/p/11103017.html 先来看一下MyBatis 的编程式使用的方法: public void testMapper ...
- 自己yy的中缀表达式转后缀表达式(未验证完全正确)
目前自己测试的表达式都没有出过问题 思路是这样,先将后缀表达式的计算顺序搞出来..当完全缩出来一个数的时候,如果后面还有要计算的,我们就把它放到后缀表达式的后面 先算后面的..不断迭代.. #incl ...
- Angular 2 for 2017 web full stack development
1 1 1 Angular 2 for 2017 web full stack development 1 1 https://angular2.xgqfrms.xyz/ https://ng2-he ...
- H5 广告落地页
H5 广告落地页 Landing Page 用于承接通过付费搜索渠道点击进入的用户,所以叫落地页 什么是登陆页面? 在数字营销中,登录页面是专门为营销或广告活动创建的独立网页. 访问者单击电子邮件中的 ...
- project config generator
project config generator React, Vue, https://createapp.dev/ parcel https://parceljs.org/ https://git ...