重拾java之路-集合

Java容器

Collection

Collection接口常用实现

Set

TreeSet基于 红黑树 实现,支持有序性操作,查找效率为O(log n);
HashSet基于 哈希表 实现, 使用 Iterator 遍历访问时得到的结果是不确定的。
LinkedHashSet:使用 哈希表双向链表 实现,维护了元素的插入顺序。

HashSet中的value使用的是PRESENT(Object对象),不采用null是因为在remove时,失败返回的是null。

List

ArrayList 基于 动态数组 实现,但是不是线程安全的;(默认大小为 10
Vector 是线程安全的(使用synchronized进行同步);
LinkedList 基于 双向链表 实现,由于链表的特性,只能顺序访问,对于需要较多插入删除操作的应用比较适用。

Queue

LinkedList 可用于实现双向队列;
PriorityQueue 基于 结构实现,用于实现优先队列。(根据优先级出队)

ArrayDeque 实现了Deque接口,同时支持两端添加移除元素,因此可以用于替代Stack(继承自Vector),也能用于实现Queue(效率比LinkedList好)。但是不是线程安全的,并且不支持null元素。

Map

TreeMap: 基于 红黑树 实现;
HashMap: 基于 哈希表 实现,底层基于 数组+链表(头插法)实现(1.7),后面由于链表查找效率太低增加了红黑树的结构(尾插法)(1.8);(HashMap是线程不安全的,多线程编程需要使用ConcurrentHashMap)(默认大小为 16,为2的指数倍是为了加快求余计算,扩容时机为当map中包含的Entry的数量大于等于threshold = loadFactor * capacity的时候,且新建的Entry刚好落在一个非空的桶上,此刻触发扩容机制,将其容量扩大为2倍
HashTable: 遗留类,是线程安全的,get与put方法都需要进行同步,效率不高。
LinkedHashMap。 (LeetCode LRU缓存机制的实现

在遍历Map中的元素时,使用 entrySet() 方法得到的是Map.Entry<type, type> 对象,使用getKey(),getValue() 方法来获取键值对。

关于Java8中的流特性:
将 entrySet() 方法获得的流对象使用链式操作后,得到的是 Optional 对象,其中封装了Map.Entry对象。

面试题: HashMap的红黑树阈值为什么是8?为什么红黑树退化为链表的阈值不是7?

  1. 主要是为了寻找一种时间与空间的平衡。产生8次Hash碰撞的概率已经很低了,因此如果发生了8次,说明发生碰撞的可能性比较大,因此采用红黑树的结构,以空间换时间。
  2. 如果阈值之间设置得比较接近,则当出现阈值间的hash碰撞时,会产生链表与红黑树的不停转换,产生资源的浪费。

fail-fast 机制

定义:是java集合中的一种错误机制。当多个线程对集合的内容进行操作时,就可能会出现fail-fast事件。

案例:当线程A通过Iterator遍历集合的过程中,若集合的内容被其它线程改变了,则A访问该集合时就会抛出 ConcurrentModificationException,出现fail-fast事件。(使用增强型for循环时尝试直接修改容器中的内容时,即使是单线程也会出现该异常)

解决方案: 使用 Iterator.remove() 方法进行删除。

public static void main(String[] args) {
        List<Integer> test = new ArrayList<>(Arrays.asList(1,2,3,1));

        // 如果没有break则会出现 ConcurrentModificationException
        for(Integer i:test){
            System.out.println(i);
            if(i == 3){
                test.remove(new Integer(3));
                // 由于及时跳出,迭代器与修改操作不冲突,所以不会出现异常
                break;
            }
        }

		// 遍历的同时移除元素的正确姿势
        Iterator<Integer> iter = test.iterator();
        while(iter.hasNext()){
            Integer curI = iter.next();
            System.out.println(curI);
            if(curI == 3)
                iter.remove();
        }
    }

多线程下部分容器的替代方案

synchronizedList

可以使用 Collections.synchronizedList() 来获得一个线程安全的ArrayList。(实质是对对内部方法增加 synchronized关键字悲观锁)

该替代类中迭代器遍历(Iterator)时是需要在外部加锁的,适用于不需要使用迭代器并且对性能要求也不高的情况。

CopyOnWriteArrayList

使用 JUC工具包中的CopyOnWriteArrayList 类。(还有Set类)
内部使用 ReentrantLock类实现同步,add方面每次都需要复制原始数组。(写时复制容器,适合读多写少的场景,写时加入重入锁)。

synchronizedList 与Vector 比较

前者扩容为原来长度的1.5倍,后者为2倍;
前者有较好的扩展与兼容性,可以将List的子类转化为线程安全的类;
前者遍历时需要手动进行同步处理;
前者可以指定锁的对象。

使用增强型for循环(foreach,底层实际也是Iterator迭代器)时,两者都需要加锁

ConcurrentHashMap

HashMap在并发时的问题:
HashMap在接近临界点时,若此时两个或者多个线程继续进行put操作,则都会进性 resize(扩容) 和 ReHash(为key重新计算所在的位置),而ReHash在并发的情况下回形成链表环。

ConcurrentHashMap在实现上与HashMap类似,主要差别是其采用了分段锁(Segment,继承于 ReentrantLock)的技术,每个分段维护着几个桶,多个线程可以同时访问不同分段锁上的桶,从而使得其并发度更高(并发度为Segment的个数,默认16

在1.8版本中,换成使用 CAS(Compare-and-Swap原子操作)操作来提高并发度,并在其失败时使用内置锁synchronized。

泛型

定义:即 参数化类型,将原来具体的类型进行参数化。在泛型使用过程中,操作数据类型被指定为一个参数,可用于类接口以及方法当中。

泛型只在编译阶段有效。在逻辑上看成是多个不同的类型,实际上为相同的基本类型。

List<String> t1 = new ArrayList<>();
        List<Integer> t2 = new ArrayList<>();

        Class classt1 = t1.getClass();
        Class classt2 = t1.getClass();

        // 对于泛型,系统输出为true,表示两者在编译后是相同的
        if(classt1.equals(classt2))
            System.out.println("true");
        else
            System.out.println("false");

注意点:

  1. 泛型的类型参数只能为类类型,不能为简单类型。
  2. 同一种泛型可以对应多个版本,但是不同版本的泛型实例是不兼容的。(Integer为Number的子类,但是泛型中定义了Number的类并不能使用定义了Integer的类)
  3. 泛型类是在实例化类的时候指明泛型的具体类型,而泛型方法则是在调用方法的时候指明泛型的具体类型。
  4. 静态方法无法访问类上定义的泛型(静态方法在类声明时就得知道数据类型)。如果要使用泛型,则需要将其定义到方法上。(即泛型方法,修饰符与返回类型之间)

序列化与反序列化

序列化: 把对象转化为可传输的字节序列的过程;
反序列化:把字节序列还原为对象。

实现方面:实现 Serializable 接口即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
CGI(Common Gateway Interface)是一种标准,用于在Web服务器上运行外部程序。 CGI程序可以与Web服务器进行通信,从而让Web服务器获取外部程序生成的数据,并将其返回给客户端浏览器。在这个过程中,CGI程序可以读写文件、处理表单数据、查询数据库等。 文件上传是Web应用程序中非常常见的一种功能。上传文件的过程涉及到客户端浏览器将文件数据发送到Web服务器,Web服务器将文件保存到指定的目录中,并将文件相关的信息存储到数据库中。CGI程序可以处理上传文件的请求,并实现文件的保存和数据库的更新等操作。 cgicc是一个C++库,用于处理CGI程序中的表单数据。它提供了一组简单易用的API,可以方便地读取和处理表单数据,并且支持文件上传等功能。使用cgicc可以极大地简化CGI程序的开发。 下面是一个使用cgicc处理文件上传的示例: ```cpp #include <iostream> #include <fstream> #include <cgicc/Cgicc.h> #include <cgicc/HTTPHTMLHeader.h> #include <cgicc/HTMLClasses.h> using namespace std; using namespace cgicc; int main() { Cgicc cgi; const_file_iterator file = cgi.getFile("file"); if(file != cgi.getFiles().end()) { string filename = file->getName(); string filepath = "/var/www/upload/" + filename; ofstream ofs(filepath.c_str(), ios::out | ios::binary); file->writeToStream(ofs); ofs.close(); cout << HTTPHTMLHeader() << endl; cout << HTMLDoctype(HTMLDoctype::eStrict) << endl; cout << html().set("lang", "en").set("dir", "ltr") << endl; cout << head() << title("File Upload Result") << head() << endl; cout << body() << h1("File Upload Result") << endl; cout << p("File " + filename + " uploaded successfully!") << endl; cout << body() << html(); } else { cout << HTTPHTMLHeader() << endl; cout << HTMLDoctype(HTMLDoctype::eStrict) << endl; cout << html().set("lang", "en").set("dir", "ltr") << endl; cout << head() << title("File Upload Result") << head() << endl; cout << body() << h1("File Upload Result") << endl; cout << p("No file uploaded!") << endl; cout << body() << html(); } return 0; } ``` 在这个示例中,我们使用cgicc库处理表单数据,并通过getFile函数获取上传的文件。如果getFile返回的迭代器不等于getFiles返回的迭代器末尾,说明有文件上传。我们可以通过getName获取上传文件的名称,并指定文件保存的路径。然后,我们使用writeToStream将文件写入到指定的文件路径中。 最后,我们输出一个HTML响应,显示文件上传的结果。如果有文件上传成功,输出“File uploaded successfully!”,否则输出“No file uploaded!”。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值